Skip to main content
Each event triggers one delivery row per subscribed endpoint. The delivery row tracks attempts, the most recent HTTP status, and the next scheduled retry. You can inspect deliveries from the dashboard or via the API.

Statuses

StatusMeaning
pendingQueued. The next scheduled attempt is in next_attempt_at.
deliveringAn attempt is in progress.
succeededAn attempt returned 2xx. Terminal.
failedAll attempts exhausted without a 2xx. Terminal until you replay.

Retry schedule

A failed delivery is retried up to seven times after the initial attempt, for eight total attempts. The Workflow-backed retry schedule is:
AttemptDelay before this attempt
10 (immediate)
25 seconds
35 minutes
430 minutes
52 hours
65 hours
710 hours
810 hours
A delivery that finishes attempt 8 with a non-2xx response is marked failed and stops retrying. The automatic retry window is about 32 hours and 35 minutes after the first attempt. A delivery is treated as failed if:
  • The HTTP request fails to connect or times out.
  • The endpoint returns a non-2xx status.
  • The endpoint returns no response within the request timeout.
Slow endpoints are the most common cause of unintended retries. Acknowledge with 2xx first and process asynchronously if your handler is heavy.

Inspecting deliveries

curl "https://api.kayle.id/v1/webhooks/deliveries?status=failed&limit=50" \
  -H "Authorization: Bearer kk_..."
Filter by status, endpoint_id, or event_id. The response is the standard cursor-paginated list:
{
  "data": [
    {
      "id": "whd_...",
      "event_id": "evt_...",
      "webhook_endpoint_id": "whe_...",
      "webhook_encryption_key_id": null,
      "status": "failed",
      "attempt_count": 3,
      "next_attempt_at": null,
      "payload_expires_at": "2026-05-08T11:03:30Z",
      "payload_scrubbed_at": null,
      "payload_retention_reason": "terminal_failure_retention",
      "last_status_code": 502,
      "last_attempt_at": "2026-05-05T11:03:30Z",
      "created_at": "2026-05-05T11:00:00Z",
      "updated_at": "2026-05-05T11:03:30Z"
    }
  ],
  "pagination": { "limit": 50, "has_more": false, "next_cursor": null },
  "error": null
}

Payload retention

Webhook payloads are JWE-encrypted, but they can still contain personal data once your endpoint decrypts them. Kayle keeps the encrypted body only while it is needed for automatic delivery and manual recovery.
  • succeeded deliveries have payload_scrubbed_at set and the encrypted payload removed immediately after the first 2xx response.
  • failed deliveries keep the encrypted payload for the endpoint’s undelivered payload retention window after the final automatic attempt.
  • Expired, missing-key, and JWE-creation failures cannot be retried because there is no retained encrypted payload to send.
The endpoint setting undelivered_payload_retention_hours controls the manual retry/replay window after final delivery failure. Supported values are 0, 24, 72, and 168; new endpoints default to 72.

Manual retry

Replay a single failed delivery:
curl -X POST https://api.kayle.id/v1/webhooks/deliveries/whd_.../retry \
  -H "Authorization: Bearer kk_..."
The delivery moves back to pending and runs through the automatic retry schedule again. The signature is regenerated, so don’t expect the captured headers from the previous attempts to match. Manual retry is only available while the delivery still has a retained encrypted payload. Once payload_expires_at passes, or once payload_scrubbed_at is set, the API returns 409 WEBHOOK_PAYLOAD_EXPIRED.

Replaying an event

If multiple deliveries (one per subscribed endpoint) failed because of a downstream outage, replay the whole event rather than each delivery:
curl -X POST https://api.kayle.id/v1/webhooks/events/evt_.../replay \
  -H "Authorization: Bearer kk_..."
Replay requeues the existing deliveries for that event while their encrypted payloads are still retained. It does not create deliveries for endpoints that were added after the event fired. If every delivery for the event has been scrubbed or expired, the API returns 409 WEBHOOK_PAYLOAD_EXPIRED.

Listing events

curl "https://api.kayle.id/v1/webhooks/events?type=verification.session.succeeded" \
  -H "Authorization: Bearer kk_..."
/v1/events is an alias for the same resource, kept for older integrations. Use the /v1/webhooks/events path in new code.