Every call keeps an event timeline, readable at GET/v1/calls/{CallSid}/events, and ends with three fields that explain the result: hangup_cause (why it ended), sip_response_code (the raw SIP code) and call_outcome (who or what it ended at). Use call_outcome to decide whether to retry: network usually means try again later, invalid_number means never.
Event timeline#
{
"data": [
{ "id": "...", "event_type": "call.initiated", "data": {}, "created_at": "2026-10-28T09:30:00Z" },
{ "id": "...", "event_type": "call.ringing", "data": {}, "created_at": "2026-10-28T09:30:03Z" },
{ "id": "...", "event_type": "call.answered", "data": {}, "created_at": "2026-10-28T09:30:12Z" },
{ "id": "...", "event_type": "call.stream.connected", "data": {}, "created_at": "2026-10-28T09:30:12Z" },
{ "id": "...", "event_type": "call.completed", "data": {}, "created_at": "2026-10-28T09:31:02Z" }
]
}
| Event | Meaning |
|---|---|
call.initiated |
The call was placed |
call.ringing |
The destination is ringing |
call.answered |
The callee answered |
call.completed |
The call ended |
call.failed |
The call could not be connected |
call.dtmf |
A keypad digit was pressed |
call.recording.started / call.recording.stopped |
Recording started or stopped |
call.stream.connected / call.stream.failed |
Edesy reached, or could not reach, your media-stream WebSocket. failed carries the reason |
call.stream.started / call.stream.stopped |
The media stream started or stopped |
call.relay.connected / call.relay.failed |
Edesy reached, or could not reach, your Conversation Relay bot |
call.relay.handoff |
Your relay bot ended the session, with its handoffData |
call.relay.usage |
Speech usage for a relay call: TTS characters, STT seconds, turns |
When a bot "never got the call", the stream and relay events tell you why (DNS, TLS, refused upgrade) without opening a ticket.
Statuses#
status |
Terminal | Meaning |
|---|---|---|
queued |
no | Accepted, not yet dialled |
initiated |
no | Dialling |
ringing |
no | Ringing |
in-progress |
no | Answered and running |
completed |
yes | Answered, then ended |
busy |
yes | Busy or rejected |
no-answer |
yes | Rang out, phone off, or out of coverage |
failed |
yes | Could not be connected |
canceled |
yes | Ended before answer, for example by the caller |
Hangup reasons and SIP codes#
hangup_cause on the call (and HangupReason in status callbacks) is carrier-neutral:
| SIP code | status |
Hangup reason |
|---|---|---|
| 200 (answered) | completed |
completed |
| 486, 600 | busy |
busy |
| 603 | busy |
rejected |
| 487, or 408 after ringing | no-answer |
no_answer |
| 487 when the caller hung up first | canceled |
caller_canceled |
| 480 | no-answer |
unavailable |
| 404, 410, 484, 485, 604 | failed |
invalid_number |
| 401, 403, 407 | failed |
call_blocked |
| 408 before ringing | failed |
network_timeout |
| 402, 429, 5xx | failed |
carrier_error |
| none or other | failed |
call_failed |
Call outcomes#
call_outcome groups the reason by who or what the call ended at. failed alone cannot tell these apart.
call_outcome |
Hangup reasons | Meaning | What to do |
|---|---|---|---|
answered |
completed |
Answered | Nothing |
callee |
busy, rejected, no_answer, caller_canceled |
The person did not take the call | Retry later, within your calling hours |
unreachable |
unavailable |
Phone switched off or out of coverage | Retry later |
invalid_number |
invalid_number |
The number does not exist | Do not retry; fix the number |
network |
network_timeout, carrier_error, call_blocked |
The telecom network did not connect the call; usually temporary | Retry later |
account |
insufficient_balance, missed_call |
Refused by your wallet balance or number settings | Top up, or check the number's routing |
platform |
call_failed |
Edesy could not place the call | Contact support with the call_sid |
Unanswered calls of every kind are free. See Pricing and Limits.