CirculeID

Webhooks

The interesting part is what happens when delivery fails

Signing, retries, ordering and idempotency are the whole contract. An integration that handles the happy path is not finished; it has not started.

Delivery
At least once
Signed
Over the raw body
Retries
Exponential backoff

Definition

How do passport webhooks work?

CirculeID posts a signed callback to your endpoint when a passport, event or credential changes. Delivery is at-least-once with exponential backoff, so handlers must verify the signature, treat repeated delivery identifiers as no-ops, and read current state back through the API before acting.

Treat a payload as a notification, not as the record. That single habit removes the entire class of bug where a delayed retry overwrites newer data with older data.

Events

What you can subscribe to

Grouped by resource. Subscribe narrowly — an endpoint receiving events it does not act on is an endpoint whose failures nobody investigates.
Webhook event types and what each one signals
EventFires whenTypical handler
passport.createdA passport is issued against an identifierPrint or encode the data carrier
passport.updatedThe record changes materiallyRe-read state; refresh a cached storefront view
event.recordedAn EPCIS event is appended to an objectAdvance an internal workflow
credential.issuedA supplier signs a claim against your productClear the compliance gap for that field
credential.revokedAn issuer withdraws a claimRe-open the gap; review anything that relied on it
passport.gap_detectedA delegated act change leaves a field unmetRaise it to the compliance owner

Verification

Verify before you parse

Compute the signature over the raw body — not over a re-serialised object, which will not match. Reject anything outside your timestamp tolerance.
Node.js
import { createHmac, timingSafeEqual } from 'node:crypto';

export function verify(rawBody, header, secret) {
  const [ts, signature] = parseHeader(header);

  // Reject replays before doing any work.
  if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false;

  const expected = createHmac('sha256', secret)
    .update(`${ts}.${rawBody}`)   // raw body, exactly as received
    .digest();

  // Constant-time: a fast reject leaks the signature one byte at a time.
  return timingSafeEqual(Buffer.from(signature, 'hex'), expected);
}

Contract

What the platform guarantees

  • Signed payloads

    HMAC over the raw body with a per-endpoint secret, plus a timestamp.

  • At-least-once delivery

    The honest guarantee for a retrying system. Handlers must be idempotent.

  • Exponential backoff

    Retried over an extended window, so a short outage costs you nothing.

  • Version on every payload

    Ignore a delivery describing a version you have already passed.

  • Visible failure log

    Failed deliveries are listed and replayable rather than silently dropped.

  • Scoped subscriptions

    Subscribe per event type, so an endpoint only receives what it acts on.

Answers

Frequently asked questions

How do I know a webhook really came from CirculeID?

Every delivery carries a signature over the raw request body and a timestamp. Verify the signature against your endpoint’s secret before parsing, and reject deliveries whose timestamp is outside your tolerance window. An endpoint that trusts an unverified payload is an endpoint anyone can post to.

Are deliveries ordered?

Per object, best effort — but do not depend on it. Retries and network conditions mean an older event can arrive after a newer one. Every payload carries the object version it reflects, so a handler should ignore a delivery describing a version it has already passed rather than assume arrival order.

What happens if my endpoint is down?

Delivery is retried with exponential backoff over an extended window, and the failure log is visible in your account rather than silent. After the window expires the delivery is marked failed; the underlying change is still queryable through the API, so a webhook outage causes a delay rather than data loss.

Can the same event be delivered twice?

Yes, and you should assume it will be. At-least-once delivery is the honest guarantee for any retrying system. Each delivery carries a stable identifier, so the correct handler records that identifier and treats a repeat as a no-op — which also makes replaying a failed window safe.

Should webhook payloads be trusted as the full record?

Treat the payload as a notification rather than as the source of truth. It tells you something changed and gives you enough to decide whether you care; if you act on it, read the current state back through the API. That way a stale or reordered delivery cannot write old data into your system.

Next step

Point one at a test endpoint

Subscribe in sandbox, break your endpoint on purpose, and watch the retry and replay behaviour before you rely on it.

Index