Replay keeps the delivery attempt counter
Operator replay of a
webhook_dispatch row no longer resets attempts.
The next POST increments the same lifetime counter so Entregas and the
subscriber attempt field keep a full history. Automatic retries still
stop at five POSTs; replaying an exhausted row performs one additional
POST. See Retries and failures.Outbound webhooks — first release
Outbound webhooks (first release)
horizon-api now notifies external systems when a resource changes.What shipped- Event catalogue generated from the v3 route tree
(
<resource>.<action>), synced to theeventtable on every deploy and checked in CI. See the catalogue. - Subscriptions — global or per-account webhooks with a one-shot
whsec_secret,PUT /webhook/webhook/{id}/eventsto manage the event set, secret rotation and test deliveries. See Subscriptions. - Signed deliveries —
x-bbrands-signature: t=<ts>,v1=<hmac>over<ts>.<raw body>, 300 s tolerance, thin body withapi_version2026-09-11. See Delivery and signature. - Retries —
1 m → 5 m → 30 m → 2 h, thenexhausted; automatic disable after 20 consecutive failed deliveries; operator replay withconfirm_duplicate. See Retries and failures. - Operations —
GET /webhook/dashboard/summary,GET /webhook/catalogue/status, health componentwebhook-out, metricwebhook_out_deliveries_total{status}, development/certification sink. See Operations. - API reference — every webhook endpoint documented from a real OpenAPI spec: Webhook, Webhook Event, Webhook Dispatch, Event.
- Webhooks created before this release are disabled with
disabled_reason = "legacy-secret-rotation-required". Rotate the secret, deploy it on the subscriber, then activate. - Callback URLs must be HTTPS on a public host. Existing rows that no longer pass validation are rejected on the next update.
- Publishing is behind
ENABLE_API_EVENT_QUEUE, enabled per environment by the platform team after the catalogue sync. - Redirects (
3xx) are never followed and count as terminal failures.
0236 splits the webhook-in / webhook-out
queue lanes; 0237 adds webhook.is_global, description,
consecutive_failures, disabled_at, disabled_reason, makes
webhook.account nullable, renames webhook_dispatch.retry to attempts
and adds event.account_scope, method, route. See the
Webhook schema.