Skip to main content

Components

How a delivery flows

1

Publish (inside the request)

Every route that goes through the shared handler()/ok() wrapper calls publishApiTransactionEvent after a 2xx. The publisher derives the catalogue internal from the method and route, then checks the in-memory set of internals that currently have at least one active subscriber. If nobody listens, nothing is enqueued — the queue tables stay empty for the thousands of mutations no one subscribed to. Failures here are logged and never surface to the API caller.
2

Route (webhook-out-route)

The consumer loads the catalogue row, resolves the owning account of the mutated resource (see below), and selects the matching webhooks: global ones always match; account-scoped ones match only when the resolved account equals the webhook’s account. One webhook_dispatch row is inserted per match with status = pending, then one webhook-out-deliver message is sent per row.
3

Deliver (webhook-out-deliver)

The consumer builds the thin body, signs it with the subscriber secret, POSTs it with a 10 s timeout and redirect: "error", and records the outcome (success, error with next_retry_at, or exhausted) on the ledger row. Retryable failures are rescheduled on the custom backoff schedule; see Retries and failures.

Owning-account resolution

Account-scoped subscriptions need to know which account a mutated row belongs to. The catalogue records one of four strategies per event: The resolver reads the row once, on the route stage; deliveries never touch the source table again.

Lanes and schedule

Both topics run on the dedicated webhook-out lane (vercel.json → app/api/queue/consumers/webhook-out/route.ts) so a burst of deliveries cannot starve business jobs, and business backlogs cannot delay a subscriber’s notification. The deliver topic uses a custom retry schedule instead of the platform default — the subscriber-facing backoff is part of the public contract.

Feature flag

Publishing is gated by ENABLE_API_EVENT_QUEUE. While it is false the API behaves exactly as before: no catalogue lookup, no cache, no queue traffic. Operators flip it per environment after the catalogue is synced and the first subscriber has been tested; see Operations.
The body deliberately carries identifiers only. Never rely on a webhook to transport authoritative data or personal information; read the resource through the API with the subscriber’s own credentials.