Skip to main content
Webhooks deliver real-time notifications to your server when events occur in Closient. Each webhook endpoint receives signed HTTP POST requests with JSON payloads.

Creating a Webhook Endpoint

Configure endpoints in Settings > Integrations in your Dashboard, or via the Integrations API. Each endpoint has:
  • URL — your HTTPS endpoint (HTTPS required in production)
  • Event types — which events to subscribe to, from the event catalog. Prefix matching is supported: subscribing to "recall" receives every recall.* event, including ones added later — the recommended form for safety-critical domains. Values outside the catalog are rejected with a 422, so a typo can never leave you silently subscribed to nothing.
  • API version — the envelope version this endpoint receives. Pinned at creation; we never move it.
  • Filters — optional per-event-type filtering rules (AND logic between filter keys, OR within a key)
  • Signing secret — auto-generated 64-byte hex secret for payload verification

Event Catalog

Fetch the live catalog — this is the authoritative list, and it is also published in the OpenAPI spec:

Recalls & safety

Consumer signals

Serial verification

System

Deprecated

offer.updated, product.recalled, and retailer.created predate the catalog. They remain valid in a subscription so existing integrations keep working, but no emitter is wired for them. product.recalled is superseded by the recall.* lifecycle above — build new integrations against that.
The catalog response also has a planned section listing event types on our roadmap. Those are not subscribable yet: subscribing returns a 422 naming the tracking issue, rather than accepting a subscription that would silently never fire.

Envelope Versioning

Every endpoint carries an api_version that pins the shape of the envelope it receives. We never move an endpoint’s pin. Shipping a new envelope version does not change what your existing integration receives — change api_version yourself, with a PATCH, once your parser is ready.

2026-07-01

CloudEvents-shaped field names, plus a cursor for replay:

v1

Same information, different field names, and no cursor (v1 predates the replay feed). The data block is identical across versions — versioning governs the envelope only, never the event payload.

Replay on Reconnect

Retries cover an endpoint that is failing. They do not cover an endpoint that was simply down, was auto-disabled after sustained failures, or did not exist yet. For a recall feed that gap matters more than anything else in this document: a subscriber who missed a recall and can’t tell they missed it is worse off than one with no feed at all. So every event is written to a durable log when it is emitted — before any delivery is attempted — and you can walk that log forward from wherever you left off:
The loop:
  1. Persist the cursor of the last event you durably processed — not the last one you received.
  2. On reconnect, call the endpoint with ?after=<that cursor>.
  3. Process the page, then repeat using the last cursor on the page, until a page comes back short.
Events are returned oldest first. Each carries an envelope that is byte-identical to what a live delivery would have POSTed, so a replayed event goes through the same handler with no special casing, and event_id is stable across the live delivery and every replay — deduplicate on it.
The cursor is a keyset over (occurred_at, id), not a timestamp. That is what makes the walk both gap-free and duplicate-free when two events share a timestamp — a plain since=<timestamp> filter can only pick one of those two failure modes.
An unrecognised cursor returns 422. We deliberately do not fall back to “start from the beginning” (which would flood you) or “start from now” (which would hide the very gap you reconnected to close).

Request Headers

Every delivery includes these headers:

Verifying Signatures

The X-Closient-Signature header uses a Stripe-style format that includes a timestamp for replay protection:
To verify:
  1. Extract the timestamp (t) and signature (v1) from the header.
  2. Construct the signed payload: {timestamp}.{raw_json_body}.
  3. Compute HMAC-SHA256 using your signing secret.
  4. Compare your computed signature with v1 using a constant-time comparison.
  5. Check the timestamp is within your tolerance window (recommended: 5 minutes).
Always verify the signature before processing a webhook payload. Reject requests with missing, expired, or invalid signatures.

Secret Rotation

When you rotate a signing secret via the API (POST /endpoints/{id}/rotate-secret), the previous secret remains valid for a 24-hour grace period. During this window, deliveries include both signatures:
Your verification code should check v1 first, then fall back to v1old. After the grace period, only v1 is included.

Delivery Lifecycle

Each delivery progresses through these statuses:

Retry Policy

Failed deliveries are retried with exponential backoff and jitter: After 8 attempts, the delivery moves to dead_letter. You can replay dead-lettered deliveries via the API:

Auto-Disable

If an endpoint has a 100% failure rate over the last 24 hours (with at least one delivery attempt), Closient automatically disables it and sends an email notification. Re-enable it from the Dashboard or API after resolving the issue.

Testing

Send a test event to verify your endpoint is reachable and signature verification works:
This delivers a test.ping event synchronously and returns the HTTP status code from your endpoint.

Best Practices

  • Return 2xx quickly — acknowledge receipt, then process asynchronously. Closient times out after 30 seconds.
  • Handle duplicates — use the id field (event_id on v1) for idempotency. The same event may arrive more than once via retries or a replay.
  • Checkpoint your cursor — persist the cursor of the last event you durably processed, so an outage becomes a replay rather than a silent gap. For recall events this is the difference between a feed you can rely on and one that manufactures false confidence.
  • Verify signatures — always validate X-Closient-Signature before processing. Reject unsigned or expired requests.
  • Use HTTPS — required in production. HTTP is only allowed in development.
  • Monitor health — check delivery status in the Dashboard or via the Integrations API. Address failures before they accumulate into auto-disable.
See the Integrations API reference for full endpoint management, delivery inspection, and filtering options.