Skip to content
man glossary/statuses-webhook

glossary define statuses-webhook

Name

Statuses Webhook

Status webhooks report what happened to each message you sent — sent, delivered, read, played or failed — keyed by its wamid, with pricing and error details.

Synopsis

Status webhook, Message status notification, statuses array, Delivery receipt webhook

Section

Webhooks & events

Level

practitioner

▍

Reviewed

Trace one wamid from send to read

6 / 6

Next dayEvent 6 of 6

failed

A marketing message to another user: errors[0].code 131049 with error_data.details. Wait at least 24 hours before retrying.

An illustrative utility message's status webhooks in arrival order, including a retried duplicate and a failure on a different message. Step through to see what each payload carries.

Event 6 of 6: Next day — failed. A marketing message to another user: errors[0].code 131049 with error_data.details. Wait at least 24 hours before retrying.

Description

What is the statuses webhook?

The statuses webhook is a notification on the messages field whose value contains a statuses array instead of a messages array — the same field as the messages webhook, with the opposite job. Each item reports one step in the life of a message your business sent: id (the wamid the send call returned), status, timestamp and recipient_id. The status is sent, delivered, read or failed, plus played the first time a recipient plays a voice message. A message can trigger up to three success statuses — sent, delivered and read (plus played for a voice message) — or a single failure, and when a message is read the moment it arrives Meta skips the delivered webhook, because the read implies it.

Two objects make statuses more than ticks. The webhook pricing object, pricing, rides on the sent status and on one of the later delivered or read statuses, and says how the message is billed: pricing_model (PMP, per-message pricing, for everything since 1 July 2025), type (regular or free_entry_point, plus free_customer_service for in-window replies before 1 October 2026) and category (marketing, utility, authentication, authentication-international, service, marketing_lite or referral_conversion). Meta says its billable flag will be deprecated in a future version. The errors array, one of the webhook error objects, appears on failed statuses with code, title, message, error_data.details and an href to Meta's error-code page.

Several fields are conditional. recipient_user_id carries the user's business-scoped user ID, biz_opaque_callback_data echoes a tracking string you set when sending, and group messages add recipient_type: group and recipient_participant_id. On webhooks for Graph API v24.0 and later the old conversation object is omitted except inside a free entry point window. This page is about the payload; the message status page explains what the ticks mean to a person reading the chat.

Key takeaways

  • A statuses webhook reports what happened to a message you sent — sent, delivered, read or failed — keyed by its wamid.
  • Statuses arrive on the messages field in a statuses array, often batched and never guaranteed to be in order.
  • The pricing object rides on sent and on one of delivered or read, giving the billing category and type.
  • Failed statuses carry an errors array with a numeric code, such as 131049 for per-user marketing limits.
  • Rank sent < delivered < read, only ever move forward, and deduplicate on wamid plus status.

Usage

The statuses webhook in code

const RANK = { sent: 10, failed: 15, delivered: 20, read: 30, played: 40 }; // failed only beats sent
export async function onStatuses(change) {
for (const s of change.value.statuses ?? []) {
const key = s.id + ":" + s.status;
if (await seen(key)) continue; // retries resend statuses
const msg = await outbound.byWamid(s.id); // saved when you sent it
if (!msg) { await orphans.save(s); continue; } // a status can beat your insert
if (RANK[s.status] > (RANK[msg.status] ?? 0)) { // never move backwards
await msg.update({ status: s.status, at: Number(s.timestamp) * 1000 });
}
if (s.pricing && !msg.pricing) { // first seen on sent; repeated on delivered or read
await msg.update({ pricing: { category: s.pricing.category, type: s.pricing.type } });
}
if (s.status === "failed") {
const e = s.errors?.[0];
await failures.record(msg, e?.code, e?.error_data?.details);
}
}
}

Deduplicate on wamid plus status, only move forward, record pricing once and file failures by code.

Mechanics

