# Verify events

A verification produces events on two axes. One is the **session**: it's created, and it eventually converts when the recipient enters the right code. The other is the **delivery** of each passcode Bird sends toward that recipient: an attempt goes out on a channel, and it either reaches the recipient or it doesn't. A verification that resends a code, or that fails over from SMS to WhatsApp, produces more than one delivery attempt against 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 |

Not every verification reaches `verify.verification.verified`: one that expires or exhausts its check attempts stops without ever emitting it, the same way [a verification's own status](/docs/guides/verify/sending-verifications#statuses) can resolve to `failed` or `expired` instead of `verified`. On the delivery axis, an attempt on a fallback channel produces its own `verify.attempt.sent`, so a single verification with a channel failover can carry two full 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](https://www.standardwebhooks.com) nested envelope described in the [Webhooks guide](/docs/guides/webhooks): 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`](/docs/api/reference/create-verification)
- `workspace_id`: the workspace that created the verification
- `to`: the recipient identity of the verification, an object with `email_address` 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`.

```json
{
  "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 the moment [`POST /v1/verify/verifications/check`](/docs/api/reference/create-verification-check) confirms the correct code. Adds `status: "verified"`, `channel` (whichever channel delivered the code the recipient actually entered, or `null` when the verification resolved without attributing a channel), and `verified_at`.

```json
{
  "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 a channel failover creates a new attempt against the same `verification_id`, each with its own delivery sequence below; the `webhook-id` header keeps each attempt's events distinct even when their payloads look alike.

### 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`.

```json
{
  "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": "Authifly",
    "sent_at": "2026-07-23T14:45:59Z",
    "metadata": { "user_id": "usr_4821" }
  }
}
```

### verify.attempt.delivered

Fires when the channel confirms the passcode reached the recipient. Adds `channel`, `address`, `carrier`, and `mcc_mnc` (the handling network and its mobile country/network code, always `null` for email and WhatsApp), and `delivered_at`. There is no `from` on this event; the sender rode on `verify.attempt.sent` for the same attempt.

```json
{
  "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 get the passcode to the recipient. Adds `channel`, `address`, `reason` (an open enum: `carrier_rejected`, `hard_bounce`, `undelivered`, `channel_unavailable`, with more added over time), `error` (a human-readable, display-only string describing what happened, `null` when the channel gave no detail), and `failed_at`. There is no `from` on this event, the same as `verify.attempt.delivered`.

```json
{
  "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 doesn't end the verification: Bird advances to the next channel in the [delivery plan](/docs/guides/verify/sending-verifications#recipient) and that attempt gets its own `verify.attempt.sent`. A channel that fails at send time, before the message ever leaves Bird, reports the same event with `reason: "channel_unavailable"` and advances the failover the same way: there is no `verify.attempt.sent` for that attempt, and no delivery report follows it. So a verification whose every channel fails still tells the whole story: one `verify.attempt.undelivered` per attempt, never silence after `verify.verification.created`.

Delivery reports are indicative, not a hard guarantee: carriers and mailbox providers vary in what they confirm and how quickly, so `verify.attempt.delivered` and `verify.attempt.undelivered` can arrive minutes apart from the send, or, for some carriers and markets, not distinguish delivery from acceptance at all. Treat `verify.verification.verified` as the definitive signal that a recipient received and used their code; the attempt events tell you where it went, not a guarantee it was read.

## Webhooks

Subscribe an endpoint to any `verify.*` type from the [**Webhooks**](https://bird.com/dashboard/w/webhooks) page in the dashboard or through the webhooks API. The [Webhooks guide](/docs/guides/webhooks) covers creating endpoints, verifying the Standard Webhooks signature, retries, and replaying missed deliveries.

## Next steps

| Page                                                                            | What it covers                                              |
| ------------------------------------------------------------------------------- | ----------------------------------------------------------- |
| [Sending verifications](/docs/guides/verify/sending-verifications)              | The send and check calls, statuses, settings, and limits    |
| [Webhooks & events](/docs/guides/webhooks)                                      | Endpoint setup, signature verification, retries, and replay |
| [API reference: create a verification](/docs/api/reference/create-verification) | Send-endpoint schema and error details                      |