Sign inGet started

WhatsApp metrics

The Metrics page in the Bird dashboard shows how your WhatsApp channel is doing: how much of it reached the recipient's device, whether your failure rate is drifting, and how fast it all moved. This guide walks through that page, what each number means, and when to act on it.
Metrics are the aggregate view across everything your workspace sends. For the lifecycle of a single message (did this number receive it, and when), see the WhatsApp log and events.

Read your metrics

The Metrics page lives at WhatsApp → Metrics in the Bird dashboard; it is visible to workspace members who hold both the WhatsApp read and Analytics read permissions. Every number respects the range picker (last 24 hours, 7, 30, or 90 days). New events appear after aggregation and replication, so the most recent period is provisional.
Outbound rates use each message's acceptance time. A delivery receipt that arrives today for a message accepted yesterday counts toward yesterday, alongside that message's own accepted. Each range therefore follows the messages accepted in it as observations arrive. Recent hours can under-report delivered while receipts are still arriving. accepted is the denominator for the delivery and failure rate cards.
Counts represent distinct messages for each observed event and can be approximate at scale. They are not a mandatory funnel: a read receipt can exist without a delivered receipt, and missing observations can leave gaps between stages. Group statistics also count messages, not recipients; the first observed participant delivery can increment the delivered count before every group member has received the message.
The WhatsApp Metrics page in the Bird dashboard: the delivery-rate, failure-rate, and accepted-volume summary tiles above the delivery-over-time chart

The summary tiles

The row of tiles at the top is your at-a-glance health check:
  • Delivery rate: delivered messages as a share of accepted. The tile reads Healthy above 95%. At or below that, something is failing to reach devices: invalid numbers, an expired care window, or a template problem. The failure-cause histogram (see Failure rate and its causes) tells you which.
  • Failure rate: the share of accepted messages that ended failed. The tile shows your rate against a 5% limit with a progress bar; reaching it flips the tile to Risk. A sustained high failure rate usually points at list quality, an expired customer service window, or a Meta rate limit.
  • Accepted: the raw count of messages accepted in the range, with the number handed on to the WhatsApp network (sent).
The 95% and 5% thresholds are the guardrails we use to color the tiles. They're deliberately conservative; a tile can read Healthy and still have room to improve.

Delivery over time

The delivery chart plots accepted, delivered, and failed volume across the range so you can spot trends and one-off spikes: a bad campaign, a number-list import gone wrong, a template that started getting rejected. The bucket size follows the range (hourly for 24 hours, daily for longer windows).

Failure rate and its causes

Below the delivery chart on the Metrics page, a failure-rate line plots the per-bucket failure rate across the range, while the Failure rate summary tile holds the whole-window figure. A histogram breaks failures down by normalized error code, ranked by count with each code's share of failed messages. WhatsApp's failure reasons are an open set, so the histogram lists whichever codes actually occurred in the range rather than a fixed list; a rising bar on one code points you straight at the fix.

Delivery latency

The latency table reports two stages at the p50, p95, and p99 percentiles:
  • Processing: from accepting a message until successful submission to the WhatsApp provider. A slow percentile here calls for investigation of the send path, including provider submission.
  • Total: end to end, from accepting the message until WhatsApp confirms delivery to the recipient's device. The gap between Total and Processing is the WhatsApp network and the recipient's device, which we don't control. A phone that is offline for an hour stretches Total while leaving Processing unchanged.
Use p95/p99 to catch the slow tail: a healthy median with a slow p99 usually points at one template or destination lagging. Latency is a whole-window figure; a stage or percentile with no data for the range shows a placeholder.

Breakdowns

The Breakdowns panel slices the same delivery numbers so you can isolate a problem to its source:
  • By number: each sending business number's accepted, delivered, and failed volume with its delivery rate, so you can compare senders side by side.
  • By template: the same split per template, to find the one template dragging a rate down.
  • By template category: the same split across Meta's template categories, the cut that also drives your spend.
  • By tag: the tags you attach to a send, the most flexible cut: tag a campaign, template, or experiment variant and compare them directly.
  • By country: the same split per destination country, to see whether a delivery problem follows a market rather than a sender or a template. A recipient whose country cannot be worked out (a number that looks like a phone number but belongs to no country, or an international range like freephone) is counted under ZZ, the same placeholder the SMS country breakdown uses. Group sends are left out because a group can span several countries and has no single destination. Historical country coverage from before this breakdown shipped can be incomplete, including delivered or read observations without matching accepted counts. Aggregate history outlives the 30-day message-detail window; waiting 30 days does not repair those older cohorts.
