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 |
| verify.verification.failed | Session | The delivery plan ended with failures indicating that no passcode was sent |
A verification that does not convert never emits verify.verification.verified, and its status does not tell you why on its own. failed is shared: a verification lands there both when too many incorrect passcodes were submitted, with reason attempts_exhausted, and when the delivery plan ends with failures indicating that no passcode was sent, with reason undeliverable. Only the second emits verify.verification.failed, and that event always carries reason undeliverable, so the event is what separates the two where the status cannot. A validity window that elapses resolves to expired. Neither expired nor an attempts-exhausted failed emits an event of its own. 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.
Exemple de code
{
"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.
Exemple de code
{
"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" }
}
}verify.verification.failed
Fires when the delivery plan is exhausted and the recorded failures indicate that no passcode was sent. The payload adds status: "failed", reason: "undeliverable", channel (the last channel tried, or null when none was attributed), last_attempt_reason, and failed_at.
channel_unavailable, channel_disabled, channel_restricted, and not_billable indicate that an attempt did not send a passcode. If an attempt might have sent one, a later bounce, carrier rejection, or delivery timeout leaves the session pending and emits no verify.verification.failed. An earlier code can still verify before it expires.
last_attempt_reason uses the same failure reasons as verify.attempt.undelivered. A not_billable failure means the send could not be charged; check the workspace balance and whether pricing is available for the destination.
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. No event carries an attempt identifier, and webhook-id will not group them: it identifies one delivery of one event, so the sent and the delivered for a single attempt carry different values. Pair them on verification_id, channel and address in timestamp order. A resend on the same channel is the case that defeats this, since its events differ only by timestamp.
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.
Exemple de code
{
"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.
Exemple de code
{
"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, channel_unavailable, channel_restricted, channel_disabled, delivery_timeout, and not_billable), error (display-only detail, or null), and failed_at. Like verify.attempt.delivered, this event omits from.
Exemple de code
{
"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, as does one that does not carry passcodes to the recipient's country, with reason: "channel_restricted" (see Country configuration). That attempt has no verify.attempt.sent or later delivery report. Bird emits verify.attempt.undelivered for each failed attempt. If the plan is exhausted and the recorded failures indicate that no passcode was sent, it also emits verify.verification.failed for the session.
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 |
Ressources associées
Poursuivez avec la documentation, les guides et les exemples sur ce sujet. Les ressources sont en anglais.
Regarder le guideVerify phone numbers at signupComprendre le conceptWhat does OTP mean? One-time passwords explainedExplorer la fonctionnalitéCustomer verificationSuivre le parcours d'apprentissageBuild your first integration
Essayez la pratique et obtenez un guide d'implémentation