A customer waiting for a code experiences queueing, delivery, reading and entry as one delay. Separate those intervals before deciding whether a slow signup reflects message delivery or the wider verification flow.
Which timestamps should I collect?
Collect the Verify lifecycle events through a webhook subscription. Store their payload timestamps.
The event payloads identify these stages:
| Event | Timestamp | What it records |
|---|---|---|
verify.verification.created | created_at | The verification was created |
verify.attempt.sent | sent_at | Bird handed a passcode to a channel |
verify.attempt.delivered | delivered_at | The channel reported delivery |
verify.attempt.undelivered | failed_at | That passcode attempt failed |
verify.verification.verified | verified_at | The recipient submitted the correct code |
verify.verification.failed | failed_at | The delivery plan could not deliver a code |
Keep the event type with each timestamp. The two failed_at fields describe different scopes: one attempt and the verification's delivery plan.
Deduplicate deliveries using webhook-id, which identifies an event and stays stable across retries. Use the payload's timestamp when ordering events, because delivery order can change.
Which interval answers my question?
Use sent-to-delivered for reported delivery timing. Use created-to-verified for completed verification timing.
| Interval | What it includes |
|---|---|
created_at to sent_at | Time before the channel accepted a passcode, potentially including earlier channel failures |
sent_at to delivered_at | Processing after channel acceptance and the reported delivery interval |
created_at to verified_at | The complete wait, including reading and entering the code |
A sent timestamp does not always mark carrier submission. For SMS, Bird's channel accepts the attempt before the downstream SMS pipeline submits it to the carrier.
Delivery reports are indicative. Carriers and mailbox providers differ in what they confirm and how quickly they report it.
Compare the same channel and market over time. Differences between countries can reflect reporting conventions as well as delivery speed.
A verified event confirms that the code was received and used. Its interval measures completion rather than message delivery alone.
How do I pair events when codes are resent?
Group events by verification_id, channel and recipient address. Exclude pairs that remain ambiguous.
Verify's public events contain no attempt identifier. A resend or channel change creates another attempt under the same verification identifier.
A sent event and its delivered event have different webhook-id values. That header deduplicates events. It does not join the stages of an attempt.
Timestamp order can separate simple sequences. Repeated sends to the same address on the same channel can overlap. Ordering alone does not prove which delivery matches.
Mark those samples as ambiguous instead of assigning a precise attempt latency. The verification's created-to-verified interval remains a separate measurement.
An unavailable or restricted channel can fail without a sent event. Require sent_at before calculating a sent-to-delivered interval.
Why can my figures differ from the dashboard?
The dashboard can measure a different interval. It can also include different attempts than your event report.
The dashboard measures attempt creation to resolution for qualifying charged, delivered attempts. It excludes corrected delivery timeouts from that latency sample because their resolution times are not measured deliveries.
Its reporting window uses the charge time. An event-based report using send time can therefore include a different set of attempts.
The stored latency reflects delivery state when the charge is read. A later delivery update can leave that sample unchanged.
A percentile is null when no qualifying samples exist. Preserve that distinction instead of displaying zero, which would imply immediate delivery.
Compare the same reporting window before investigating a discrepancy. Use webhook events when your application needs its own interval and grouping.
How should I report slow and missing codes?
Report timing alongside undelivered attempts and verifications that did not complete.
A latency report containing only delivered attempts omits the people whose codes never arrived. Keep those failures visible beside the timing summary.
The verify.verification.failed event covers exhausted delivery plans. Expiry and exhaustion of incorrect-code attempts do not emit that event, so it is not a complete nonconversion count.
Track verification creation and successful completion in your application. Keep unresolved sessions separate rather than assigning them an invented delivery duration.
Break down attempts by channel and recipient market. When reported, carrier and mcc_mnc identify the handling network. Both are null for email, WhatsApp and Telegram.
A channel failover can explain a customer's late code. Inspect the attempt sequence before treating the whole delay as one channel's delivery time.
In short
Choose the interval you need.
Reported delivery time and completed verification time answer different questions. Completion includes reading and entering the code.
Pair attempts cautiously.
Events identify the verification but not each attempt. Repeated sends on the same channel can make pairing ambiguous.
Keep failures beside the latency report.
Successful deliveries alone exclude codes that never arrived. Report undelivered and incomplete verifications separately.
Compare like-for-like traffic.
Delivery reports differ by channel and market. Record your interval and sample rules before comparing percentiles.