Verify events
A verification produces events for its session and each delivery attempt. The session starts when Bird creates the verification and converts when the recipient enters the correct code. Each passcode send creates an attempt on one channel, which can be delivered or undelivered. Resends and channel failover add attempts to the same session.
| Event | Axis | Fires when |
|---|---|---|
| verify.verification.created | Session | A verification is created and the first passcode is queued for send |
| verify.attempt.sent | Delivery | A passcode has been handed to a channel for delivery |
| verify.attempt.delivered | Delivery | The channel confirmed the passcode reached the recipient |
| verify.attempt.undelivered | Delivery | The channel could not get the passcode to the recipient |
| verify.verification.verified | Session | The recipient submitted the correct code before the verification expired |
A verification that expires or exhausts its check attempts does not emit verify.verification.verified. Its status resolves to failed or expired. A fallback channel creates its own verify.attempt.sent, so one verification can have multiple attempt sequences.
The event type list is open: new types may be added over time, so treat an unrecognized value as a future event rather than an error.
The event envelope
Events arrive at your webhook endpoint in the Standard Webhooks nested envelope described in the Webhooks guide: a type, a timestamp, and a type-specific data object. The event's identity is not in the body: it rides in the webhook-id HTTP header, which is stable across retries of the same delivery and is your deduplication key.
Every event's data carries this identity base:
- verification_id: the verification this event belongs to, matching the id from POST /v1/verify/verifications
- workspace_id: the workspace that created the verification
- to: the recipient identity of the verification, an object with email and/or phone_number matching what the create request supplied. A single passcode attempt reports the one address it went to in its own address field
- metadata: the free-form object from the create request, echoed unchanged, or null when the request carried none
Session events
verify.verification.created
Fires as soon as a verification is created and its first passcode is queued. Adds channel (the channel the first attempt is going out on), status: "pending", and created_at.
Code example
{
"type": "verify.verification.created",
"timestamp": "2026-07-23T14:45:58Z",
"data": {
"verification_id": "vrf_01ky7q1fdze3695yvyz7z9nm3a",
"workspace_id": "ws_01ky7m235keycbnwyajabe1a6b",
"channel": "sms",
"to": { "phone_number": "+14155550100" },
"status": "pending",
"created_at": "2026-07-23T14:45:58Z",
"metadata": { "user_id": "usr_4821" }
}
}verify.verification.verified
Fires when POST /v1/verify/verifications/check confirms the correct code. Adds status: "verified", channel (whichever channel delivered the submitted code, or null when the verification resolved without attributing a channel), and verified_at.
Code example
{
"type": "verify.verification.verified",
"timestamp": "2026-07-23T14:46:38Z",
"data": {
"verification_id": "vrf_01ky7q1fdze3695yvyz7z9nm3a",
"workspace_id": "ws_01ky7m235keycbnwyajabe1a6b",
"to": { "phone_number": "+14155550100" },
"status": "verified",
"channel": "sms",
"verified_at": "2026-07-23T14:46:38Z",
"metadata": { "user_id": "usr_4821" }
}
}Delivery events
Every passcode Bird sends is one attempt. A resend or channel failover creates another attempt against the same verification_id, with its own delivery sequence. The webhook-id header keeps each attempt's events distinct.
verify.attempt.sent
Fires once Bird has handed the passcode to the channel. Adds channel, address (the single address this attempt was sent to, an E.164 phone number or an email address), from (the sending address or number, null when the channel exposes no sender), and sent_at.
Code example
{
"type": "verify.attempt.sent",
"timestamp": "2026-07-23T14:45:59Z",
"data": {
"verification_id": "vrf_01ky7q1fdze3695yvyz7z9nm3a",
"workspace_id": "ws_01ky7m235keycbnwyajabe1a6b",
"to": { "phone_number": "+14155550100" },
"channel": "sms",
"address": "+14155550100",
"from": "29999",
"sent_at": "2026-07-23T14:45:59Z",
"metadata": { "user_id": "usr_4821" }
}
}verify.attempt.delivered
Fires when the channel confirms that the passcode reached the recipient. Adds channel, address, carrier, mcc_mnc (the handling network and its mobile country/network code), and delivered_at. The carrier and mcc_mnc fields are always null for email, WhatsApp, and Telegram. This event omits from; read it from verify.attempt.sent for the same attempt.
Code example
{
"type": "verify.attempt.delivered",
"timestamp": "2026-07-23T14:46:03Z",
"data": {
"verification_id": "vrf_01ky7q1fdze3695yvyz7z9nm3a",
"workspace_id": "ws_01ky7m235keycbnwyajabe1a6b",
"to": { "phone_number": "+14155550100" },
"channel": "sms",
"address": "+14155550100",
"carrier": "Example Wireless",
"mcc_mnc": "310260",
"delivered_at": "2026-07-23T14:46:03Z",
"metadata": { "user_id": "usr_4821" }
}
}verify.attempt.undelivered
Fires when the channel could not deliver the passcode. Adds channel, address, reason (an open enum including carrier_rejected, hard_bounce, soft_bounce, undelivered, and channel_unavailable), error (display-only detail, or null), and failed_at. Like verify.attempt.delivered, this event omits from.
Code example
{
"type": "verify.attempt.undelivered",
"timestamp": "2026-07-23T14:46:04Z",
"data": {
"verification_id": "vrf_01ky7q1fdze3695yvyz7z9nm3a",
"workspace_id": "ws_01ky7m235keycbnwyajabe1a6b",
"to": { "phone_number": "+14155550100" },
"channel": "sms",
"address": "+14155550100",
"reason": "carrier_rejected",
"error": "Carrier rejected the message before delivery",
"failed_at": "2026-07-23T14:46:04Z",
"metadata": { "user_id": "usr_4821" }
}
}An undelivered attempt on a recipient with more than one channel available does not end the verification. Bird advances to the next channel in the delivery plan, which gets its own verify.attempt.sent. A channel that fails before sending emits verify.attempt.undelivered with reason: "channel_unavailable" and advances in the same way. That attempt has no verify.attempt.sent or later delivery report. If every channel fails, Bird emits one verify.attempt.undelivered per attempt.
Delivery reports are indicative rather than guaranteed. Carriers and mailbox providers vary in what they confirm and how quickly. In some markets, attempt events arrive minutes later or do not distinguish delivery from acceptance. Treat verify.verification.verified as the definitive signal that a recipient received and used their code.
Webhooks
Subscribe an endpoint to any verify.* type from the Webhooks page in the dashboard or through the webhooks API. The Webhooks guide covers creating endpoints, verifying the Standard Webhooks signature, retries, and replaying missed deliveries.
Next steps
| Page | What it covers |
|---|---|
| Sending verifications | The send and check calls, statuses, settings, and limits |
| Webhooks & events | Endpoint setup, signature verification, retries, and replay |
| API reference: create a verification | Send-endpoint schema and error details |