Blog · October 21, 2026 · 9 min read

Retell AI Webhooks: How Agencies Should Receive, Verify and Route Call Events Across Clients (2026)

By Nabeel Hassan — builder of VoiceDash

Retell AI Webhooks: How Agencies Should Receive, Verify and Route Call Events Across Clients (2026)

TL;DR: Retell AI webhooks are how your systems find out what happened on a call without polling the API. Retell sends call events (the call started, the call ended, and the call was analyzed) as POST requests to a URL you set at the account level or per agent. For an agency, the hard part is not receiving them, it is everything around that: knowing which client a call belongs to, verifying the request really came from Retell, answering fast so Retell does not retry, handling duplicates without double booking or double texting, and logging enough to debug a client's "why did my CRM not update?" I build production voice agents on Retell for US clients, wired into n8n, GoHighLevel and Twilio, and we run VoiceDash, the white-label client portal agencies hand their clients. This is the webhook setup I use for every client.

If you have only ever pasted a webhook URL into the Retell dashboard and moved on, that works for one client. At ten clients it turns into the place where silent failures hide.

What Retell sends and when

Retell posts a JSON body to your webhook URL whenever a call reaches certain stages. The three events every agency cares about are:

  • call_started: the call connected. Useful for live dashboards and for "a call is in progress" alerts, rarely needed for automation.
  • call_ended: the call is over. You get the call object with the transcript, timing, disconnection reason and the phone numbers involved.
  • call_analyzed: post-call analysis is finished. This is the one you usually want, because it carries the call_analysis object with the summary, sentiment, success flag and any custom fields you defined.

Retell adds event types and fields over time, so check the current Retell docs for the full list and build against a payload you actually captured, not one you remember. If you have not set up the analysis fields yet, start with Retell AI post-call analysis, because the quality of your call_analyzed events depends entirely on those field definitions.

Why I automate off call_analyzed, not call_ended

call_ended arrives first, but it does not carry the structured data. If your workflow creates a CRM contact on call_ended and then updates it on call_analyzed, you have two writes, two chances to fail and a race between them. For most client automations I ignore call_ended entirely and run everything from call_analyzed: create or update the contact, tag the outcome, send the follow-up text, notify the owner.

The exception is anything that must happen the moment the caller hangs up and does not need analysis, like freeing a held calendar slot. That can run on call_ended.

Account-level vs agent-level webhook URLs

Retell lets you set a webhook URL for the whole account and override it on an individual agent. When an agent has its own URL, events for that agent go there instead of to the account URL.

For agencies, that gives you two workable patterns.

Pattern 1: one URL per client agent

Each client's agent points at its own endpoint, for example a separate n8n workflow per client. The client is implied by the URL, so routing is trivial and a broken workflow only affects one client.

The cost is drift. Ten workflows that started as copies slowly become ten different workflows, and a fix you make in one never reaches the other nine.

Pattern 2: one shared endpoint that routes

Every agent points at one endpoint. That endpoint reads the agent_id from the payload, looks up which client it belongs to, and hands the event to that client's logic or config.

This is what I use once an agency passes a handful of clients. One place to verify signatures, one place to log, one place to fix bugs. Per-client differences live in a config table (which CRM, which calendar, which phone number to alert) rather than in copied workflows.

Whichever you choose, keep a written map of agent ID to client. You will need it for routing, for billing and for the day a client asks you to hand everything over. How you split clients across Retell accounts changes this picture, so if that is not settled yet, read Retell AI account structure for agencies.

Verify every request

Your webhook URL is a public endpoint that writes to client CRMs and sends texts to their customers. Anyone who finds it can post a fake call to it.

Retell signs its webhook requests with a signature header derived from your API key, and its SDKs include a verify helper. Verify the signature against the raw request body before anything else runs, and reject anything that fails. Two details trip people up:

  • Verify the raw body, not the parsed JSON. Many frameworks parse the body before your code sees it. Re-serializing it can change whitespace or key order and break the signature check.
  • Use the right key. If your clients sit on different Retell accounts, each account signs with its own key. A shared endpoint needs to know which key to check against.

If you are on a no-code tool where signature verification is awkward, at minimum keep the URL unguessable and treat it like a password. But do the real verification as soon as you can.

Answer fast, do the work later

Retell waits a limited time for a successful response. If your endpoint is slow or errors, Retell can retry the delivery, which means the same event may arrive more than once. Check the Retell docs for the current timeout and retry behavior.

The safe pattern:

  1. Receive the request.
  2. Verify the signature.
  3. Store the raw event, or push it onto a queue.
  4. Return a 2xx immediately.
  5. Process the event in a separate step.

The mistake I see most is doing all the work inline: look up the contact in GoHighLevel, create a note, book something, send an SMS, and only then respond. One slow CRM call and Retell retries, and now the caller got two follow-up texts.

