Skip to content
PuraPura Developers
Developers/Lifecycle webhooks

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.

typescript
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.

typescript
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.