> ## Documentation Index
> Fetch the complete documentation index at: https://developers.cloudtalk.io/llms.txt
> Use this file to discover all available pages before exploring further.

# CloudTalk Webhook Delivery, Retries, and Best Practices

> How CloudTalk delivers webhooks: success criteria, the retry schedule, automatic endpoint disabling, duplicate handling, ordering, and best practices for reliable handlers.

This page covers the mechanics of getting events to your endpoint reliably: what counts as a successful delivery, what happens when your endpoint fails, and how to build a handler that never loses an event.

This applies to the account-level webhooks configured under **Account → Webhooks**. Other CloudTalk features that call an endpoint you own (the Call Flow Designer **Webhook** step, an AI Voice Agent's **Send via Webhook** option, Workflow Automation actions) use their own delivery path and are not covered here. See the [overview](/guides/webhooks/overview).

## What counts as a successful delivery

A delivery succeeds when your endpoint responds with any **`2xx`** status code within **15 seconds**. Anything else is a failed attempt: a `4xx` or `5xx`, a timeout, a connection error, and `3xx` redirects, which are not followed.

Respond **before** you process. Do the minimum to durably accept the event (verify the signature, push it onto a queue), return `200`, and do the real work asynchronously. Handlers that do heavy work inline risk hitting the timeout and receiving duplicate deliveries.

## Retries

A failed delivery is retried automatically: **up to 50 times over roughly 11.5 hours**, with the delays growing quickly at first and then settling into a steady cadence:

| Retry   | Delay after the previous attempt |
| ------- | -------------------------------- |
| 1       | 5 seconds                        |
| 2       | 30 seconds                       |
| 3       | 2 minutes                        |
| 4       | 5 minutes                        |
| 5       | 10 minutes                       |
| 6 to 50 | every 15 minutes                 |

Every retry of an event carries the same `event_id` in the body and the same `svix-id` header, so a retry that arrives after a slow-but-successful first attempt is easy to recognise and skip.

If all retries fail, that delivery is marked **failed** in the endpoint's delivery log. Nothing is lost: you can [resend it or recover a whole time range](#delivery-log-test-events-and-replay) once your endpoint is healthy again.

## Automatic disabling and re-enabling

An endpoint that fails **continuously for about 5 days** is disabled automatically, and no further events are sent to it. This protects both sides: a dead endpoint doesn't accumulate an endless backlog.

Disabling isn't the end, though. For the next **12 hours**, CloudTalk keeps probing the endpoint with a test event, backing off from a few minutes to a couple of hours between probes. The moment a probe gets a `2xx`, the endpoint is **re-enabled automatically** and the deliveries that failed while it was down are **replayed**. If it hasn't recovered within those 12 hours, it stays disabled until you re-enable it yourself.

Account admins receive an **email** when an endpoint is automatically disabled and again when it is re-enabled.

To re-enable an endpoint manually: fix the endpoint, open it under **Account → Webhooks**, switch it back on, then use **Recover** to replay the deliveries that failed while it was disabled.

<Tip>
  The simplest way to never get disabled: return `200` as soon as the event is safely queued, and keep the processing that can fail out of the request path.
</Tip>

## Delivery log, test events, and replay

Every endpoint under **Account → Webhooks** has its own operations panel:

* **Delivery log**: every delivery attempt with its event type, status, response code, and time.
* **Resend**: redeliver any single event from the log, for example after fixing a bug in your handler.
* **Recover**: replay every failed delivery since a time you choose, in one action. Use it after an outage on your side.
* **Send test event**: pick an event type and CloudTalk sends a sample payload to your endpoint, so you can verify connectivity and signature handling before real traffic arrives.

## Data retention

Event payloads are stored for **30 days**. Within that window you can open a delivery in the log, inspect the payload CloudTalk sent, resend it, or recover a range of failed deliveries. After 30 days the payload is deleted and can no longer be viewed or replayed.

Plan around it: your own system should be the system of record for anything you need long term. Thirty days is comfortably longer than the retry and auto-disable windows above, so a normal outage never risks losing replayable events.

## Duplicates and idempotency

Delivery is **at-least-once**: in rare situations (a retry racing a slow response, infrastructure failover) the same event is delivered more than once. Duplicates always carry the same `event_id`.

Make your handler idempotent: **claim** the `event_id` before you do the work, not after. Insert it into a store with a uniqueness constraint (a unique index, `SET NX` in Redis, or an equivalent atomic operation) and only proceed when the insert succeeds. Checking first and recording afterwards leaves a window in which a retry and the original delivery, or two workers, both pass the check and both repeat the side effect.

Keep those records for **at least as long as you might replay**. Resend and Recover reach back over the full 30-day retention window, so a store that expires after an hour will happily reprocess a three-week-old delivery and repeat whatever side effect it had. Thirty days of `event_id`s is the safe floor; a durable idempotency key on the work itself (the order you create, the ticket you open) is better still.

## Ordering

Events are **not guaranteed to arrive in order**: a `call.answered` can occasionally arrive before its `call.started`, and events for different resources interleave freely.

* Sort by `occurred_at` when sequence matters.
* Don't treat a "later" lifecycle event as an error when the earlier one hasn't arrived yet.
* When you need the current state of a resource rather than its history, fetch it from the [REST API](/api-reference/overview) instead of reconstructing it from events.

## Best practices

<AccordionGroup>
  <Accordion title="Verify every delivery">
    Reject requests that fail [signature verification](/guides/webhooks/verify-signatures) with a `401`. Never process an unverified payload.
  </Accordion>

  <Accordion title="Acknowledge fast, process async">
    Return `2xx` as soon as the event is durably accepted: queue it and process in the background. This keeps you inside the timeout and lets you absorb bursts.
  </Accordion>

  <Accordion title="De-duplicate on event_id">
    Track processed `event_id`s and skip repeats. De-duplication plus idempotent processing makes at-least-once delivery indistinguishable from exactly-once.
  </Accordion>

  <Accordion title="Tolerate the unknown">
    New event types and new optional fields appear over time without a version bump. Ignore event types you don't handle and fields you don't recognize instead of failing.
  </Accordion>

  <Accordion title="Subscribe only to what you use">
    Fewer subscribed events means less traffic to secure, queue, and pay attention to, especially high-frequency events like `user.status_changed`.
  </Accordion>

  <Accordion title="Exempt the route from CSRF protection">
    Web frameworks often apply CSRF checks to all POST routes. Your webhook route authenticates via signatures, not sessions: exempt it, or every delivery will be rejected.
  </Accordion>

  <Accordion title="Use HTTPS with a valid certificate">
    Endpoints must be HTTPS. Expired or self-signed certificates cause failed deliveries that count toward automatic disabling.
  </Accordion>
</AccordionGroup>

## Payload conventions worth knowing

* **Empty means absent.** Optional fields with no value are omitted, never `null`.
* **Timestamps** are RFC 3339 UTC (`2026-08-17T09:34:21.512Z`); sub-second precision may vary.
* **Versioning is per event type.** `version` bumps only on breaking changes; both versions may be delivered during a migration window while you switch over.
* **Recordings and transcripts arrive by reference**, never inline: fetch them through the API with your own credentials.


## Related topics

- [CloudTalk Webhooks](/guides/webhooks/overview.md)
- [How to Verify CloudTalk Webhook Signatures](/guides/webhooks/verify-signatures.md)
- [Changelog](/changelog.md)
