StatusCallback
Overview
StatusCallback is a webhook. Exotel sends an HTTP POST to your URL when an outbound AgentStream call changes state. It is the only way to learn the outcome of calls that were never answered (busy, no-answer, failed), because no flow, Passthru or WebSocket runs for them.
Which API supports what
API | Default callback (call end only) | Event callbacks (StatusCallbackEvents) | Stream details |
|---|---|---|---|
Connect Voice AI API (StreamUrl + StreamType=bidirectional) | Yes | Yes | DisconnectedBy in the terminal callback, if turned on for your account |
Connect Voice AI with Flow API (Url = your flow) | Yes | No. The request is rejected with HTTP 400. | Passthru applet after the Voicebot applet |
Request parameters
Parameter | Description |
|---|---|
StatusCallback | Your URL that receives callbacks |
StatusCallbackEvents[] | Events to receive: ringing, answered, terminal. Repeat the parameter for each event. Requires StatusCallback. Not allowed with a flow Url. |
StatusCallbackContentType | multipart/form-data (default) or application/json. Requires StatusCallback. |
CustomField | Your own value. Returned in event StatusCallbacks (Connect Voice AI) and in Passthru (Connect with Flow). It is not sent to the bot as start.custom_parameters. For Connect Voice AI, put bot keys on the StreamUrl query string; for a flow, put them on the Voicebot applet WebSocket URL. |
These requests are rejected with HTTP 400:
- StatusCallbackEvents without StatusCallback
- StatusCallbackEvents that isn't an array, or contains no valid event
- StatusCallbackEvents with a flow Url
Unknown event names are ignored when at least one valid event is present. An invalid StatusCallbackContentType, or one sent without StatusCallback, is also rejected.
Default StatusCallback (no StatusCallbackEvents)
Sent once, when the call ends. It includes:
- CallSid
- Status: completed, failed, busy, no-answer or canceled
- RecordingUrl, if recorded
- DateUpdated
It does not include CustomField.
Event callbacks
Each event callback includes:
Parameter | Description |
|---|---|
CallSid | Call ID |
EventType | ringing, answered or terminal |
Status | Call status at this event |
DateCreated, DateUpdated | When the call was created and last updated |
StartTime | Same value as DateCreated |
EndTime | Call end time. Terminal event only; empty on ringing and answered. |
EventTime | When this event happened |
From, To | Call parties. To is empty for Connect Voice AI calls. |
PhoneNumberSid | ExoPhone used |
Direction | Call direction |
RecordingUrl | Terminal event only, if recorded |
CustomField | The value you sent, if any |
ConversationDuration | Seconds connected. Terminal event only; empty on ringing and answered. |
Legs | Per leg: OnCallDuration and Status. Status is in-progress on ringing and answered. |
Sent only when turned on for your account, on the terminal event:
- DisconnectedBy
- Duration
- DetailedStatus, DetailedStatusSubBucket
- per-leg RingingDuration
AMD accounts also get per-leg AnsweredBy and AmdCause on the terminal event.
Times are in your account time zone. Numbers are in E.164 format if E.164 is turned on for your account.
When each event is sent
- terminal is sent once per call.
- answered and ringing are not sent after terminal has been sent.
- ringing is not sent after answered.
Delivery and retries
- Exotel sends an HTTP POST and waits 6 seconds for a response.
- 2xx and 3xx responses count as delivered. Exotel follows at most 1 redirect.
- A 4xx other than 429 is not retried.
- A 429, a 5xx, a timeout or a connection error is retried.
- With multipart (the default), nested values are flattened (for example Legs[0][Status]) and empty values are sent as empty strings. With JSON, Legs is an array.
Recommendations:
- Return 2xx quickly.
- Make your handler idempotent on CallSid + EventType.
- Reconcile calls with no terminal callback using the Call Details API.
What you receive for each outcome
Outcome | Connect Voice AI API with StatusCallbackEvents[]=terminal | Connect Voice AI with Flow API |
|---|---|---|
Answered, bot conversation | terminal, Status=completed, with CustomField, ConversationDuration, RecordingUrl | Default callback, no CustomField. Passthru after the bot has stream details and CustomField. |
Busy | terminal, Status=busy, with CustomField | Default callback, Status=busy |
No answer | terminal, Status=no-answer, with CustomField | Default callback, Status=no-answer |
Failed | terminal, Status=failed, with CustomField | Default callback, Status=failed |