# Guide to Retell AI Webhooks: Features and Best Practices

Retell AI webhooks notify your application about voice-agent call activity: a call starts, a call ends, a call's analysis completes. If you're building on Retell's voice agents, webhooks are how you react to calls in real time and capture transcripts and outcomes without polling.

This guide covers how Retell webhooks work, the events you'll handle, how to verify the `X-Retell-Signature`, the retry behavior, and the best practices for production.

## What are Retell AI webhooks?

Retell webhooks are HTTP POSTs delivered to a URL you configure (account-level or per-agent), each carrying a call event. Retell signs every delivery with an `X-Retell-Signature` header. The unusual detail: the signing secret is your Retell API key (the one with a webhook badge), not a separate webhook secret.

## Retell AI webhook features

| Feature | Details |
| --- | --- |
| Configuration | Account-level (dashboard Webhooks tab) or per-agent (`webhook_url`, overrides account) |
| Signature header | `X-Retell-Signature`, formatted `v={unix_ms_timestamp},d={hex_digest}` |
| Signature scheme | HMAC-SHA256 over `raw_body + timestamp`, keyed with your Retell API key |
| Replay window | ~5 minutes (enforced by the SDK helper) |
| Acknowledgement | 2xx within 10s; retried up to 3 times otherwise |
| IP allowlist | Optional: `100.20.5.228` |
| SDK | `retell-sdk` (npm, pip) with a `verify` helper |

## Common events

Retell events cover the call lifecycle:

| Event | Fires when |
| --- | --- |
| `call_started` | A call begins |
| `call_ended` | A call ends |
| `call_analyzed` | Post-call analysis completes |
| `transcript_updated` | The live transcript updates |

There are also `chat_*` and `transfer_*` event families. Subscribe to what you process, and consult Retell's webhook reference for the full list.

## Setting up Retell webhooks

Configure a webhook at the account level (dashboard Webhooks tab) or per agent (set `webhook_url` on the agent, which overrides the account-level URL). Optionally allowlist Retell's source IP (`100.20.5.228`). The signing secret is the Retell API key that carries a webhook badge.

## Securing Retell webhooks

Each delivery carries an `X-Retell-Signature` header formatted `v={timestamp},d={hex}`, where the digest is an HMAC-SHA256 over `raw_body + timestamp`, keyed with your Retell API key. Verify against the raw body. The official SDK does this (including the ~5-minute timestamp window and a constant-time comparison), so prefer it over hand-rolling:

```javascript
const Retell = require("retell-sdk");

const API_KEY = process.env.RETELL_API_KEY; // the key used as the signing secret

app.post("/webhook", express.raw({ type: "application/json" }), (req, res) => {
  const valid = Retell.verify(
    req.body.toString("utf8"), // raw body
    API_KEY,
    req.headers["x-retell-signature"]
  );
  if (!valid) return res.sendStatus(401);

  res.sendStatus(200); // acknowledge within 10s
  processQueue.add(JSON.parse(req.body)); // process asynchronously
});

```

The same in Python:

```python
import os
from retell import Retell

client = Retell(api_key=os.environ["RETELL_API_KEY"])

@app.post("/webhook")
def webhook():
    valid = client.verify(
        request.get_data(as_text=True),  # raw body
        api_key=os.environ["RETELL_API_KEY"],
        signature=request.headers["X-Retell-Signature"],
    )
    if not valid:
        return "", 401
    # process asynchronously
    return "", 200

```

The exact argument order of the verify helper can vary by SDK version, so follow the current SDK docs. If you verify manually, recompute `HMAC-SHA256(api_key, raw_body + timestamp)`, compare the `d=` hex in constant time, and reject timestamps outside ~5 minutes.

## Retell AI webhook limitations and pain points

### The signing secret is your API key

The Problem: Verification uses your Retell API key as the HMAC secret, not a dedicated webhook secret. That couples webhook verification to a broadly-scoped credential, and using the wrong key (or a non-webhook-badged one) fails verification.

Why It Happens: Retell keys webhook signing on the API key rather than issuing a separate secret.

Workarounds:

* Use the API key with the webhook badge, store it securely, and rotate carefully since it's also your API credential.

How Hookdeck Can Help: Hookdeck verifies the `X-Retell-Signature` at the edge, so the API key lives in one place rather than in every handler.

### The signed message is `raw_body + timestamp`

The Problem: The message appends the timestamp to the raw body (`raw_body + timestamp`), and the header packs both as `v=…,d=…`. Signing the body alone, or re-serializing it, fails.

Why It Happens: Retell binds the timestamp into the signature for replay protection.

Workarounds:

* Use the SDK helper, or recompute over `raw_body + timestamp` and enforce the ~5-minute window yourself.

How Hookdeck Can Help: Hookdeck verifies signature and freshness at the edge, so your app receives pre-verified, fresh events.

### Retries and duplicates

The Problem: If your endpoint doesn't 2xx within 10 seconds, Retell retries up to 3 times, so the same event can arrive more than once.

Why It Happens: At-least-once delivery favors eventual delivery of call events.

Workarounds:

* Dedupe on event type + `call_id`, and make side effects idempotent.

How Hookdeck Can Help: Hookdeck deduplicates deliveries at the edge, so retries don't double-process calls. See our [guide to webhook idempotency](/webhooks/guides/implement-webhook-idempotency).

### Account vs agent-level configuration

The Problem: An agent-level `webhook_url` overrides the account-level one, so events for that agent won't hit your account endpoint. It's easy to miss events if configuration is split.

Why It Happens: Retell lets agents override the account webhook.

Workarounds:

* Track which agents have their own `webhook_url` and route consistently.

How Hookdeck Can Help: Hookdeck can receive both account- and agent-level webhooks at one ingestion point and route them uniformly.

## Best practices

### Verify with the SDK helper

Use `retell-sdk`'s `verify` with your API key and the raw body; it enforces the ~5-minute window and constant-time comparison. Follow the current SDK docs for argument order.

### Acknowledge within 10 seconds, process asynchronously

Return 2xx immediately and defer work to a queue. See [why to process webhooks asynchronously](/webhooks/guides/why-implement-asynchronous-processing-webhooks).

### Dedupe on event + call_id

Use event type + `call_id` as an idempotency key so retries don't double-process.

### Keep account and agent webhooks consistent

Track agent-level `webhook_url` overrides so you don't miss events.

## Conclusion

Retell AI webhooks deliver call events verified with an `X-Retell-Signature` HMAC over `raw_body + timestamp`, keyed unusually with your Retell API key and packed as `v=…,d=…`. Use the SDK verify helper, enforce the ~5-minute window, dedupe on `call_id`, and keep account/agent configuration consistent.

[Hookdeck](https://hookdeck.com) verifies the signature, deduplicates, and durably queues every event at the edge, so your app only ever processes verified, unique call events.

[Get started with Hookdeck](https://dashboard.hookdeck.com/signup) for free and handle Retell AI webhooks reliably in minutes.