Make processing idempotent

Retries and duplicates are normal, so design for them. Every call has a unique call_id. Before you act on an event, check whether you already processed that call_id and event type. If yes, return success and do nothing.

Practical ways to do it:

  • A processed-events table keyed on call_id plus event type. Insert first, process second, and skip if the insert fails because the row exists.
  • Upserts instead of inserts in the CRM, matched on phone number or an external ID field, so a second run updates the same contact instead of creating a duplicate.
  • Guard the side effects that cannot be undone. A duplicate CRM note is annoying. A duplicate SMS to a customer or a double booked appointment is a client complaint.

Log enough to debug a client complaint

Sooner or later a client says "a lead called yesterday and nothing showed up in my CRM." You want to answer that in minutes, not by reading n8n executions one by one.

For every event, log: received time, event type, call_id, agent_id, the client it was routed to, whether the signature passed, and the processing result with any error message. Keep the raw payload for a reasonable window so you can replay an event after fixing a bug.

With that log, the answer to the complaint is a lookup: did Retell send the event, did it pass verification, did processing succeed, and if not, what failed. I fold a daily check of failed events into the routine in voice AI agent monitoring.

Test webhooks before a client goes live

Webhook bugs rarely show up in the first test call. They show up on the call where something is slightly different. Before launch I run at least these:

  • A normal completed call that should create a contact and trigger follow-up.
  • A call that hits voicemail, so you confirm the voicemail branch does not text a customer as if they had spoken to the agent.
  • A caller who hangs up in the first few seconds, which produces sparse analysis fields that can break workflows expecting values.
  • A transferred call, if the agent can transfer, so the outcome is tagged correctly.
  • A replayed event, sending the same payload twice to prove the idempotency guard works.
  • A forged request with a bad signature, to prove it gets rejected.

The wider test plan, including prompt and conversation testing, is in how to test a voice AI agent.

Webhooks vs mid-call functions

Agencies sometimes mix these up. Webhooks are after the fact: nobody is waiting, so you can take your time. Mid-call custom functions are when the agent calls your endpoint during the conversation to check a calendar or look up a customer, and the caller is waiting in silence for the answer.

They need different design. Webhooks favor reliability: queue, retry, log. Functions favor speed: one fast lookup, a small response. If you need the mid-call side, see Retell AI custom functions, and for the n8n wiring of both, how to connect Retell AI to n8n.

Where VoiceDash fits

You do not need to build webhook plumbing just to show clients their calls. VoiceDash connects to your Retell account and syncs call data automatically into an isolated, white-label portal for each client on your domain: analytics, call logs, recordings, full transcripts and usage. Your webhooks stay focused on the automations that are specific to each client, like CRM updates and follow-up texts, instead of also feeding a reporting dashboard you have to maintain.

VoiceDash also lets you bill clients through your own Stripe, with call minutes from Retell billed automatically. Setup takes under 10 minutes with no code: connect Retell, create the client, brand their portal, share the link. Plans are Starter, Growth and Ultimate, each with a 7-day free trial. Today VoiceDash works with Retell AI. VAPI and Bland support is coming soon.

The bottom line

Retell AI webhooks are simple to receive and easy to get wrong at agency scale. Run client automations off call_analyzed, pick either one URL per agent or one shared routing endpoint and keep an agent-to-client map, verify every signature against the raw body, respond with a 2xx before doing the work, make processing idempotent on call_id, and log every event so client complaints become a lookup. Test the odd calls, not just the happy path.

If you want each client in a branded portal showing their calls, recordings, transcripts and usage without building the reporting side yourself, start free on VoiceDash or book a demo and I will walk you through it.

FAQ

Which Retell AI webhook event should I automate on?

For most client automations, use call_analyzed. It arrives after post-call analysis finishes and carries the call_analysis object with the summary, sentiment, success flag and your custom fields. call_ended arrives earlier but without that structured data, so automating on both creates two writes and a race between them. Use call_ended only for actions that must happen immediately and do not need analysis.

Should each client agent have its own Retell webhook URL?

Either works. Retell lets you set a webhook URL at the account level and override it per agent. One URL per client agent makes routing trivial but leads to copied workflows that drift apart. One shared endpoint that routes on agent_id gives you a single place to verify, log and fix bugs, which is easier to maintain once an agency has more than a handful of clients.

How do I stop duplicate Retell webhook events from double texting customers?

Respond with a 2xx quickly and do the work in a separate step so Retell has less reason to retry, then make processing idempotent. Record each call_id and event type before acting and skip any you have already processed. Use upserts in the CRM instead of inserts, and guard irreversible side effects like SMS and bookings behind that check.

Give your clients a dashboard with your name on it

VoiceDash turns your Retell agents into branded client portals. Live in under 10 minutes, no code.

Keep reading