Each row also gets a derived status (Healthy, Watching, or Throttled) driven by its own delivery and failure rates, so a struggling number or category stands out without you reading every column. Each tab ranks the top rows for the range; when a dimension has more distinct values than fit, the panel notes "Top N of M".
The lower half of the WhatsApp Metrics page in the Bird dashboard: the delivery-latency table (processing p50/p95/p99) above the Breakdowns panel, with delivery broken down by number, template, template category, and tag

Programmatic access

The aggregates behind this page are also a public API. Typed methods ship in the TypeScript, Python, PHP, and Go SDKs under bird.whatsapp.stats, the bird CLI exposes them as bird whatsapp stats <verb>, and an agent reaches them through the whatsapp_stats_* MCP tools. Full request and response schemas are in the API reference.

The aggregate and the time series

GET /v1/whatsapp/stats/summary returns one aggregate row for the window: lifecycle counts (accepted, sent, delivered, failed, rejected) with delivery and failure rates, engagement (read, read_rate), and latency percentiles (p50, p95, p99) for three stages: processing, delivery, and total. /daily and /hourly return the same lifecycle and read counts one row per calendar day or hour, each with its own latency percentiles; only the rates (delivery_rate, failure_rate, read_rate) are whole-window figures, read those from /summary. All three accept one dimension filter at a time: template, category, tag, or phone_number. Here, phone_number restricts to a single business sender, in E.164 form, not the contact it filters by on the deprecated phone_number param of GET /v1/whatsapp/messages.
The read rate is read / delivered, while delivery and failure rates use accepted. A zero denominator returns null, meaning the rate cannot be calculated. Read rate is not capped at 100%, so missing delivered observations can produce a higher value; this is an observation gap to investigate, not evidence that more than every recipient read the message.
Latency percentiles use recorded samples. An absent delivery-latency sample does not imply zero latency, and total latency can be present when the intermediate sent timestamp was unavailable. Do not average finalized percentiles from separate buckets. Replayed events can affect latency distributions even when distinct-message counts remain deduplicated.
When present, data_as_of reports aggregation freshness. It does not prove that all provider callbacks have arrived or that billing has settled. A null freshness value means it was unavailable for that response.

Choosing the window

from and to accept a calendar day or an RFC 3339 instant, but which forms an endpoint takes differs:
EndpointBoundsMaximum window
/summaryBoth calendar days, or both RFC 3339 instants365 days, or 720 hours on instants
/dailyCalendar days only365 days
/hourlyRFC 3339 instants only720 hours (30 days)
On /summary, mixing a day bound with an instant bound returns 422. Instant bounds round down to the hour on /summary and /hourly, the only two that accept them. Set timezone to an IANA identifier to compute day and hour boundaries locally instead of in UTC; once it's set, a numeric UTC offset such as +05:45 in an instant bound is rejected. Add compare=previous_period to /summary for the preceding equal-length window and the change against it.

Breakdowns

Six endpoints rank the same delivery numbers by one dimension, each already single-dimension so none takes a filter: by number, by template, by template category, by tag, and by error code (failed messages only, grouped by normalized failure reason). Rows rank by accepted volume (failure count on error codes) and cap at limit (default 50, maximum 200). A send with no value for a dimension is absent from that breakdown: a free-form send resolves no template, and an untagged send no tag. A message with several tags can appear in several tag rows, so adding those rows does not give unique workspace volume. Compare a breakdown against itself over time. Every row but an error-code row also carries its own latency percentiles. A sixth, by country, groups the same numbers by the recipient's destination market; a recipient whose country cannot be resolved counts under ZZ, and group sends are absent because one send can span several countries.

Received messages

Four endpoints under /v1/whatsapp/stats/inbound/ cover what your numbers received rather than sent: summary, daily, hourly, and by phone number. Each row carries only a received count and follows the incoming message's occurrence time. A received message has no outbound delivery lifecycle to break down further. These are nested under bird.whatsapp.stats.inbound in the SDKs and bird whatsapp stats inbound <verb> in the CLI.

Per-message reconciliation

The stats endpoints answer aggregate questions; they don't replace per-message lookups. To confirm what happened to one message, consume webhook events as they happen, or page through GET /v1/whatsapp/messages and each message's events endpoint, whose filters (status, recipient, tag, time window) cover most reconciliation jobs.

Next steps