Voice events
Bird emits a webhook event at three points in a call, so your systems learn what happened without polling the dashboard. This page is the event vocabulary. How events reach your endpoint (subscriptions, signatures, retries, replay) is covered in the Webhooks guide.
A call's path through the event types:
- voice_call.initiated: Bird accepted the call setup request (a SIP INVITE) and started routing the call
- voice_call.answered: the number you called picked up. Only answered calls get this event
- voice_call.ended: the call is over, and the event carries the outcome
Read initiated as "a call exists" and ended as the verdict. A call Bird refuses after accepting the INVITE still ends with voice_call.ended, carrying status: "failed" and sip_response_code: 503, so every call you hear about opening also closes.
The event type is an open enum: Bird may add types over time, so match the ones you handle and ignore the rest rather than treating an unfamiliar type as an error.
The event envelope
Voice events arrive in the same nested envelope as every other Bird event, described in the Webhooks guide: type, timestamp, and a type-specific data object. The event's identity is in the webhook-id HTTP header rather than the body.
| Field | Description |
|---|---|
| type | One of the three types on this page, for example voice_call.ended |
| timestamp | When the event occurred (RFC 3339). Sort by this, never by arrival order |
| data | Event-specific payload, always carrying the same call identity fields |
Every voice event's data carries the same identity fields, so you can correlate against your own records without a second lookup. Both numbers are in E.164 format, a leading + followed by the country code and national number.
| Field | Description |
|---|---|
| call_id | The call record's id (vcl_…), the same one shown in the Call log |
| session_id | Shared by every leg of a transferred or multi-party call (vcs_…). Null when no session correlation applies |
| workspace_id | The workspace the call belongs to |
| direction | outbound for calls your equipment placed |
| from | The calling number |
| to | The number that was called |
voice_call.initiated
Bird received the INVITE and began routing.
Przykład kodu
{
"type": "voice_call.initiated",
"timestamp": "2026-06-10T14:30:00Z",
"data": {
"call_id": "vcl_01krdgeqcxet5s7t44vh8rt9mg",
"session_id": "vcs_01krdgeqcxet5s7t44vh8rt9mh",
"workspace_id": "wsp_01krdgeqcxet5s7t44vh8rt9mj",
"direction": "outbound",
"from": "+14155551234",
"to": "+16505559876"
}
}voice_call.answered
The number you called picked up, and billable time starts here. An unanswered call never emits this event, so its absence between initiated and ended is how you know nobody answered.
The payload is the call identity fields every voice event carries, with timestamp set to the moment of answer.
voice_call.ended
The call is over. This event adds the outcome:
| Field | Description |
|---|---|
| status | How it ended: answered, no_answer, failed, rejected, or unknown (see Statuses) |
| sip_response_code | The call's final SIP code, for example 200 or 486. A call Bird refused carries 503; null when no final code was recorded |
| duration_ms | Total call length in milliseconds, from the moment Bird received the call to hangup |
| billable_ms | Answered time in milliseconds, so a call nobody answered reports zero |
Przykład kodu
{
"type": "voice_call.ended",
"timestamp": "2026-06-10T14:31:05Z",
"data": {
"call_id": "vcl_01krdgeqcxet5s7t44vh8rt9mg",
"session_id": "vcs_01krdgeqcxet5s7t44vh8rt9mh",
"workspace_id": "wsp_01krdgeqcxet5s7t44vh8rt9mj",
"direction": "outbound",
"from": "+14155551234",
"to": "+16505559876",
"status": "answered",
"sip_response_code": 200,
"duration_ms": 65000,
"billable_ms": 60000
}
}Two details live on the call record rather than in this event. The rejection reason is one, so a failed status that Bird caused rather than a carrier is only distinguishable there: open the call in the Call log to read which check refused it. Cost is the other, since rating happens once the call has ended.
Consuming them safely
- Deduplicate on webhook-id. Bird delivers at least once, and a call's initiated event can be published more than once when a signaling retry replays. Same call, same stage, same webhook-id, so keying on it collapses the duplicate.
- Do not rely on order. Deliveries are not ordered, so answered can reach you after ended. Sort by timestamp, and let a later-arriving event with an earlier timestamp lose.
- Treat ended as the only reliable outcome. It is the event that carries status and durations, and it is the one to key your own records off.
- Events are best-effort alongside the record. The Call log is the record of what happened; the events are how you find out promptly. For reconciliation, export the calls as CSV rather than replaying events.
Next steps
| Page | What it covers |
|---|---|
| Webhooks & events | Endpoint setup, signature verification, retries, and replay |
| Call log | Every field on a call record, and CSV export |