How does the statuses webhook work?

  1. step 1 — Store the wamid at send time

    The send call returns messages[0].id. Save it with your own reference (order ID, campaign ID) before any status can arrive, so every webhook can be joined back to a business record.

  2. step 2 — Recognise statuses on the messages field

    Statuses arrive on the same messages field as inbound messages. If value.statuses exists, loop over every item; one POST can batch statuses for several messages.

  3. step 3 — Move status forward only

    Rank sent < delivered < read and only ever upgrade. Webhook event ordering is not guaranteed, so a read that arrives before its delivered must not be overwritten by it.

  4. step 4 — Record pricing once

    Take pricing.category and pricing.type from the first status that carries them (normally sent), but count the message as billed only once delivered or read arrives. Reconcile on those fields, not on billable, which Meta plans to deprecate.

  5. step 5 — Handle failures by code

    On failed, read errors[0].code and error_data.details. Some codes need a different template or a pause (131049, for example, means wait at least 24 hours before retrying marketing to that user).

Properties

The statuses webhook: key facts and limits

Arrives on
messages field, value.statuses[]
Status values
sent, delivered, read, played, failed
Join key
statuses[].id = the wamid returned by the send call
Pricing model
PMP (per-message) since 1 July 2025
Pricing object on
sent, plus one of delivered or read
Pricing type
regular, free_entry_point (free_customer_service until 30 Sep 2026)
conversation object
Omitted on v24.0+ webhooks, except in a free entry point window
Ordering
Not guaranteed: a read can arrive before its delivered

Caveats

Common mistakes with the statuses webhook

  • Letting late statuses overwrite newer ones

    Webhooks are not ordered. If delivered arrives after read and you simply save the latest webhook, the message moves backwards. Rank statuses and only upgrade.

  • Expecting a read for every delivered message

    Users can switch the read receipt setting off, and many simply never open a message. A missing read is not a failure — report read rate against delivered, not against sent.

  • Billing from billable or from sent

    billable is slated for deprecation, and a sent status carries pricing too, yet Meta bills delivered messages only. Count deliveries by pricing.category and pricing.type; since 1 October 2026 a service category in India is charged at ₹0.145.

  • Dropping statuses that beat your database write

    A fast sent webhook can arrive before your own insert commits. Park unmatched statuses briefly and replay them, instead of discarding them as unknown wamids.

Why it matters

Why does the statuses webhook matter?

Statuses are the only record of whether a message actually reached someone. Support teams use them to answer "did the customer get the OTP?", marketers to measure delivery and read rates, and finance to reconcile Meta's invoice, since billing counts delivered messages only. A pipeline that drops, misorders or double-counts statuses produces wrong dashboards, wrong invoices and agents who cannot tell a failed send from a slow reader.

Example

Example: Proving an OTP arrived in Pune

A Pune lending app's customer says she never received her login code. The support agent searches by her number and sees the authentication template's wamid with three statuses: sent at 21:14:02, delivered at 21:14:04 with pricing.category: authentication, and no read yet, because she has either not opened the chat or switched read receipts off. The agent can say with confidence that the code reached her phone two seconds after it was sent, and suggests she check the chat rather than resending and paying for a second delivery.

Notes

Best practices for the statuses webhook

  • Set biz_opaque_callback_data when sending campaigns; it comes back on the status and saves a lookup.
  • Alert on a rising share of failed statuses per template, not just on individual failures.
  • Store recipient_user_id beside recipient_id, since usernames can hide phone numbers.
  • Keep failed error codes in reporting — 131049 and 131026 point to very different fixes.

Questions

Statuses Webhook FAQs

Why do I get statuses on the messages field?
Meta groups inbound messages and outbound statuses under one subscription field. The value object tells them apart: a messages array is inbound, a statuses array reports on what you sent.
Which status means I am charged?
The delivered (or read) status is the one that marks a charge, because Meta bills delivered messages only. The pricing object already appears on the sent status, so use it to classify the message, but do not count it as billed until delivery is confirmed.
Why don't I see the conversation object any more?
On webhooks for Graph API v24.0 and later Meta omits it, except for messages inside a free entry point window. With per-message pricing, billing no longer depends on a conversation ID.
Can a message go from failed to delivered?
Treat failed as final for that wamid. If you resend, the new message has a new wamid and its own statuses.

See InfiQ pricing

* Per-message rates for India, ex-GST: marketing, utility and authentication effective 1 July 2026; service messages ₹0.145 since 1 October 2026 (they were ₹0 until 30 September 2026). Volume commitments earn discounts — final rate is confirmed on your account; applicable GST extra. Rates for other countries differ (see the international rate table on /pricing).

Put Statuses Webhook to work

InfiQ is an official Meta Business Partner. Start a 7-day free trial — no card needed — and see this running on your own number.