Skip to main content

WebhookDelivery

A WebhookDelivery is one attempt to POST a webhook payload to its target URL. The dispatcher creates one row per attempt; retries produce additional rows linked to the same system_event_id.

Purpose​

  • Provide an auditable history of every webhook attempt (status, payload, response, duration).
  • Allow replay of failed deliveries without re-emitting the underlying event.
  • Power the webhook deliveries UI in /manage.

Schema​

FieldTypeDescription
idUUIDInternal id.
webhook_idUUIDOwning webhook.
system_event_idUUIDThe event being delivered.
attemptint1-indexed retry counter.
statusenumpending, success, failed.
request_headersobjectOutbound headers (auth values redacted).
request_bodyobjectThe JSON payload sent.
response_statusint | nullHTTP status received.
response_headersobject | nullHeaders from the target (capped to 4 KiB).
response_bodystring | nullBody from the target (capped to 16 KiB).
errorstring | nullTransport error (timeout, DNS failure, TLS).
duration_msint | nullWall time of the HTTP call.
delivered_atdatetime | nullWhen the call finished.
next_retry_atdatetime | nullWhen the next retry is scheduled (if any).
created_at / updated_atdatetime

Status​

StatusMeaning
pendingQueued or in-flight; delivered_at not set.
success2xx received within the timeout.
failedNon-2xx, timeout or transport error. May retry — check next_retry_at.

Example​

{
"id": "9a8b7c6d-5e4f-3a2b-1c0d-9e8f7a6b5c4d",
"webhook_id": "f6a7b8c9-0d1e-4f2a-3b4c-5d6e7f8a9b0c",
"system_event_id": "1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"attempt": 2,
"status": "success",
"request_headers": {
"X-Kobana-Event": "email.db.created",
"X-Kobana-Timestamp": "1747490400",
"X-Kobana-Signature": "sha256=…",
"Authorization": "Bearer [REDACTED]"
},
"request_body": { "event": "email.db.created", "data": {} },
"response_status": 200,
"response_headers": { "content-type": "application/json" },
"response_body": "{\"ok\":true}",
"error": null,
"duration_ms": 312,
"delivered_at": "2026-05-17T12:05:14.218Z",
"next_retry_at": null,
"created_at": "2026-05-17T12:05:13.906Z",
"updated_at": "2026-05-17T12:05:14.218Z"
}

Reading​

MethodPath
GET/webhooks/{id}/deliveries?status=failed&attempt[gte]=2
GET/webhooks/{id}/deliveries/{deliveryId}

Filters: status, attempt[gte|lt], created_at[gte|lt].

Replay​

POST /webhooks/{id}/deliveries/{deliveryId}/replay enqueues an immediate redelivery — it ignores any pending backoff but is recorded as a new row (attempt is not incremented; a new WebhookDelivery is appended with attempt=1 and a metadata.replay_of reference).

Once any delivery of an event to a webhook has succeeded — a replay included — the retries still scheduled for that event are not sent, and the next_retry_at of its earlier failed rows is cleared. A successful replay therefore ends the backoff chain of the original delivery.

The replayed request also carries its own X-Idempotency-Key, a fresh random value rather than the event id every automatic attempt uses (see Headers). Automatic retries are still deduplicated against the original attempt on purpose; a replay is a deliberate request to act on the event again, so it must not be swallowed by the same guard.