Skip to main content
Webhook delivery is retried for about five hours before it is abandoned. This page covers how to build a reliable receiver that handles failures gracefully.

Delivery guarantees

  • Each delivery attempt has a 10-second timeout.
  • If your server does not respond within 10 seconds or returns a non-2xx status, the delivery is retried with exponential backoff: 30 seconds, then 1, 2, 4, 8, 16 and 32 minutes, then hourly.
  • There are 12 attempts in total, so the last one lands roughly five hours after the job completed.
  • After the final attempt, delivery is abandoned. The result is always available via the job status endpoint, regardless of webhook delivery outcome.
An outage on your side therefore delays results rather than losing them. If your receiver is down for an hour and comes back, the queued deliveries arrive on their next scheduled attempt. You do not need to ask us to resend them. Two cases are not retried, because no amount of waiting would fix them. Delivery stops immediately if your webhook URL is malformed, does not use HTTPS, or resolves to a private or internal IP address. Re-register a valid URL with POST /webhook/register.
Do not rely on webhooks as your only mechanism for receiving results. Always fall back to polling the job status endpoint for critical workloads.

Handling delivery failures

The retry window covers an outage of a few hours, not an outage of a few days. Build a recovery mechanism into your integration for anything longer:
  1. Persist job IDs on submission. Store the job_id returned from any of the modality endpoints (POST /moderate/text, POST /moderate/image, POST /moderate/video).
  2. Poll on startup or recover from failures. On application restart or after a webhook outage, query GET /job/{id} for any job IDs in a non-terminal state (queued or processing).
  3. Set a polling timeout. If a job has not completed after a reasonable period (for example, 5 minutes for prompt or image moderation, 15 minutes for video moderation), treat it as failed and investigate.

Idempotency

Your webhook handler may occasionally receive a duplicate delivery. For example, if your server processed a request but responded after the 10-second timeout, the delivery is retried. Every attempt for the same result carries the same id in three places: the delivery_id field in the body, the X-Omnifence-Delivery-Id header, and the webhook-id signature header. Deduplicate on that value. The body is byte-identical to the original, but the signature is not: each attempt is signed as it is sent, so webhook-timestamp and webhook-signature are fresh and a retry verifies against the default five-minute tolerance. See Verifying a webhook.
There is exactly one webhook per job, so delivery_id and job_id map one to one today. Key your deduplication on delivery_id / webhook-id anyway. That is the value guaranteed to stay unique per delivery.

Responding quickly

Webhook payloads are small, but any processing you do synchronously (database writes, downstream API calls) can push your response time past the 10-second timeout. Acknowledge receipt immediately and process asynchronously.

Security considerations

Every webhook is signed with your account’s signing secret, following the Standard Webhooks specification. Verify the signature before you act on a payload — see Verifying a webhook for the headers, the signed string and a worked example. Without that check, anyone who learns your webhook URL can POST a forged completed result. That matters most for the fields you act on: a spoofed is_prohibited: false waves prohibited content through your pipeline. Alongside the signature:
  • Use HTTPS for your webhook URL.
  • Validate the job_id against your records before trusting the payload.
  • Reject a payload whose signature does not verify; do not fall back to trusting it.

Hybrid approach

For maximum reliability, use webhooks for speed and polling as a fallback:
  1. Register a webhook to receive results immediately when a job completes.
  2. Store all submitted job_id values with a pending state in your database.
  3. Run a background job every few minutes to poll the job status endpoint for any IDs that remain pending longer than expected.
Because retries now span hours, a result can arrive by webhook after your polling job has already recovered it. This is another reason to deduplicate on delivery_id.