BETA
Skip to content

Webhook delivery ​

Every Krafter webhook delivery — across Mail, Forms, Flags, Push, Signal, and Surveys — follows the same envelope, signing, retry, and auto-disable contract. Per-service pages link to this page rather than restating the contract in full.

Headers ​

Every delivery POST sets:

HeaderValue
content-typeapplication/json
x-krafter-eventevent name (e.g. email.delivered, submission.created, flag.toggled, notification.sent)
x-krafter-webhook-idUUID v4, regenerated on every attempt so retries are individually identifiable
x-krafter-signaturet=<unix>,v1=<hex> — HMAC-SHA256 of <t>.<body>; see Verifying signatures. Omitted only when the webhook has no secret (a nil or empty string)

Headers are applied centrally by Krafter.Workers.WebhookDeliveryHelper.build_headers/3. The x-krafter-test header is added when a delivery is triggered from the dashboard's "Send test event" button.

Verifying signatures ​

Every signed delivery carries x-krafter-signature: t=<unix>,v1=<hex>, where the HMAC-SHA256 covers <t>.<raw body> — the timestamp is inside the signed payload, which is what lets a receiver reject a replayed request.

The verification recipe — splitting the header, checking that t is fresh, and the constant-time compare — is written once, with Node.js, Elixir and Python examples, on Verifying signatures. It is the same on every Krafter delivery, so there is nothing service-specific to add here.

Retry policy ​

Failed deliveries — any non-2xx HTTP response, network error, or timeout — are re-enqueued by Oban.

WorkerServicemax_attemptsBackoff
WebhookDeliveryMail5Oban default exponential ((2 ^ attempt) + 15 seconds + jitter)
FormWebhookDeliveryForms5Linear attempt * 15 seconds → 15s, 30s, 45s, 60s, 75s
FlagWebhookWorkerFlags5Oban default exponential
PushWebhookWorkerPush5Linear attempt * 15 seconds
SignalWebhookDeliverySignal5Oban default exponential
SurveyWebhookDeliverySurveys5Linear attempt * 15 seconds

A delivery that exhausts max_attempts is logged and its failure counter is incremented (see auto-disable below). The Oban job is then discarded — it is not retried after Oban gives up.

Logs alert webhook is different

The Logs alerting webhook (Krafter.Workers.LogAlertWebhook) is not part of this contract. It uses max_attempts: 3 and does not participate in the auto-disable mechanism. Treat it as a fire-and-forget alert delivery, not a webhook in the platform sense.

It is signed, despite what this page said until now: LogAlertWebhook goes through the same build_headers/4, so an alert with a notify_webhook_secret carries both signature headers like any other delivery.

Auto-disable after consecutive failures ​

After @max_consecutive_failures = 10 consecutive failures, the shared helper flips the webhook's "is active" flag to false. The field name varies per service:

ServiceField set to false
Mail, Flags, Push, Signalenabled
Forms, Surveysactive

A successful delivery (any 2xx response) resets failure_count to 0, so the counter only triggers auto-disable on a consecutive failure streak.

Owner notification ​

When auto-disable fires, services that opt in send an email to the team owner so the disable doesn't go unnoticed. Today this is wired for Forms and Push webhooks. Other services (Mail, Flags, Signal, Surveys, Webhooks Destinations, Forwarding) auto-disable silently — monitor failure_count via the dashboard or list endpoint until they opt in.

The notification email includes the webhook URL, the failure count, and a link to the relevant dashboard.

After auto-disable:

  1. Fix the receiving endpoint.
  2. Re-enable / reactivate the webhook in the dashboard (this also resets the failure counter).
  3. Use the per-service "test delivery" endpoint to confirm the endpoint is reachable before re-enabling, if your dashboard exposes one.

Idempotency ​

Receivers should treat x-krafter-webhook-id as the deduplication key for a single delivery attempt. The same logical event delivered after a retry will carry a new id — use the event payload's domain id (e.g. email_id, submission.id, notification_id) to deduplicate across retries of the same event.

Built by Krafter Studio