Lifecycle webhooks
HMAC signatures, retries, identity and atomic event handling.
Configure the receiver
The app owner configures a public HTTPS endpoint in the console or with PUT /v1/integrations/apps/{client_id}/webhook. Save the returned signing_secret on your server. Webhooks are optional for inference and never deliver gateway keys or OAuth tokens.
Pura checks destination DNS, excludes local/private addresses and does not follow redirects. The endpoint must be reachable from the Pura worker. A test returns 202 and queued status: that alone does not prove delivery.
Events and payload
Events: integration.connected, integration.revoked, subscription.updated, subscription.ended, integration.test. The envelope contains api_version, event_id, event, integration_id, sequence, created, user and data. Bind it to the registered client_id and the pura_user_id already verified during OAuth.
Subscription events are lifecycle snapshots rather than a per-request billing feed. paid_period contains percentages and available services without model identifiers. Use usage() for current consumption. The Pura server remains authoritative and checks limits on every request.
Verify original bytes
X-Pura-Signature: t=<unix-seconds>,v1=<hex-HMAC-SHA256>. The signature covers the UTF-8 timestamp, a dot and the original body bytes. Default tolerance is ±300 seconds. Parsing and reserializing JSON changes bytes and must not precede verification.
import {verifyWebhook} from '@pura-ai/sdk';
const rawBody = new Uint8Array(await request.arrayBuffer());
const event = await verifyWebhook(rawBody, request.headers, {
signingSecret: serverSigningSecret,
integrationId: registeredClientId,
});Transactional idempotency
processOnce(eventId, handler) must check the ID, execute handler and save the ID in the same transaction shared by all workers. It returns false for an already committed event. Errors must roll back the marker too; return 2xx only after commit.
Use your own transactional outbox for email or other external effects. A database marker does not make an external service call atomic. process_once is the equivalent Python interface.
Receiver handler
The onEvent callback must use the same transaction context as processOnce. The helper verifies signature and app before invoking the store, then returns event_id and duplicate. Return this response with HTTP 200 only after the Promise resolves.
import {createWebhookHandler} from '@pura-ai/sdk';
const receive = createWebhookHandler({
signingSecret: serverSigningSecret,
integrationId: registeredClientId,
store: persistentEventStore,
onEvent: async event => applyEventInTheSameTransaction(event),
});
const result = await receive(rawBody, request.headers);
// Return HTTP 200 after processOnce committed.
// A verification error must not be acknowledged as a valid event.Ordering, retries and rotation
sequence is monotonic per app. The receiver must discard stale snapshots and detect gaps; the SDK does not apply ordering automatically. On retries event_id, created and sequence stay the same while the signed timestamp changes. Pura uses up to eight attempts with backoff.
Configuration returns a new generation and signing secret. Coordinate replacement with the receiver and inspect events in the console. Retry and skip are manual recovery actions: only skip when a missing event is acceptable.