Skip to content

Parity: Zaileys, Meta's Cloud API and meta-whatsapp-rs ​

Verified against main at b6fc893 (PR #20) on 2026-09-26; rows 17, 19 and 84–88 again when the bot framework (roadmap B1) landed, against its code; the service's cells of rows 5, 6, 9, 33, 84, 91, 112 and 113 again when its core was extracted (roadmap S1), against the core's code; rows 69, 91, 111 and 139 again with the ConversationStore port change (roadmap L5), against its code; rows 89 and 92 when paced broadcast (B2) landed, against its code; rows 119 and 139 again when the inbox began recording window events, thread ownership and identity links (roadmap L7), against its code; rows 66, 129–133 and 151 again with the phone number calls (roadmap L9), against their code, and row 156 added for what L9 left (L26); the service's cell of row 9 again with S2's security review, against its code; row 152 again with the parent BSUID accounts API (roadmap L10a), against its code; row 140 again with the Messaging Account a send bills (roadmap L16a), against its code and Meta's account model evolution pages of 2026-10-26; the service's cell of row 112 again when the port suites moved into the core (roadmap S3), against its code, and again when the service was composed from the bundle alone (roadmap S4), against its code and its tests; row 155 with app deep links on template URL buttons (roadmap L24), against its code and the service's test; rows 101 and 153 with template archiving and template groups (roadmap L23), against its code and Meta's pages of 2026-10-26 (§ Sources); row 156 with the number settings L9 left (roadmap L26), against its code and Meta's pages of 2026-10-27 (§ Sources); row 103 with the catalog and product reads (roadmap L17), against its code and Meta's Catalog API pages of 2026-10-27 (§ Sources); row 63 with the typed Flow JSON builder (roadmap L18), against its code and Meta's Flows pages of 2026-10-28; row 135 with call recording and transcription (roadmap L14), against its code and Meta's calling/call-recording and calling/call-transcription pages of 2026-10-31; rows 144 and 145 with Multi-Partner Solutions and migration intents (roadmap L12), against its code and Meta's solution-provider pages of 2026-11-01; rows 90, 127 and 146–148 with WABA administration (roadmap L10b), against its code and Meta's pages of 2026-11-01; row 139 again with the thread control API (roadmap L15), against its code and Meta's conversation-routing pages of 2026-11-01; rows 137, 138 and 154 with max price, reach estimates and welcome message sequences (roadmap L13), against its code and Meta's marketing and CTWA pages of 2026-11-01; rows 89 and 90 with durable scheduling (roadmap B3), against its code and Meta's templates/marketing-templates/per-user-limits page of 2026-11-01; row 48 with media conversion (roadmap L19), against its code and Meta's business-phone-numbers/media and messages/audio-messages pages of 2026-11-01, and again with stickers and image resizing (roadmap L19b), against its code and Meta's messages/sticker-messages and business-phone-numbers/media pages of 2026-11-01; row 91 with retention and auto-delete (roadmap B4), against its code; row 117 with llms.txt and the service's doctor (roadmap L22a), against their code and tests and Meta's webhook pages of 2026-11-01 (§ Sources); row 6 with the token-expiry signal (roadmap L11e), against its code and Meta's access-tokens and facebook-login/facebook-login-for-business pages of 2026-11-01; rows 69, 110 and 111 with the message and contact stores (roadmap L8), against its code and Meta's webhooks/reference/smb_app_state_sync page of 2026-11-01; row 139 again with the Routing Roles API (roadmap L15a), against its code and Meta's conversation-routing/routing-roles page of 2026-11-01; row 117 again with the docs MCP server (roadmap L22b), against its code and tests and the MCP specification of 2026-11-01 (§ Sources); row 122 with several WABAs in one signup (roadmap L11b), against its code and Meta's embedded-signup/implementation and solution-providers/manage-accounts pages of 2026-11-01; row 123 with pre-verified numbers (roadmap L11c), against its code and Meta's embedded-signup/pre-verified-numbers and pre-verified number reference pages of 2026-11-01. Every cell about us was checked against the code: a cell that says a thing is done names the symbol that does it, and what the service (meta-whatsapp-server) does is read from its routes, not from its design. The plan to close the gaps is roadmap.md; the Meta platform categories are in categories.md; the per-feature list is coverage.md.

Where we stand ​

42 of the 132 counted rows are done on both sides. Parity is not reached.

132 counted rowsdonepartialgapn/a (that side does not carry it)
Library125601
Service4222671
  • The table has 156 rows. 24 of them are not counted: they work only over WhatsApp Web ("n/a — unofficial protocol"). The counted rows' n/a column is a side that does not carry the capability at all: the library for row 115 (packaging), the service for row 63 (a Flow JSON builder).
  • Each of the library's 6 partial or gap rows is cited by a roadmap item. Each of the service's 89 names, in its status, the roadmap items that bring it; by family (a row can name two): M2 8, M3 11, M4 2, M5 70, P 2 (payments).
  • The largest gaps: the service routes for the modules the design once left "on demand" (M5), the inbox, live events and onboarding over HTTP (M2, M3), and the rest of the bot framework (section G: its commands, middleware, plugins, access lists and Markdown replies exist, B1, with subcommands and flags, B1b, paced broadcast, B2, rich replies, B1c, durable scheduling, B3, and retention and auto-delete, B4; over HTTP they wait for M5k).

What parity means ​

The owner's definition (2026-09-26):

  • Cloud API only. Every capability of Meta's Cloud API is a row. A Zaileys capability that works only over WhatsApp Web is listed and marked "n/a — unofficial protocol"; meta-whatsapp-rs has no WhatsApp Web provider and will not get one.
  • Zaileys' framework features are in scope, although Zaileys runs them only over WhatsApp Web: commands, middleware, plugins, broadcast with rate limiting, scheduled messages, auto-delete, message stores, rich markdown responses, multiple accounts and typed errors. We build them on Cloud API webhooks.
  • Done means every row that is not n/a is done in the library, and in the service wherever the service is meant to carry it (roadmap.md § Done criteria).
  • This is the comparison with Zaileys and Meta's Cloud API, not CONTRIBUTING.md's "docs and skills parity" (the docs and skills agreeing with the code).

How to read the table ​

  • Status is library / service, each one of done, partial, gap or n/a. A side is done when it carries every endpoint and field the capability's pages document for the Graph API version we target (v25.0), partial when it carries some, gap when it carries none, and n/a when that side does not carry the capability. A part another row covers counts in that row, and a part outside the Cloud API (Meta's ads APIs, say) is named and not counted. Something the service will carry counts as a gap (or partial) until it ships; the roadmap items that bring it are in parentheses. An n/a row says why instead.
  • The same grades hold in coverage.md, whose planned is a gap here and whose out of scope is an n/a, and in categories.md, which has no n/a (a category's n/a parts are named in its notes).
  • Zaileys (Web) and Zaileys (Cloud): Yes, No, Partial, or — when Zaileys does not mention the capability. The wording is ours.
  • Meta Cloud API: doc paths relative to https://developers.facebook.com/documentation/business-messaging/whatsapp/ (append .md for Markdown). (nm) marks one of the 5 pages Meta lists that the local mirror (.meta-docs/, just meta-docs) cannot fetch (flows/changelog, overview, pricing/prepaid-billing, webhooks/reference/messaging-handovers, webhooks/reference/standby: missing or gated at Meta); a claim resting on one rests on the reference pages or on our code's fixtures. Every other page cited is in the mirror.
  • Library and Service: code cited by module path and symbol, never by line. client:: is meta_whatsapp_client::, webhooks:: is meta_whatsapp_webhooks::, core:: is meta_whatsapp_core::, adapters:: is meta_whatsapp_adapters::, typst:: is meta_whatsapp_typst:: (the facade meta_whatsapp_rs re-exports each under the same name), inbox:: is meta_whatsapp_rs::inbox::, server:: is meta_whatsapp_server::, the service's binary (its process: configuration, serve, the command line), server_core:: is meta_whatsapp_server_core::, its framework-free core (the domain, the authorization order, event routing and polling, idempotency, the error model as data, and the ports its backends implement), and since roadmap S7 the crates the binary composes: server_http:: is meta_whatsapp_server_http:: (listeners, authentication, the error renderer, Meta's webhook, media, operations), server_api_axum:: is meta_whatsapp_server_api_axum:: (the /v1 routes and their OpenAPI document), server_store_memory:: and server_store_postgres:: the backends; an item the service re-exports from another of its crates is cited where it is declared (the binary re-exports the moved modules under their old paths). The bot framework's items (meta_whatsapp_bot::, re-exported as meta_whatsapp_rs::bot) are cited by type (Command::alias) in a cell that names meta-whatsapp-bot. A type already qualified in a row is not qualified again in it. Routes are the service's /v1 API; "the send union" is its message types (server_api_axum::api::messages::MessageType: text, the five media types, location, contacts, reaction, template, and interactive button, list, cta_url).
  • Row numbers are permanent: other documents cite them.

Sources ​

  • Zaileys (zeative/zaileys, a Node library over two providers, Baileys for WhatsApp Web and Meta's Cloud API): its feature matrix and the feature pages in its navigation (getting started, core concepts, messaging, bots, chats, groups, data, and its Cloud pages), read on 2026-09-26 through summaries. Where its pages disagree (its providers page against the matrix, for locations and HTML apps; the plugins and middleware pages against the matrix and the Cloud overview), the matrix and the Cloud overview win here. Not read: the quickstart, installation, client reference, changelog and the recipes other than auto-reply. The Zaileys columns were not re-read for this revision.
  • Meta: .meta-docs/, crawled on 2026-09-24 and completed on 2026-09-26. The rows that rested on the pages the first crawl missed were checked again against those pages that day: their key facts (limits, fields, endpoints) and every claim that something is absent, not each sentence. Rows 101 and 153 were rewritten on 2026-10-26 against templates/template-archival and templates/template-groups as Meta served them that day, not against the mirror: the first now shows the archive and unarchive endpoints, and the second, absent from Meta's page list at the crawl, is in it now. Row 156 was checked on 2026-10-27 against reference/whatsapp-business-phone-number/settings-api and reference/whatsapp-business-phone-number/whatsapp-business-account-phone-number-api as Meta served them that day: both still document its fields by schema only, without an example.
  • Meta's Catalog API, outside the WhatsApp docs and the mirror, for row 103: read on 2026-10-27 as Meta served it that day. From the Marketing API reference (developers.facebook.com/documentation/ads-commerce/marketing-api/reference/, as Markdown): product-catalog, product-catalog/products, product-catalog/product_sets, product-set and product-set/products. From the Graph API reference (developers.facebook.com/docs/, as HTML; the new documentation serves no product item page): marketing-api/reference/product-item, graph-api/reference/whatsapp-business-account/product_catalogs and graph-api/results (cursor pagination).
  • Meta's Flows pages, for row 63: read on 2026-10-28 as Meta served them that day, as Markdown, not against the mirror: flows/guides/flowjson, flows/guides/components, flows/guides/media_upload, flows/guides/versioning, flows/changelogs, flows/reference/error-codes, flows/guides/flowsapi and flows/guides/flows-templates. The examples those pages show in an interactive preview are not in their Markdown and were not read.
  • Meta's webhook subscription pages, for row 117: read on 2026-11-01 as Meta served them that day, not against the mirror. As Markdown: webhooks/overview (§ Fields, the field names), webhooks/create-webhook-endpoint (the object whatsapp_business_account, the app token) and solution-providers/manage-webhooks (GET /{WABA_ID}/subscribed_apps and its example). As HTML, from the Graph API reference (developers.facebook.com/docs/, which serves no Markdown for them): graph-api/reference/app/subscriptions (object, callback_url, fields as string[], active; reading needs the app token) and graph-api/webhooks/subscriptions-edge (its example answer gives each field as {name, version}; the doctor reads both shapes).
  • The Model Context Protocol, for row 117's docs server, outside Meta's docs: the specification at modelcontextprotocol.io, read on 2026-11-01 as Markdown. Revision 2026-07-28, the current one (specification/versioning): changelog, basic/index (_meta, resultType, the error codes), basic/versioning (no handshake, the dual-era rules), basic/transports/stdio, server/discover, server/tools (protocol errors against isError) and server/utilities/caching; and revision 2025-11-25's basic/lifecycle (the initialize handshake).
  • Us: the code at b6fc893; for rows 17, 19 and 84–88, the bot framework's (B1).

The table ​

A. Connection, credentials, accounts ​

#AreaCapabilityZaileys (Web)Zaileys (Cloud)Meta Cloud APILibraryServiceStatus
1ConnectionQR-code loginYesNonone: the Cloud API authenticates with access tokens (access-tokens)——n/a — unofficial protocol
2ConnectionPairing-code loginYesNonone——n/a — unofficial protocol
3ConnectionSaved session, logout, a budget of login attemptsYesNo (the Cloud API is stateless HTTPS)none——n/a — unofficial protocol
4ConnectionToken authentication; checking the token and the number at startNoYes (checks the token and the phone number id when it connects)access-tokens, permissions.mdclient::ClientBuilder::access_token; token inspection client::embedded_signup::EmbeddedSignup::debug_token; client::phone_numbers::PhoneNumber::get. No single "check my credentials" callincomplete configuration refuses the start (server::config); attaching a WABA checks it with Meta: POST /v1/admin/tenants/{id}/wabas (server_core::admin::Admin::attach_waba)done / done
5ConnectionReconnect semantics on the Cloud API: back off when throttled, never replay a send that timed outYes (reconnects with growing delays)Partial (waits after a rate limit)throughput.md (error 130429), support/error-codes.mdclient::RetryPolicy (jittered exponential backoff, Retry-After honoured, a send replayed only after a throttle or the account in maintenance, core::Error::may_resend); core::ErrorKind::is_retryablemay_have_been_sent on 502/504; Idempotency-Key replays an answer, never a send (server_core::idempotency::run, over HTTP server_http::idempotency), a key covering the operation and its canonical input, not the path (server_core::idempotency::Fingerprint, S6)done / done
6ConnectionToken expiry and refresh——access-tokens (business tokens need no re-authentication; no refresh call is documented)expiry recorded (client::embedded_signup::StoredBusinessToken::expires_at) and reported a configurable lead ahead, 7 days by default, from the record's clear metadata with nothing decrypted (client::embedded_signup::TokenVault::token_expiry, a TokenExpiry; L11e); nothing refreshes it, as Meta documents no refresh (OPEN_QUESTIONS #8, decided: re-onboard)409 reconnect_required (server_core::authz, over HTTP server_http::auth); the expiry surfaced before it lapses in M3edone / partial (M3e)
7ConnectionWebhook endpoint: the verify-token handshake, X-Hub-Signature-256NoYes (client.webhook(); an unsigned mode for development)webhooks/overview.md, webhooks/create-webhook-endpoint.mdwebhooks::verify::verify_subscription; webhooks::SignatureVerifier (several secrets, fail-closed, no unsigned mode); webhooks::WebhookHandler::deliver; the axum webhooks::routerGET and POST /webhooks/meta (server_http::api::webhooks::verify, receive, over the core's pipeline since S5e, server_core::inbound::Pipeline::deliver): a missing or malformed signature header refused before the body is read, the signature checked against every app secret before parsing, 3 MiBdone / done
8ConnectionWebhook deduplication——webhooks/overview.md (Meta retries a delivery)webhooks::dedup::DedupGuard (a lease: pending, then done or released); an event the sink refuses permanently is dead-lettered and marked done instead of holding back its batch (webhooks::deadletter::DeadLetters, WebhookHandlerBuilder::dead_letters, roadmap L21a)the library's lease, shared by every replica through the Postgres KvStore; no dead letters (its sink classifies no error as permanent)done / done
9AccountsSeveral numbers and accounts, routed by phone_number_idYes (one client per session)Yes (one client per phone number id; a router reads the body)solution-providers/manage-accounts.mdclient::Client::with_token; client::embedded_signup::TokenVault::get_by_phone_number; every event carries its business numbertenants, keys and WABA bindings (server_core::admin::Admin, under the routes of server_api_axum::api::admin); each webhook event routed to the tenant that owns its number or WABA (server_core::events::route, owner), a dated event only when that binding began by its second (a first onboarding keeps its first second; after a move from another tenant, a second before it, by the skew margin server_core::events::MoveRules); since S2b an undated event (an error, a history chunk, an undated group update) of a WABA that moved from another tenant is operator-only for 7 days and an hour after the move (server_core::events::UndatedAfterMove, OperatorOnly by default, CurrentHolder the behaviour before: design D36), from the record of the WABA's last unbinding (server_core::model::WabaTombstone), so a previous holder's that Meta redelivers no longer reach a new holderdone / done
10AccountsA live event stream for a UI——none (ours to build)webhooks::sse; adapters::sink::BroadcastSinkpolling: GET /v1/events (server_api_axum::api::events::list_events); SSE and webhooks-out in M2done / partial (M2b, M2c)

B. Sending messages ​

#AreaCapabilityZaileys (Web)Zaileys (Cloud)Meta Cloud APILibraryServiceStatus
11MessagingText, with WhatsApp formattingYesYesmessages/text-messages; reference/whatsapp-business-phone-number/message-api.mdclient::messages::OutboundMessage::textPOST /v1/numbers/{pn}/messages (server_api_axum::api::messages::send_message, over server_core::messages::Messages::send), type textdone / done
12MessagingLink previews——link-previews, messages/text-messagesOutboundMessage::text_with_previewtext.preview_urldone / done
13MessagingReply to (quote) a messageYesYesmessages/contextual-repliesOutboundMessage::reply_toreply_todone / done
14Messaging@-mentionsYesNo (dropped)no mention object in reference/whatsapp-business-phone-number/message-api.md or in the groups guides (groups/groups-messaging)——n/a — unofficial protocol
15MessagingDisappearing messagesYesNonot supported: groups lists them among the types groups do not support, embedded-signup/onboarding-business-app-users says coexistence turns them off in one-to-one chats, and reference/whatsapp-business-phone-number/message-api.md has no such option——n/a — unofficial protocol
16MessagingRich responses in Meta AI's block formatYes (sent as a forwarded bot message)Noundocumented format——n/a — unofficial protocol
17MessagingRich markdown responses rendered as Cloud messages: formatting, long text split, images as media, suggestions as buttons or lists, product cards as carouselsYes (through row 16)Nobuilding blocks only: text (at most 4096 characters, messages/text-messages), media, interactive (reference/whatsapp-business-phone-number/message-api.md)formatting and the split, in meta-whatsapp-bot: Ctx::reply_markdown renders with a MarkdownRenderer (the default Renderer: bold, italic, strike, code, quotes, lists, links, tables as padded columns or header: value lines within Renderer::table_max_width and Renderer::table_max_growth) and sends messages of at most 4096 UTF-16 code units, cut between blocks; and since B1c Ctx::reply_rich sends a RichReply of Markdown and typed parts, in order, one message each: RichReply::image as an image message by link (messages/image-messages; the renderer's URL rule, then an absolute http(s) URL with a host and no user name or password), RichReply::suggestions as reply buttons up to 3 (messages/interactive-reply-buttons-messages) and a list from 4 to 10 (messages/interactive-list-messages), RichReply::products as a product carousel of 2 to 10 cards (catalogs/interactive-product-carousel-messages), each the client's own builder checked before the first send, the body of buttons, lists and carousels the text right before them, Suggestion::command sending a command's payload so the tap runs it, a failed send stopping the reply as an Error::Step. An image goes by link only, and Markdown's ![alt](url) stays textgap (the bot API, M5k)done / gap (M5k)
18MessagingHTML apps in the chat bubblePartial (Android only, undocumented)Noundocumented; the official in-chat UI is Flows (rows 60–63)——n/a — unofficial protocol
19MessagingEmoji reactionsYesYesmessages/reaction-messagesclient::messages::Messages::react; OutboundMessage::reaction; in a bot, Ctx::react (the received message)type reactiondone / done
20MessagingEdit a sent messageYesNono business-side edit in reference/whatsapp-business-phone-number/message-api.md; a user's edit arrives as an unsupported type (webhooks/reference/messages/edit.md)inbound only: webhooks::fields::MessageContent::Editinbound only, in message_received eventsn/a — unofficial protocol (sending an edit)
21MessagingDelete (revoke) a sent messageYesNono business-side delete in reference/whatsapp-business-phone-number/message-api.md; an inbound revoke for coexistence (webhooks/reference/messages/revoke.md)inbound only: webhooks::fields::MessageContent::Revoke; the inbox leaves a tombstone (core::store::ConversationStore::revoke)inbound only, recorded by the service's inboxn/a — unofficial protocol (sending a revoke)
22MessagingPin a messageYesNoin groups only (groups/groups-messaging); an inbound pin is an unsupported type (webhooks/reference/messages/unsupported.md)OutboundMessage::pin, unpin; client::groups::Groups::pin_messagenot in the send union (server_api_axum::api::messages::MessageType)done / gap (M5a)
23MessagingForward a messageYesNono forward call in reference/whatsapp-business-phone-number/message-api.md; a media id can be sent againa media id can be sent again (OutboundMessage::image_id and the other *_id constructors)a media id can be sent againn/a — unofficial protocol
24MessagingLocationYesYesmessages/location-messagesOutboundMessage::locationtype locationdone / done
25MessagingContact cardsYesYesmessages/contacts-messages (up to 257 cards)OutboundMessage::contactstype contactsdone / done
26MessagingPollsYesNoan unsupported inbound type (webhooks/reference/messages/unsupported.md)inbound only: MessageContent::Unsupportedinbound onlyn/a — unofficial protocol
27MessagingEvent invitationsYes (groups)Nonone in the mirror——n/a — unofficial protocol
28MessagingGroup invite cardYesNoan inbound group_invite is unsupported (webhooks/reference/messages/unsupported.md); an invite link goes out as a utility template from Meta's template library, or as text or a CTA URL (groups/reference)the link: client::groups::Group::invite_link (row 79)—n/a — unofficial protocol (the card; the link is row 79)
29MessagingAsk a user for their phone numberYesNo"request contact info" for users who write by username (business-scoped-user-ids)client::messages::Interactive::RequestContactInfonot in the send uniondone / gap (M5a)
30MessagingPost a statusYesNono status endpoint anywhere in the mirror——n/a — unofficial protocol
31MessagingMark as readYesYesmessages/mark-message-as-readMessages::mark_readPOST /v1/numbers/{pn}/messages/{message_id}/read (server_api_axum::api::messages::mark_read, over server_core::messages::Messages::mark_read)done / done
32MessagingTyping indicatorYesPartial (only with a read receipt, which is how the Cloud API works)typing-indicators.mdMessages::mark_read_with_typing_indicatortyping_indicator on the read routedone / done
33MessagingDelivery statuses (sent, delivered, read, played, failed; pricing, conversation, errors)YesYes (adds the conversation id and the error)webhooks/reference/messages/status.md, pricing.mdwebhooks::WebhookEvent::StatusUpdated; webhooks::fields::MessageStatus (played included); webhooks::fields::Pricingstatus_updated events on GET /v1/events (server_core::events::TENANT_EVENT_TYPES)done / done
34MessagingOpaque callback data on a send——biz_opaque_callback_data, at most 512 characters (Meta's changelog), echoed in status webhooks (webhooks/reference/messages/status.md); the message reference's schema (reference/whatsapp-business-phone-number/message-api.md) does not list itOutboundMessage::callback_datacallback_datadone / done
35MessagingSend to a groupYes (groups)Noreference/whatsapp-business-phone-number/message-api.md (recipient_type group)core::recipient::Recipient::Groupa {"group_id"} recipientdone / done
36MessagingSend by BSUID or parent BSUIDPartial (LID addresses)Nobusiness-scoped-user-idscore::recipient::Recipient::Usera {"user_id"} recipientdone / done
37MessagingDirect Send (beta: category, TTL, configuration)——direct-send, direct-send/api-referenceOutboundMessage::category, ttl_seconds, direct_send_template_namerefused today, 422 unsupported_message_type (design §4.3)done / gap (M5a)
38MessagingPayment messages (order details, order status)——payments/payments-in/orderdetailstemplate.md, payments/payments-in/orderstatustemplate.md, payments/payments-br/orders.mdIndia typed (P1a): client::messages::Interactive::OrderDetails and client::messages::Interactive::OrderStatus (client::payments::OrderDetails, client::payments::OrderStatus: UPI intent links, payment links, payment gateways and the older payment_configuration, every documented rule checked before the request, amounts as client::payments::Amount); Brazil typed (P1b): BRL orders (client::payments::OrderDetailsParameters::brl, client::payments::OrderDetailsParameters::brl_simplified, the order optional, a reference_id of 60, payment_type br) paid by Pix, payment link, Boleto or one-click card (client::payments::PixDynamicCode, client::payments::OffsiteCardPay), and order_status with a payment update (client::payments::OrderStatus::brl, client::payments::PaymentUpdate; pending allowed), each currency checked by its own pages' rules; the order templates' order_details button and order_status component (P1c) only through client::messages::MessageContent::Raw— (a Graph passthrough stays a non-goal: typed routes come with payments)partial / gap (P2)

C. Media ​

#AreaCapabilityZaileys (Web)Zaileys (Cloud)Meta Cloud APILibraryServiceStatus
39MediaImage, video, document (by id or link; caption, filename)YesYesmessages/image-messages, messages/video-messages, messages/document-messagesclient::messages::Image, Video, Documenttypes image, video, document by id or https linkdone / done
40MediaStickersYesYesmessages/sticker-messagesclient::messages::Stickertype stickerdone / done
41MediaAudio fileYesYesmessages/audio-messagesclient::messages::Audiotype audiodone / done
42MediaVoice noteYesPartial (arrives as audio)messages/audio-messages (voice messages, Ogg with Opus)Audio::voiceaudio.voicedone / done
43MediaRound video notes, view-once, albums (GIF playback: row 154)YesNo, or sent as plain mediaan inbound gif is unsupported (webhooks/reference/messages/unsupported.md); view-once is unsupported in groups and turned off for coexistence (groups, embedded-signup/onboarding-business-app-users); none of these in reference/whatsapp-business-phone-number/message-api.md; the nearest to an album is a media carousel (row 51)——n/a — unofficial protocol (a GIF as a template header is row 154)
44MediaUpload, type and size checkedYes (URL, file or buffer)Yesreference/whatsapp-business-phone-number/media-upload-api.mdclient::media::Media::uploadPOST /v1/numbers/{pn}/media (server_http::api::media::upload_media, over server_core::media::Media::upload, the checks server_core::media::Upload::new)done / done
45MediaDownload received mediaYesYes (by key)reference/media/media-api.md, reference/media/media-download-api.mdMedia::download (SHA-256 checked; of media hosts, the token goes only to Graph and lookaside.fbsbx.com, and otherwise to one GET on api.facebook.com, roadmap L10a)GET /v1/numbers/{pn}/media/{media_id} (server_http::api::media::download_media, over server_core::media::Media::download: whole, or the core's server_core::media::MediaStream, one chunk held back); whether Meta serves a received media id under the route's phone_number_id check is unverified (OPEN_QUESTIONS #43, decided: exempt the ids the service recorded as received, M2e)done / partial (M2e)
46MediaDelete media——reference/media/media-api.mdMedia::deleteDELETE /v1/numbers/{pn}/media/{media_id} (server_http::api::media::delete_media, over server_core::media::Media::delete)done / done
47MediaResumable upload (template header handles, profile pictures)——Graph's Resumable Upload API, documented outside the WhatsApp docs; the WhatsApp pages that rely on it: templates/template-media.md (header handles), reference/whatsapp-business-profile/whatsapp-business-profile-node-api.md (pictures)Media::resumable_uploadnot exposeddone / gap (M5c1)
48MediaConverting media (voice to Opus, stickers from images, resizing)YesYes (runs locally: ffmpeg, sharp)none (client side); accepted formats: business-phone-numbers/media (Ogg "OPUS codecs only", "mono input only"; images "8-bit, RGB or RGBA", JPEG and PNG, 5 MB), messages/audio-messages, messages/sticker-messages (WebP, 100 KB static, 500 KB animated; no dimensions)partial: through the port core::media::MediaConverter, voice to Opus (core::media::TargetFormat::OggOpus: mono Ogg Opus, uploaded as audio/ogg; L19), a PNG or JPEG into a static sticker (core::media::TargetFormat::StaticSticker: a static WebP within 100 000 bytes, uploaded as image/webp) or under the image limit (core::media::TargetFormat::Jpeg, core::media::TargetFormat::Png: 8-bit, within 5 000 000 bytes; L19b), each of which client::media::validate_upload takes (client::media::Media::upload_converted), implemented by adapters::media::FfmpegConverter (feature ffmpeg, off by default: an ffmpeg the integrator installs, run with fixed arguments, stdin and stdout only, its input protocols and formats restricted (an image: PNG or JPEG, by its first bytes and by ffmpeg's own detection), bounded in input, pixels, output and time, an image brought under its limit by a fixed ladder of sizes and qualities; failures core::error::ConversionError); animated stickers (Meta's 500 KB WebP) are not made, a GIF or an animated PNG is refused (L19c)gap: the image carries no ffmpeg (design §7, M4); configuring a converter is M5c1'spartial / gap (M5c1)
154MediaA GIF (an animated mp4) as a template's media headerYes (GIF playback, row 43)No, or sent as plain media (row 43)Marketing Messages API only: marketing-messages/features.md (an animated GIF header), templates/components.md (header format GIF: an mp4 of at most 3.5 MB); no page shows the send-time header parameter for a GIF (re-read for L13 on 2026-11-01: templates/components.md, templates/template-media.md, marketing-messages/features.md, marketing-messages/send-marketing-messages.md, the MM API and Messages API references)the definition: client::templates::HeaderFormat::Gif; the send-time parameter is not typed, for want of a page showing it (client::templates::Parameter::Other would carry it verbatim; unverified); sent through client::marketing::Marketing::sendcreating the template takes Meta's JSON (POST /v1/wabas/{waba_id}/templates); Marketing Messages API sends are not exposed (row 136)partial / gap (M5h)

D. Interactive messages and Flows ​

#AreaCapabilityZaileys (Web)Zaileys (Cloud)Meta Cloud APILibraryServiceStatus
49InteractiveReply buttons (at most 3 on the Cloud API), header, footerYes (up to 10)Partial (3)messages/interactive-reply-buttons-messages (up to three)client::messages::ReplyButtons (1–3 checked)interactive buttondone / done
50InteractiveA media header on buttonsYesNoreference/whatsapp-business-phone-number/message-api.md (header: text, image, video, document)client::messages::Headerheader text, image, video, documentdone / done
51InteractiveCarousels (interactive media carousel, product carousel, template carousels)YesNomessages/interactive-media-carousel-messages (2–10 cards), catalogs/interactive-product-carousel-messages, templates/marketing-templates/media-card-carousel-templates.mdclient::messages::MediaCarousel; client::messages::ProductCarousel; client::templates::TemplateMessage::carouseltemplate carousels pass through; interactive carousels are not in the send uniondone / partial (M5a)
52InteractiveURL button (CTA URL)YesPartial (one button)messages/interactive-cta-url-messagesclient::messages::CtaUrlinteractive cta_urldone / done
53InteractiveCopy-code and call buttonsYesNocopy code: templates/authentication-templates/copy-code-button-authentication-templates.md, templates/marketing-templates/coupon-templates.md; calls: calling/call-button-messages-deep-linksTemplateMessage::copy_code_button; client::templates::Button::phone_number, Button::voice_call; client::messages::Interactive::VoiceCalltemplate buttons pass through; interactive voice_call is not in the send uniondone / partial (M5a)
54InteractiveLists (at most 10 rows)YesYesmessages/interactive-list-messages (10 sections, 10 rows in all)client::messages::ListMessageinteractive listdone / done
55InteractiveButton and list taps receivedYesYeswebhooks/reference/messages/interactive.md, webhooks/reference/messages/button.mdwebhooks::fields::InteractiveReply::ButtonReply, ListReply; webhooks::fields::MessageContent::Buttonin message_received eventsdone / done
56InteractiveLocation requestYes (location button)Nomessages/location-request-messagesclient::messages::Interactive::LocationRequestnot in the send uniondone / gap (M5a)
57InteractiveAddress requestYes (address button)Yes (sendAddressRequest)messages/address-messagesclient::messages::Interactive::Addressnot in the send uniondone / gap (M5a)
58InteractiveReminder buttons, bottom sheets, countdown offersYesNothe countdown offer is the limited-time-offer template (templates/marketing-templates/limited-time-offer-templates.md: an expiration timer on the offer code), row 99; no reminder buttons or bottom sheetsthe countdown: TemplateMessage::limited_time_offer (row 99)the template passes throughn/a — unofficial protocol (reminder buttons, bottom sheets; the countdown offer is row 99)
59InteractiveCall permission request——calling/user-call-permissions; reference/whatsapp-business-phone-number/message-api.md (call_permission_request)client::messages::CallPermissionRequest; replies as InteractiveReply::CallPermissionReplynot in the send union; replies arrive in message_received eventsdone / partial (M5a)
60FlowsSend a Flow, receive its answerNoYes (flows.send, a flow-response event)flows/guides/sendingaflow, flows/guides/flowswebhooksOutboundMessage::flow; client::messages::FlowMessage; answers as InteractiveReply::NfmReply; webhooks::WebhookEvent::FlowUpdatedanswers in message_received events, flow_updated events; sending a Flow is not in the send uniondone / partial (M5a)
61FlowsManage Flows (create, upload the JSON, preview, publish, deprecate, delete, assets)NoPartial (list only)flows/guides/flowsapiclient::flows::Flows::create, list; client::flows::Flow::update, upload_flow_json, publish, deprecate, deletenot exposeddone / gap (M5d)
62FlowsThe data endpoint: decrypt requests, seal responses, check signatures, decrypt uploaded media; the business public key——flows/guides/implementingyourflowendpoint, flows/guides/whatsapp-business-encryption; reference/whatsapp-business-phone-number/business-encryption-api.mdclient::flows::endpoint::FlowEndpointKey::decrypt_request; client::flows::endpoint::ResponseSealer; client::flows::BusinessEncryption::set_public_keynot exposeddone / gap (M5d)
63FlowsA typed Flow JSON builder——flows/guides/flowjson, flows/guides/components, flows/guides/media_upload, flows/reference/error-codesclient::flows::flow_json::FlowJson::builder, FlowJson::validate; client::flows::flow_json::Component; client::flows::CreateFlow::flow_json_typed; client::flows::Flow::upload_flow_json_typedn/a (callers send Flow JSON as JSON)done / n/a

E. Profile, chats, contacts, calls ​

#AreaCapabilityZaileys (Web)Zaileys (Cloud)Meta Cloud APILibraryServiceStatus
64ProfileOwn profile (on the Cloud API, the business profile: about, address, description, email, websites, vertical, picture)Yes (name, about, picture)Yes (business profile)business-profiles; reference/whatsapp-business-phone-number/whatsapp-business-profile-api.md, reference/whatsapp-business-profile/whatsapp-business-profile-node-api.mdclient::business_profile::BusinessProfile::get, update; the picture by upload handle (ProfileUpdate::profile_picture_handle)GET and PATCH /v1/numbers/{pn}/profile (server_api_axum::api::numbers::get_profile, update_profile, over server_core::numbers::Numbers::profile, update_profile), without the picturedone / partial (M5c2)
65ProfileDisplay name change——display-namesclient::phone_numbers::PhoneNumber::request_display_name_changenot exposeddone / gap (M5c2)
66ProfileBusiness username: adopt or change it, read it, reserved names, delete it; changes by webhook——business-scoped-user-ids (§ Business usernames: POST, GET and DELETE /{Phone-Number-ID}/username, username_suggestions); reference/whatsapp-business-phone-number/whatsapp-business-account-phone-number-api.mdclient::phone_numbers::PhoneNumber::set_username (the format checked first, transfer_action typed), username, username_suggestions, delete_username; the webhook webhooks::WebhookEvent::BusinessUsernameUpdatedbusiness_username_updated events; no setterdone / partial (M5c3)
67ChatsArchive, pin, mute, star, clear chatsYesNononethe inbox tracks unread only (inbox::Inbox::mark_read)—n/a — unofficial protocol
68ContactsCheck that a number is on WhatsAppYesNonone: the contacts check belonged to the retired On-Premises API (Meta's changelog)——n/a — unofficial protocol
69ContactsSave, rename, look up contactsYesNothe coexistence contacts sync (webhooks/reference/smb_app_state_sync.md)the sync requested by onboarding after a coexistence signup (row 124; L11d); typed webhooks::WebhookEvent::AppStateSynced; the port stores and pages them per number (core::store::ConversationStore::put_contact, remove_contact, contacts, core::store::StoredContact: L5), and inbox::InboxSink records every field of an add and a remove (under inbox::synced_contact_key, switch inbox::RecordingSwitches::contacts), read back with inbox::Inbox::contacts and contact (coverage row 21; L8)app_state_synced events; the inbox records no synced contact (server_core::inbound::INBOX_RECORDING: the library's switches with inbox::RecordingSwitches::contacts off, decided 2026-11-01) until M3b turns recording on with the routes that read themdone / partial (M3b)
70ContactsPrivacy settingsYesNonone for an account's privacy; the business number's search visibility is row 129——n/a — unofficial protocol
71ContactsBlock, unblock, list blocked usersYesYesblock-users; reference/whatsapp-business-phone-number/block-api.mdclient::block_users::BlockUsers::block, unblock, list (phone number or BSUID)not exposeddone / gap (M5g)
72ContactsPresence (online, typing, recording)YesNotyping only (row 32)——n/a — unofficial protocol
73ContactsLID and username lookupsYesNo (null)no lookup endpoint: identities arrive on webhooks, changes as user_id_update (business-scoped-user-ids)the BSUID and username on every event; webhooks::WebhookEvent::UserIdChangeduser_id_changed eventsdone / done
74CallsIncoming calls (accept, reject, terminate)YesNocalling/user-initiated-calls; reference/whatsapp-business-phone-number/calling-api.mdwebhooks::WebhookEvent::CallUpdated; client::calling::Calling::pre_accept, accept, reject, terminatecall_updated and call_status_updated events; the actions are not exposeddone / partial (M5e)
75CallsRejecting calls automatically, call restrictionsYes (auto-reject with an allow list)Nocalling/call-settingsclient::calling::CallingSettings (call hours, callback permission, voicemail, restrictions)not exposeddone / gap (M5e)
151ContactsMeta's contact book (a user's phone number kept with their BSUID across the portfolio): delete an entry——business-scoped-user-ids (§ Contact book: DELETE /{Phone-Number-ID}/contact_book)client::phone_numbers::PhoneNumber::delete_contact_book_entry: an erasure at Meta, exposed as an explicit call that nothing else in the library makes (a test scans the sources); malformed and parent BSUIDs are refused before any request—done / gap (M5c3)

F. Groups and channels ​

#AreaCapabilityZaileys (Web)Zaileys (Cloud)Meta Cloud APILibraryServiceStatus
76GroupsCreate, read, list, update, set the picture, deleteYesNogroups; reference/whatsapp-business-phone-number/groups-management-api.md, reference/groups/groups-query-api.mdclient::groups::Groups::create, list; client::groups::Group::info, update, set_picture, deletenot exposed (sends to a group work, row 35)done / gap (M5f)
77GroupsAdd and remove membersYesNoreference/groups/groups-participants-api.mdGroup::add_participants, remove_participants (adding kept with a warning, OPEN_QUESTIONS #23)not exposeddone / gap (M5f)
78GroupsPromote and demote admins, leave a groupYesNono promote, demote or leave call in the groups reference or guides (groups/reference); a participant leaving arrives as a webhook——n/a — unofficial protocol
79GroupsInvite links, join requestsYesNoreference/groups/groups-invite-link-api.md, reference/groups/groups-join-requests-api.mdGroup::invite_link, reset_invite_link, delete_invite_link; join_requests, approve_join_requests, reject_join_requestsnot exposeddone / gap (M5f)
80GroupsGroup events (joins, leaves, settings, lifecycle)YesNogroups/webhooks; fields group_lifecycle_update, group_participants_update, group_settings_update, group_status_updatewebhooks::WebhookEvent::GroupUpdatedgroup_updated eventsdone / done
81GroupsGroup analytics——analyticsclient::analytics::Analytics::groupsnot exposeddone / gap (M5f)
82GroupsCommunitiesYesNonone anywhere in the mirror——n/a — unofficial protocol
83GroupsChannels (newsletters)YesNonone anywhere in the mirror——n/a — unofficial protocol

G. Bots and automation (framework features) ​

#AreaCapabilityZaileys (Web)Zaileys (Cloud)Meta Cloud APILibraryServiceStatus
84BotsTyped message eventsYesPartial (text, media, location, contacts, reactions, button and list taps)webhooks/reference/messages.md and its pages, one per typewebhooks::WebhookEvent (34 variants, Unknown and Unparsed included); webhooks::fields::MessageContent; in a bot, listeners by message type or event kind: Listen::MessageType, Listen::Event (a kind not in WebhookEvent::KINDS fails the build)every reviewed type on GET /v1/events (server_core::events::TENANT_EVENT_TYPES, 28 types); the standby, handover and click types in M2d (design D25)done / partial (M2d)
85BotsCommands: prefixes, arguments and flags, aliases, subcommands, guards (group, private, admin), cooldowns, generated help, error eventsYesNo (parse by hand)Meta shows a command menu, but dispatch is ours: business-phone-numbers/conversational-components; reference/whatsapp-business-account/conversational-automation-api.mdin meta-whatsapp-bot: prefixes (PrefixParser, BotBuilder::prefixes; names case-insensitive by default), aliases (Command::alias), arguments split on whitespace with quoted strings kept whole (Args::parse), image and video captions (BotBuilder::commands_from_captions), reply buttons, list rows and quick-reply buttons by payload (Command::payload), usage hints and metadata (Command::usage, Command::metadata), guards (Command::private_only, Command::group_only, Command::owner_only: Meta's group info lists participants by wa_id alone, with no role, so the bot's owners stand in for Zaileys' group admins), per-user cooldowns (Command::cooldown, KvCooldowns on KvStore), a generated help (BotBuilder::help_command, CategoryHelp), an unknown-command hook (BotBuilder::unknown_command), error events (ErrorHandler, BotBuilder::errors), Meta's command menu (Bot::sync_command_menu, over client::phone_numbers::PhoneNumber::configure_conversational_automation), subcommands one level deep with their own guards, usage, help line and cooldown (Command::subcommand, Invocation::subcommand; B1b), and flags beside the positional arguments for a command that declares one (Command::flag, Args::has_flag, Args::flag, Args::flags; --name or --name=value, -- ends them, a quoted one is an argument, an undeclared one refuses the command with its usage, Refusal::UnknownFlag, unless Command::pass_unknown_flags; a command that declares none reads every word as an argument)gap (the bot API, M5k)done / gap (M5k)
86BotsMiddleware: ordered, next(), short-circuit, wraps the handlerYesNonone (ours to build)Middleware in meta-whatsapp-bot, run in registration order (BotBuilder::middleware, Registrar::middleware) with Next::run: one that does not call it stops the event, and what follows the call runs after the handler (Logging times it); built in: Logging, MarkRead. Unlike Zaileys', it runs for every event, after the ban and the command match and before the command's guardsgap (M5k)done / gap (M5k)
87BotsPlugins: setup and unload hooks; loaded from a folder with hot reloadYesNononecompile-time plugins in meta-whatsapp-bot: Plugin::setup registers commands, middleware and listeners through a Registrar, Plugin::on_unload runs at Bot::unload, Plugin::category names the help section; added with BotBuilder::plugin. Loading from a folder and hot reload are not offered, by design (D28): dynamic loading of Rust code is neither idiomatic nor safe, so a plugin is a crategap (M5k)done / gap (M5k)
88BotsSender allow and deny lists (owners, banned users)YesYesnone (ours; row 71 is Meta's block list)in meta-whatsapp-bot: an AccessPolicy (the default AccessList: owners and bans by BSUID, AccessList::owner, AccessList::ban, or by phone number, AccessList::owner_phone, AccessList::ban_phone), set with BotBuilder::access; a banned sender's message stops before the command match and the middleware (Refusal::Banned), and owners pass Command::owner_onlygap (M5k)done / gap (M5k)
89BotsBroadcast with pacing (progress, retries)Yes (5 a second by default)No (throws)limits to respect: throughput.md (80 messages a second per number by default), the pair limit (131056, support/error-codes.md), templates/marketing-templates/per-user-limits.md, messaging-limitsin meta-whatsapp-bot: Broadcast (one message, or one per recipient with BroadcastBuilder::compose), each send after a slot of the number's Pacer, a swappable RateLimiter (default TokenBucket: per number, in this process, evenly spaced at Rate::DEFAULT, 80 a second, other numbers at Rate::HIGHER_THROUGHPUT or any Rate::per_second with TokenBucket::rate_for; every one-second window of 10,000 sends checked under concurrency, a slow-down and its recovery); each person once by default (BroadcastBuilder::dedupe, SendOutcome::Duplicate); progress and cancel (BroadcastHandle::progress, BroadcastHandle::cancel, the slots waited for given back, RateLimiter::release); a report per recipient (BroadcastReport, SendOutcome, RecipientReport::last_error, BroadcastEnd), kept or streamed to a ReportSink as each settles (BroadcastBuilder::report_to); retries decided by a BroadcastPolicy on ErrorKind and the code (default Backoff: the pair limit defers only that recipient on Meta's 4^X schedule, throughput retries, a number in maintenance is retried every 20 s (Backoff::MAINTENANCE_RETRY), the per-user marketing limit is reported, number-wide kinds stop the run (Backoff::STOPS, the spam limit among them) and so do a template's own refusals when everyone gets the same message (Backoff::CONTENT_STOPS); every delay at most Backoff::max_delay), only when Error::may_resend holds (the client's rule too), and through the pacer only (BroadcastBuilder::client turns the client's replays off, Client::with_retry); throttling slows the number down (SlowDownRule, default ThrottlingErrors) and a number's rate changes while running (TokenBucket::set_rate), Meta's throughput upgrade included when asked (ThroughputUpgrades, BotBuilder::follow_throughput_upgrades: a THROUGHPUT_UPGRADE raises that number to Rate::HIGHER_THROUGHPUT through RateLimiter::raise_rate, never lowering it: in memory with a TokenBucket, for every replica with a KvRateLimiter; the number found by WABA and display number, NumberDirectory). One budget per process by default; every replica on one KvStore books from one with KvRateLimiter (opt-in, B2b: a record per number under wa.bot.pacer, PACER_NAMESPACE, booked by compare-and-swap with bounded attempts, failing closed when the store does, the replicas' clocks assumed synchronized); a run does not survive a restart, a job does (row 90, KvJobs); Meta's daily messaging limit is not countedgap (M5k)done / gap (M5k)
90BotsScheduled messages: send at a time, cancel, survive restarts, retryYesNo (fails when due)Meta's WABA campaign schedules, reference only (reference/whatsapp-business-account/schedules-api.md; its audience_id is explained nowhere in the mirror)Meta's own campaign schedules are wrapped (client::waba::Waba::schedules, client::waba::Waba::create_schedule: a template to an audience at a Unix time, kept by Meta, so it survives our restarts; never replayed after a timeout; L10b), with no cancel or retry documented; and in meta-whatsapp-bot (B3; design D29), durable jobs, a typed store on KvStore (KvJobs: KvJobs::schedule at a time, KvJobs::cancel, KvJobs::rerun), one recipient or many (each person once), run by any number of JobRunners sharing the store (JobRunner::tick, JobRunner::run_until): each due job claimed by compare-and-swap under a lease that a crashed runner's successor waits out (JobRunnerBuilder::lease), sent through a paced Broadcast, each recipient marked before its send so a restart, a crash or a second runner sends nobody twice, and a send whose outcome is unknown is never resent (JobSummary::unknown); a pass that leaves recipients provably unsent is retried with backoff, then stopped (JobRunnerBuilder::retries, JobState::Stopped); a 131049 status webhook for a message a job sent marks that recipient for 24 hours, and a re-run skips them (KvJobs::observe, JobFeedback, JobSummary::limited). Schedules are one-off (no recurrence)gap (M5k)done / gap (M5k)
91BotsAuto-delete stored messages (by age, a cap per chat)YesNo ("not yet" on the Cloud API)none (local data)done: by age, core::store::ConversationStore::purge_before (one number or all) and apply_retention with a core::store::Retention set per store (adapters::store::MemoryConversationStore::with_retention, PostgresConversationStore::with_retention, SqliteConversationStore::with_retention, RedisConversationStore::with_retention; design D10); a cap per chat, core::store::ConversationStore::trim_conversations (each conversation's newest rows kept, the summary deleted when its latest message goes, an append at the same time never lost; B4); both applied by the bot framework once or on its Timer (RetentionPolicy::apply, RetentionSweeper::run, in meta-whatsapp-bot; B4); and erasure of a person on one number, core::store::ConversationStore::erase_all over core::store::ConversationStore::identities, their group messages redacted or deleted (core::store::ErasureMode; L5)gap: the outbox purges after 7 days (server_core::events::DEFAULT_OUTBOX_RETENTION); the inbox keeps everything (retention per store: M2a; erasure of a customer: M2f; the bot's auto-delete over HTTP: M5k)done / gap (M2a, M2f, M5k)
92BotsThrottling our own typing indicators and group operationsYesYes, per the summary of its configuration page (doubtful for group operations, which Zaileys does not offer on the Cloud API; unverified)none (ours)in meta-whatsapp-bot: BotBuilder::pacer puts the bot's outbound in a PacedOutbound, so every read receipt and typing indicator (MarkRead), reply and refusal waits for a slot of its number's Pacer, the budget its broadcasts share, and a throttled one slows the number down and is retried through the pacer (PacedOutbound::retry; PacedOutbound over a ClientOutbound does the same outside a bot, given a client with RetryPolicy::NONE); a handler reaches the pacer with Ctx::pacer; the client's group operations go through PacedGroups (client.groups(number): create, list, pin, unpin) and PacedGroup (client.group(id): info, settings, picture, delete, invite link, join requests, participants), each after a slot of the number; the paging streams are not wrapped (page by page instead). Meta documents no rate for group operations: pacing them in the number's budget is a choicegap (M5k)done / gap (M5k)

H. Templates, commerce, numbers ​

#AreaCapabilityZaileys (Web)Zaileys (Cloud)Meta Cloud APILibraryServiceStatus
93TemplatesSend approved templates (named or positional parameters, media headers, buttons)NoYestemplates/overview.md, templates/components.md; reference/whatsapp-business-phone-number/message-api.mdclient::templates::TemplateMessage; OutboundMessage::templatetype templatedone / done
94TemplatesCreate, list, get, deleteNoYestemplates/template-management.md; reference/whatsapp-business-account/message-template-api.mdclient::templates::Templates::create, list, get, delete_by_name, delete_by_id, delete_by_ids/v1/wabas/{waba_id}/templates: list, get, create, delete (server_api_axum::api::templates, over server_core::templates::Templates)done / done
95TemplatesEdit a templateNoNoreference/whatsapp-business-account/message-template-api.md (POST /{TEMPLATE_ID})Templates::editnot exposeddone / gap (M5b)
96TemplatesTemplate library, migration, comparison, unpausing——templates/template-library.md, templates/template-migration.md, templates/template-comparison.md, templates/template-pausing.mdTemplates::library, create_from_library, migrate_from, compare, unpausenot exposeddone / gap (M5b)
97TemplatesReview, quality, category and component eventsNoYes (status only)webhooks/reference/message_template_status_update.md, webhooks/reference/message_template_quality_update.md, webhooks/reference/template_category_update.md, webhooks/reference/message_template_components_update.mdwebhooks::WebhookEvent::TemplateStatusUpdated, TemplateQualityUpdated, TemplateCategoryUpdated, TemplateComponentsUpdated, TemplateCategoryMisuseDetectedthe five template_* event typesdone / done
98TemplatesAuthentication templates (copy code, one-tap, zero-tap, previews, bulk upsert) and an OTP serviceNoPartial (copy code sent by hand)templates/authentication-templates/authentication-templates.md and its pagesclient::authentication::Authentication::create, previews, upsert; client::authentication::OtpService::issue, verifyM3c (/v1/otp/*, POST /v1/wabas/{waba_id}/templates/authentication)done / gap (M3c)
99TemplatesMarketing and utility template kinds: coupon, limited-time offer, media-card and product-card carousels, location, call permission, catalog, MPM, SPM, product header——templates/marketing-templates.md and its pages, templates/utility-templates/utility-templates.md and its pages, catalogs/product-card-carousel-template-messagesclient::templates::TemplateComponent::limited_time_offer, carousel, header_location, header_product, call_permission_request; client::templates::Button::catalog, mpm, spm; client::templates::Parameter::coupon_codecreate and send take Meta's JSON (unverified per kind: no service test creates each one, M5b); what the library cannot carry is refused (coverage row 33) and counted in its own row: payment buttons (rows 38, 141), optimization_spec (row 137); app_deep_link is carried (row 155); the pre-v21 one-tap fields are superseded on the Graph API version we targetdone / partial (M5b)
100TemplatesTime to live, the tap-target title override——templates/time-to-live.md, templates/tap-target-url-title-override.mdclient::templates::TemplateDefinition::message_send_ttl_seconds; TemplateMessage::tap_targetthrough Meta's JSON (unverified: no test covers these keys through the service)done / partial (M5b)
101TemplatesArchiving and unarchiving templates——templates/template-archival.md (POST /{WABA_ID}/message_templates/archive and …/unarchive, up to 100 template ids each, answering with the ids done and a reason for each that failed; § Sources: read on 2026-10-26), templates/template-management.md (§ Archive and unarchive templates); archival and unarchival arrive as ARCHIVED and UNARCHIVED in webhooks/reference/message_template_status_update.mdclient::templates::Templates::archive, unarchive (ArchiveResult, UnarchiveResult; 1 to 100 ids, each checked as digits before any request; not replayed on timeouts; sent to the Graph endpoint with its version, where the page prints https://api.facebook.com/ with none; Graph routes the edge, probed on 2026-10-26 with a template id that does not exist; a real archive not yet run live); the status events are typed (webhooks::WebhookEvent::TemplateStatusUpdated)the status events only (template_status_updated)done / gap (M5b)
102CommerceCommerce settings (cart, catalog visibility)——catalogs/set-commerce-settings; reference/whatsapp-business-phone-number/commerce-settings-api.mdclient::commerce::Commerce::settings, update_settings, set_cart_enabled, set_catalog_visiblenot exposeddone / gap (M5g)
103CommerceList connected catalogs and their productsYesYes (commerce.catalogs(), commerce.products())catalogs/upload-inventory sends integrators to Meta's Catalog API, outside the WhatsApp docs; read there (§ Sources, 2026-10-27): GET /{WABA_ID}/product_catalogs, GET /{product_catalog_id} and its products and product_sets edges, GET /{product_set_id} and its products edge, GET /{product_item_id}client::waba::Waba::product_catalogs (the WABA's connected catalog); client::commerce::Catalog::get, products, products_stream, product_sets, product_sets_stream; client::commerce::ProductSet::get, products, products_stream; client::commerce::Product::get (client::commerce::ListProducts, ListSetProducts, ListProductSets carry the cursors and the edges' documented filters; the streams follow paging.cursors.after until a page without next; every id in a path checked as digits before any request). Reads only: catalog writes, and connecting a catalog to a WABA, are not wrapped (roadmap L17)not exposeddone / gap (M5g)
104CommerceProduct messages (single, multi, catalog, product carousel)YesYescatalogs/single-product-messages, catalogs/multi-product-messages, catalogs/catalog-messagesOutboundMessage::product, product_list, catalognot in the send uniondone / gap (M5a)
105CommerceOrder eventsNoYeswebhooks/reference/messages/order.mdwebhooks::fields::MessageContent::Orderin message_received eventsdone / done
106MarketingClick-to-chat QR codes and short linksNoYes (create, list, delete)qr-codes.md; reference/whatsapp-business-phone-number/whatsapp-business-qr-code-api.md, reference/whatsapp-business-phone-number/whatsapp-business-qr-code-management-api.mdclient::qr_codes::QrCodes::create, update, get, list, deletenot exposeddone / gap (M5g)
107AnalyticsMessaging, conversation, pricing, template, call and group analyticsNoYes (conversations, messages)analyticsclient::analytics::Analytics::messaging, conversation, pricing, calls, template, template_group, groups; enable_template_insightsnot exposeddone / gap (M5g)
108NumbersRegistration: request and verify a code, register, deregister, list numbersNoYesreference/whatsapp-business-phone-number/phone-number-registration.md, reference/whatsapp-business-phone-number/phone-number-verification-request-code-api.md, reference/whatsapp-business-phone-number/verify-code-api.md, reference/whatsapp-business-phone-number/phone-number-deregister-api.md, reference/whatsapp-business-account/phone-number-management-api.mdclient::phone_numbers::PhoneNumber::request_code, verify_code, register, deregister; client::waba::Waba::phone_numbersGET /v1/numbers, GET /v1/numbers/{pn} (server_api_axum::api::numbers::list_numbers, get_number, over server_core::numbers::Numbers::details); registration in M3done / partial (M3d)
109NumbersNumber health: quality rating, throughput, messaging limit tierNoYes (info())reference/whatsapp-business-phone-number/whatsapp-business-account-phone-number-api.md; throughput.mdclient::phone_numbers::PhoneNumberInfo (quality_rating, throughput, messaging_limit_tier); webhooks::WebhookEvent::PhoneNumberQualityUpdatedGET /v1/numbers/{pn} reports the quality rating and throughput, not the messaging limit tier (server_core::numbers::NUMBER_FIELDS); phone_number_quality_updated events (limit changes included)done / partial (M5c3)
153TemplatesTemplate groups: create, read, update, delete——templates/template-groups.md (POST /{WABA_ID}/template_groups; GET, POST and DELETE /{TEMPLATE_GROUP_ID}; § Sources: read on 2026-10-26); Meta's changelog (2025-02-14) also lists GET /{WABA_ID}/template_groups, which no page documents (its Graph API reference page shows no operation)client::templates::Templates::create_group, get_group, update_group, delete_group (TemplateGroupCreate, TemplateGroupUpdate, TemplateGroup; a group id checked as digits, template ids as integers, names and descriptions against their limits, before any request; the POSTs not replayed on timeouts), and their analytics (Analytics::template_group); not wrapped, because no page documents them: the list of a WABA's groups, and the pages after the first of a group's templatesnot exposedpartial / gap (M5b)
155TemplatesAndroid app deep links on marketing templates' URL buttons——marketing-messages/deep-links.md (a URL button's app_deep_link: the Android deep link, a fallback URL, the Meta app id)client::templates::Button::Url (its app_deep_link), client::templates::AppDeepLink, client::templates::Button::url_with_app_deep_link; both strings checked non-empty before any request; the page's example round-tripspassed through to Meta unchanged when a template is created (POST /v1/wabas/{waba_id}/templates; a test sends the page's example)done / done

I. Data and production ​

#AreaCapabilityZaileys (Web)Zaileys (Cloud)Meta Cloud APILibraryServiceStatus
110StorageAuth storeYes (plain JSON in files, SQLite, Postgres, Redis, Convex)No (a token only)none (ours)client::embedded_signup::TokenVault (AES-256-GCM, key rotation) on core::store::KvStore: adapters::store::MemoryKvStore, PostgresKvStore, SqliteKvStore, RedisKvStorePostgres and the vault; POST /v1/admin/vault/rotate (server_core::admin::Admin::rotate_vault)done / done
111StorageMessage store: backends, history, chat list, a single-message lookupYes (memory, SQLite, Postgres, Redis, Convex)Yesnone (ours)core::store::ConversationStore: adapters::store::MemoryConversationStore, PostgresConversationStore, SqliteConversationStore, RedisConversationStore (L8), each passing adapters::store::conversation_conformance; the lookup by message id, scoped to the business number (core::store::ConversationStore::message, L5); inbox::Inbox::conversations, history; no Convex adapter (a hosted database of Zaileys' JavaScript world; a ConversationStore of an integrator's runs the same suite)the service records its tenants' inbox since M1c (server_core::inbound::Pipeline, the core's webhook pipeline since S5e); its read routes come in M2adone / partial (M2a)
112StoragePluggable stores with conformance testsYes (custom interfaces)Yesnone (ours)ports in meta-whatsapp-core; executable suites adapters::store::conformance, conversation_conformancethe core's ports (server_core::store::RecordStore, IdempotencyRecords, LeaderLock, Janitor, SchemaMigrator; server_core::outbox::Outbox), bundled per database as server_core::backend::Backend with the library's KvStore and ConversationStore, implemented in memory and on Postgres (server_store_memory::MemoryBackend, PgBackend), with the contracts any backend must meet since S2 (the outbox's server_core::outbox::RouteGuard checked with each insert, server_core::model::BindOutcome::NoSuchTenant, leased turns, typed contention, byte order); and since S3 their executable suites in core, which both backends pass (server_core::conformance::run, feature conformance, over a factory of empty Backends: records, outbox, the cross-port invariants with the RouteGuard cases, capabilities, races), so a backend of an integrator's runs the same suites; and since S4 the service is composed from the bundle alone (server_http::state::AppState::from_backend, server::serve::serve_with; housekeeping is server_core::housekeeping::run over it, and the command line opens it through a server::serve::BackendFactory), so a backend of an integrator's is served with no change to the server; and since S2b every backend records a WABA's last unbinding from another tenant (server_core::model::WabaTombstone, read into server_core::model::WabaBinding) and a bind generation in server_core::model::BindingEpoch, which the suites hold them to (records, invariants, capabilities, webhooks; the Postgres one by an additive migration) (design D26)done / done
113ErrorsTyped errors with retry guidanceYesYessupport/error-codes.mdcore::Error, Error::in_step; core::ErrorKind, ErrorKind::ALL, is_retryablethe §5 error model as data on ErrorKind::as_str (server_core::error, over HTTP server_http::error), each code a constant generated from the §5.2 table, so a misspelled one does not compile (server_core::error::ErrorCode, S6)done / done
114OpsLogging without secrets or personal data; metricsPartial (a custom logger)Partialnone (ours)tracing; secrets kept out of Debug; webhook log redaction (webhooks::redact)request logs with the key id, Prometheus /metrics (server_http::api::ops::metrics)done / done
115OpsRuntimes and packagingYes (Node, Bun, Deno, Termux)YesnoneRust with tokio; the crates are not published yet (publishing to crates.io is the owner's: roadmap § Owner touchpoints)a binary today; the Docker image and the TypeScript client in M4n/a / gap (M4)
116OpsServer sizing guidanceYes (for WhatsApp Web)—support/load-testing.md——n/a — unofficial protocol (sizing for a WhatsApp Web session)
117ToolingAgent skills, LLM-readable docs, a docs MCP server, a project doctorYesYeswebhooks/overview (§ Fields), webhooks/create-webhook-endpoint, solution-providers/manage-webhooks; outside the WhatsApp docs, the Graph API reference's app/subscriptions and webhooks/subscriptions-edge (§ Sources)consumer skills under skills/ (npx skills add vaam-apps/meta-whatsapp-rs); docs/public/llms.txt, served at the docs site's root and checked by tests/llms_txt.rs; the docs MCP server, tools/docs-mcp (just docs-mcp: the tools list, search and read, a page or one section by its heading, over MCP's stdio transport, revision 2026-07-28 and, after initialize, 2025-11-25; a tool, not a crate of the library; L22b); the project doctor is the service'sthe meta-whatsapp-rs-server* skills; meta-whatsapp-server doctor (server::doctor::Doctor: every configuration refusal by variable, the backend and its migration histories read-only, the app's whatsapp_business_account subscription and fields, each bound WABA's token through debug_token and the app's subscription to it, the Solution Partner's system token)done / done
118DocumentsInvoices, receipts, vouchers rendered to PDF or PNG——none (ours)typst::Renderer; typst::Template::invoice, receipt, voucherPOST /v1/numbers/{pn}/documents in M4done / gap (M4)
119InboxCMS inbox: store events, check the 24-hour window before a reply——pricing.md (the customer service window)inbox::InboxSink; inbox::Inbox::reply; core::store::CustomerServiceWindow; the window counts the calls that reopen it and the customer's standby messages (window events, inbox::Inbox::window: L7), and a reply is refused locally while another app owns the thread (inbox::Inbox::thread_owner), each check turned off by inbox::ReplyChecksrecording since M1c; the inbox routes and the window check over HTTP in M2adone / partial (M2a)

J. Cloud API capabilities Zaileys does not offer ​

#AreaCapabilityZaileys (Web)Zaileys (Cloud)Meta Cloud APILibraryServiceStatus
120OnboardingEmbedded Signup v4: launch options, session events, code exchange, token checks, the encrypted vault, an approval gate, resume——embedded-signup/overview, embedded-signup/implementation, access-tokensclient::embedded_signup::EmbeddedSignup, onboard_with_approval, resume; client::embedded_signup::LaunchOptions; client::embedded_signup::SignupSessionsM3 (/v1/signup/*)done / gap (M3a)
121OnboardingHosted Embedded Signup, app-only install——embedded-signup/hosted-es, embedded-signup/app-only-install, webhooks/reference/account_updatethe app_only_install launch feature (client::embedded_signup::FeatureName::AppOnlyInstall, refused with coexistence); hosted Embedded Signup (L11a; OPEN_QUESTIONS #11): client::embedded_signup::HostedSignup (the PARTNER_ADDED webhook's waba_info.waba_id and owner_business_id) and client::embedded_signup::EmbeddedSignup::onboard_hosted, onboard_hosted_with_approval: the business token from system_user_access_tokens on the customer's portfolio with the appsecret_proof of your system token in a form body (client::waba::Business::system_user_access_token; a fetch_only read, step fetch_token), then the code flow's steps (the WABA's owner checked against the webhook's) and the same vault record, resumed by EmbeddedSignup::resume—done / gap (M3f)
122OnboardingSeveral WABAs in one signup——embedded-signup/overview, embedded-signup/implementation (the session info's waba_ids), solution-providers/manage-accounts (debug_token's granular_scopes)opt-in (L11b; OPEN_QUESTIONS #5): client::embedded_signup::OnboardingRequest::every_granted_waba, off by default (then only the claimed WABA is onboarded, as by EmbeddedSignup::onboard), and client::embedded_signup::EmbeddedSignup::onboard_wabas, onboard_wabas_with_approval (the approval once per WABA, recorded per WABA for a Solution Partner): one code exchange and debug_token, a session naming a WABA the token was not granted refused, then each granted WABA verified, stored, subscribed and funded on its own, the claimed one with the request as it is, the others registering and syncing nothing and onboarded only when Meta reports the claimed WABA's owner as theirs, one the vault holds left alone and none replaced (stored only where absent); per-WABA outcomes in client::embedded_signup::WabasOnboarded (client::embedded_signup::WabaOutcome), a failure not failing the others, each resumed on its own by EmbeddedSignup::resume; hosted signup onboards one WABA per PARTNER_ADDEDinherits it (M3f)done / gap (M3f)
123OnboardingPre-verified numbers: pools, adding, codes, sharing, partners——embedded-signup/pre-verified-numbers; reference/business/whatsapp-business-pre-verified-phone-numbers-api.md, reference/business/whatsapp-business-pre-verified-phone-number-sharing-api.md, reference/business/add-phone-numbers-api.md, reference/whatsapp-business-pre-verified-phone-number/whatsapp-business-pre-verified-phone-number-api.md, reference/whatsapp-business-pre-verified-phone-number/request-verification-code-api.md, reference/whatsapp-business-pre-verified-phone-number/verify-code-api.md, reference/whatsapp-business-pre-verified-phone-number/whatsapp-business-pre-verified-phone-number-partners-api.mdthe pool on a partner portfolio (L11c): client::waba::Business::add_pre_verified_number, pre_verified_numbers (filtered by number and code_verification_status, with a stream), share_pre_verified_number (Meta removed unsharing); each number through client::Client::pre_verified_phone_number (client::waba::PreVerifiedPhoneNumber::get, request_code, verify_code with the secret client::phone_numbers::VerificationCode, partners, delete), its id a new numeric id type of core, checked as digits (core::ids::is_numeric_id) when built and again before any request; offered by client::embedded_signup::LaunchOptions::pre_verified_phone_ids and added to a WABA by client::waba::NewPhoneNumber::preverified_id—done / gap (M3f)
124OnboardingCoexistence (WhatsApp Business app users): onboarding, contacts and history sync, echoes——embedded-signup/onboarding-business-app-users; webhooks/reference/history.md, webhooks/reference/smb_message_echoes.md, webhooks/reference/smb_app_state_sync.mdLaunchOptions::coexistence; client::phone_numbers::PhoneNumber::sync_smb_app_data, requested by onboarding itself by default (L11d; OPEN_QUESTIONS #7): steps sync_contacts and sync_history after a coexistence onboarding, turned off by client::embedded_signup::OnboardingRequest::coexistence_sync, Meta's answers in client::embedded_signup::Onboarded::coexistence_sync (client::embedded_signup::SyncOutcome), never posted twice for one onboarding (a record under client::embedded_signup::COEXISTENCE_NAMESPACE); webhooks::WebhookEvent::HistorySynced, MessageEchoed; recorded by the inbox (coverage row 21)echoes and history recorded and delivered as events since M1c; onboarding and the automatic sync (D7) in M3bdone / partial (M3b)
125OnboardingIn-App Signup (opt-in deep links, promo codes)——in-app-signupclient::signups::Signups::create, list; client::signups::deep_linknot exposed; accepting Meta's terms on a business's first signup comes in M5i as an explicit, audited operator action, off by default (OPEN_QUESTIONS #26, decided: the deployer's act)done / gap (M5i)
126WABADetails, subscribed apps and the callback override, assigned users, client and owned WABAs, customer bases—Partial (lists numbers)whatsapp-business-accounts.md, webhooks/override.md; reference/whatsapp-business-account/whatsapp-business-account-api.md, reference/whatsapp-business-account/subscribed-apps-api.md, reference/whatsapp-business-account/assigned-users-management-api.md, reference/business/client-whatsapp-business-accounts-api.md, reference/business/owned-whatsapp-business-accounts.mdclient::waba::Waba; client::waba::BusinessGET /v1/wabas, disconnect (server_api_axum::api::numbers::list_wabas, disconnect_waba, over server_core::numbers::Waba::disconnect); admin attach and unbinddone / partial (M5c4)
127WABACreate a WABA, its activities, system users and their tokens for client businesses, a WABA's solutions——reference/business/whatsapp-business-accounts-api.md (POST), solution-providers/partner-initiated-waba-creation.md, reference/whatsapp-business-account/whatsapp-business-account-activities-api.md, reference/whatsapp-business-account/whatsapp-business-account-solutions-list-api.md, solution-providers/manage-system-users.md, system_user_access_tokens (marketing-messages/onboard-business-customers.md, embedded-signup/hosted-es)client::waba::Business::create_whatsapp_business_account (NewWaba: every field of the creation request but the deprecated On-Behalf-Of on_behalf_of_business_id; ISO 4217 currency checked locally; never replayed after a timeout); partner-initiated creation is a Meta Business Suite flow, with no API call to wrap (partner-initiated-waba-creation); client::waba::Waba::activities and client::waba::Waba::solutions (ListWabaRecords, paged, with streams); client::waba::Business::system_users (on the business portfolio: manage-system-users prints a WABA id, its prose and the Business node's reference a business id); client::waba::Business::system_user_access_token (SystemUserTokenRequest, every parameter of facebook-login-for-business § Get tokens; the appsecret_proof of the token the request carries (the partner's) computed from the app secret and sent in a form body; the answer's token an AccessToken, no error quoting the body; a generation never replayed, a fetch_only read replayed as a read) (L10b)—done / gap (M5c4)
128NumbersTwo-step PIN, data localization, identity-key check, per-number webhook override——business-phone-numbers/two-step-verification, no-storage.md, local-storage, identity-change; reference/whatsapp-business-phone-number/settings-api.mdPhoneNumber::set_two_step_pin, enable_local_storage, set_identity_key_check, set_webhook_overridethe PIN in M3d; the rest not exposed (M5c3)done / gap (M3d, M5c3)
129NumbersSearch visibility, security notifications, notifying users of a number change——reference/whatsapp-business-phone-number/whatsapp-business-account-phone-number-api.md (search_visibility, show_security_notifications, notify_user_change_number)client::phone_numbers::PhoneNumber::set_search_visibility, set_security_notifications, set_notify_user_change_number: fields of the number's own POST (whatsapp_business_api_data for the last two), one per call; the page has no example, so the bodies follow its schema—done / gap (M5c3)
130NumbersOfficial Business Account: request and status——official-business-accounts.md; reference/whatsapp-business-phone-number/whatsapp-business-account-official-business-account-status-api.mdclient::phone_numbers::PhoneNumber::official_business_account (the guide's official_business_account field; the reference's edge of the same name has no example), request_official_business_account (an application to Meta, explicit, never automatic; never replayed after a timeout, only when Meta refused it before processing it, Error::may_resend); the flag PhoneNumberInfo::is_official_business_account. The reference says the endpoint also withdraws an application but documents no field for it: not offered—done / gap (M5c3)
131NumbersBusiness compliance information (India)——reference/whatsapp-business-phone-number/business-compliance-information-api.mdclient::phone_numbers::PhoneNumber::business_compliance_info, set_business_compliance_info (the page's validation rules checked first; it has no example, so the types follow its schemas)—done / gap (M5c3)
132NumbersHealth status of a number, a WABA or a business——support/health-status.mdone type, client::common::HealthStatus (entities, can_send_message, can_receive_call_sip, errors, additional info): client::phone_numbers::PhoneNumber::health_status, client::waba::Waba::health_status, and the health_status of client::templates::TemplateInfo. Meta documents the field on numbers, WABAs and templates only; a business portfolio's status is the BUSINESS entity each of them returns (HealthStatus::entity)—done / gap (M5c3)
133BotsConversational components (welcome message, ice breakers, commands); bot details——business-phone-numbers/conversational-components; reference/whatsapp-business-account/conversational-automation-api.md, reference/whatsapp-business-bot/bot-details-api.mdPhoneNumber::conversational_automation, configure_conversational_automation; bot details client::phone_numbers::WabaBot::get (Client::waba_bot; the page has no example, and no mirrored page says where a bot id comes from)not exposeddone / gap (M5c3)
134CallingCalling settings (hours, SIP, voicemail, icons), permissions, business-initiated calls——calling, calling/call-settings, calling/business-initiated-calls; reference/whatsapp-business-phone-number/calling-api.mdclient::calling::Calling::settings, update_settings, permissions, connect; client::calling::CallingSettings (signalling only: WebRTC media is the integrator's stack)not exposeddone / gap (M5e)
135CallingCall recording and transcription——calling/call-recording, calling/call-transcriptionthe per-call recording and transcription objects on connect and accept: client::calling::ConnectCall::recording, client::calling::ConnectCall::transcription, client::calling::Calling::accept_with with client::calling::AcceptOptions (client::calling::CallRecording, client::calling::CallTranscription: off unless set; purpose and announcement_language required with ENABLED, purpose at most 250 characters, checked locally; L14); the webhooks are typed (call_recording_available, call_transcription_available in webhooks::fields) and the files download through Media::download by the media id they carry; the downloaded transcript document is not typedthe call events onlydone / gap (M5e)
136MarketingMarketing Messages API: send (product policy, activity sharing, bid multiplier), onboarding, the Cloud API marketing switch, partner onboarding to MM Lite——marketing-messages/overview.md, marketing-messages/send-marketing-messages.md, marketing-messages/onboarding.md; reference/whatsapp-business-phone-number/marketing-messages-api-for-whatsapp.md, reference/business/whatsapp-business-partner-onboarding-to-mm-lite-api.mdclient::marketing::Marketing::send; client::marketing::MarketingAccount::onboarding_status, set_cloud_api_marketing_disabled; client::marketing::MarketingBusiness::request_onboardingnot exposeddone / gap (M5h)
137MarketingMax price (agreement, partner allow list, duplicating a template at another price), reach estimates, click tracking (metrics and conversion measurement are Meta's ads APIs: not part of this row)——marketing-messages/pricing/enroll-max-price.md, marketing-messages/pricing/duplicate-templates.md, marketing-messages/pricing.md (GET /{WABA_ID}/reachestimate), marketing-messages/changelog.md (2026-10-01: partners no longer allowlist clients), marketing-messages/view-metrics.md, marketing-messages/measure-conversion.md, marketing-messages/track-click-events.mdthe agreement: client::marketing::MarketingBusiness::sign_max_price_agreement, which signs Meta's beta agreement on the business's behalf, a legal act: its own explicit call, which no other call makes, and signing is the deployer's (OPEN_QUESTIONS #45); its state, client::marketing::MarketingBusiness::max_price_agreement; duplicating a template, client::marketing::MarketingAccount::duplicate_template; reach estimates, client::marketing::MarketingAccount::reach_estimate; a template's max price, client::marketing::MarketingTemplate::set_max_price (L13). The partner allow list was withdrawn by Meta with the open beta (2026-10-01): a partner's agreement covers its shared and managed client WABAs, so there is nothing to wrap. Click tracking opt-out: client::analytics::Analytics::set_button_click_tracking; clicks as webhooks::WebhookEvent::UserActionReported. The metrics and conversion measurement come from Meta's ads APIs (insights on ad objects, events for ads), outside the Cloud APIclicks are operator-only until M2d (D25); max price in M5h, its agreement an explicit, audited operator actiondone / gap (M2d, M5h)
138MarketingClick-to-WhatsApp: the referral on inbound messages, automatic events, welcome message sequences——ctwa/welcome-message-sequences; embedded-signup/automatic-events-apiwebhooks::fields::Referral; webhooks::WebhookEvent::AutomaticEventDetected; the sequences (/{WABA_ID}/welcome_message_sequences): client::marketing::WelcomeSequences (create, update, list, get, delete; L13; coverage row 31); naming a sequence in an ad creative is Meta's Marketing API, and reporting automatic events to Meta's Conversions API (optional) is outside the Cloud API: neither is part of this rowthe referral in message_received events, automatic_event_detected eventsdone / partial (M5h)
139RoutingConversation routing: thread control (pass, take, release), standby, conversation context——conversation-routing/thread-control, conversation-routing/overview, conversation-routing/routing-roles; webhooks/reference/messaging-handovers (nm), webhooks/reference/standby (nm)typed webhooks webhooks::WebhookEvent::ThreadControlChanged, StandbyObserved; the port stores ownership and window events (core::store::ConversationStore::set_thread_owner, record_window_event: L5); the inbox records ownership from the handovers and standby copies and standby messages as window events, derives this app and idle (inbox::Inbox::thread_owner), records this app's release, pass and take (inbox::Inbox::record_release, inbox::Inbox::record_thread_owner) and refuses a reply locally while another app owns the thread (core::ErrorKind::ThreadOwnedElsewhere; OPEN_QUESTIONS #44: L7), without storing conversation_context; the thread control API: pass, take and release (client::thread_control::ThreadControl::pass, take, release; L15), each never replayed, which the inbox does not call itself (the caller records its own call); the Routing Roles API, the roles the business holds on a number and its standby grant (client::thread_control::ThreadControl::routing_roles, answering client::thread_control::RoutingRoles; L15a)the two types are operator-only until M2d (D25); the API in M5ldone / gap (M2d, M5l)
140AccountsAccount model evolution: Messaging Accounts and WhatsApp accounts (WAAC); general availability from H2 2026——account-model-evolution, account-model-evolution/messaging; reference/whatsapp-account-number/whatsapp-account-number-api.mdpartial (coverage row 30): the Messaging Account a send bills, client::messages::OutboundMessage::messaging_account_id (L16a; a WABA id names its Messaging Account, so the WABA calls reach it unchanged); not the WAAC and Messaging Account endpoints, the WAAC id or the whatsapp_account webhook, which Meta publishes in its phase 2, H1 2027 (L16b)—partial / gap (M5c4, M5c5)
141PaymentsPayments in India (UPI, payment links, order templates, onboarding) and Brazil (Pix, Boleto, payment links, one-click)——payments/payments-in/overview.md, payments/payments-br/overview.md and their pagesIndia (P1a): the order messages (row 38), the payment lookup and refunds (client::payments::Payments::lookup, client::payments::Payments::refund: never replayed after a timeout) and onboarding (client::payments::PaymentConfigurations: list, get, create, regenerate_oauth_link, delete; the OAuth link a client::common::SecretUrl); webhooks::WebhookEvent::PaymentConfigurationUpdated. Brazil (P1b): Pix (copy and paste, or Pix with redirect), payment links, Boleto and one-click payments in the order messages (row 38). Not yet: the payment status webhook of type payment and the payment_method reply, still marked out of scope in the webhooks' conformance manifest, and the templates' payment buttons (P1c); the data endpoint of POST /{WABA_ID}/payment_configuration/{CONFIGURATION_NAME} (for checkout button templates, its answer undocumented) and the alpha third-party validationpayment_configuration_updated events onlypartial / partial (P2)
142PartnersCredit lines: share, attach, verify, revoke——solution-providers/share-and-revoke-credit-lines.md; reference/whatsapp-business-account/extended-credits-api.mdclient::credit_lines::CreditLines::list, share_and_attach, revoke, allocation_statusM3a (Solution Partner mode)done / gap (M3a)
143PartnersPartner-led business verification——solution-providers/partner-led-business-verification.mdclient::business_verification::BusinessVerification::submit, submissions, statusnot exposeddone / gap (M5j)
144PartnersMulti-Partner Solutions: create, accept or reject, deactivation, the solution token, an app's solutions and client businesses——solution-providers/multi-partner-solutions.md, solution-providers/multi-partner-solution-embedded-creation.md; reference/whatsapp-business-solution/solution-details-api.md and the pages beside it, reference/application/application-solutions-api.md, reference/application/application-connected-client-businesses.mdclient::partners::solutions::PartnerSolutions::create, accept, reject, send_deactivation_request, accept_deactivation_request, reject_deactivation_request, get, partners, app_solutions, connected_client_businesses (each list with its …_stream), and the client's business token through a solution, PartnerSolutions::access_token (an AccessToken, withheld from decode errors); webhooks::WebhookEvent::PartnerSolutionUpdated; LaunchOptions::solution_idpartner_solution_updated is operator-only (no WABA to route it by)done / gap (M5j)
145PartnersMoving numbers and WABAs between partners or solutions——solution-providers/support/migrating-phone-numbers-among-solution-partners-programmatically.md and the other migration pages; reference/whatsapp-business-account-migration-intent/migration-intent-details-api.mdthe number migration flag client::waba::NewPhoneNumber::migrate_phone_number with the code and register calls; migration intents, client::partners::migration::MigrationIntents::create (set_solution_migration_intent) and get (MigrationIntentInfo::is_accepted); the templates of a moved WABA, Templates::migrate_from (row 96); the rest of the migration pages are Meta Business Suite and Embedded Signup steps, not calls—done / gap (M5j)
146PartnersA user's assigned WABAs——reference/user/assigned-whatsapp-business-accounts-api.mdclient::waba::BusinessUser::assigned_whatsapp_business_accounts (ListAssignedWabas: fields, limit, cursors; a stream) (L10b)—done / gap (M5j)
147BillingBilling currency or payment method migration——pricing/change-billing-currency.md (set_payment_method_migration_intent)client::waba::Waba::set_payment_method_migration_intent (PaymentMethodMigrationIntent: the currency checked locally, an optional Line of Credit), client::waba::PaymentMethodMigration::status, client::waba::PaymentMethodMigration::resume_migration: the intent and the resume are explicit calls no other call of the library makes, neither replayed after a timeout (L10b)—done / gap (M5j)
148HistoryMessage history events (the delivery events of one history entry)——reference/message-history/whatsapp-business-message-history-events-api.md (where its id comes from is not in the mirror)client::waba::MessageHistory::events (ListMessageHistoryEvents: delivery_status, fields, limit, cursors; a stream); the history id is the caller's, Meta documents no call that gives it (L10b)—done / gap (M5c4)
149PolicyAccount, policy and security events (alerts, violations, reviews, capability, name, quality, security)——webhooks/reference/account_alerts.md, webhooks/reference/account_update.md, webhooks/reference/account_review_update.md, webhooks/reference/business_capability_update.md, webhooks/reference/phone_number_name_update.md, webhooks/reference/security.md; policy-enforcement.mdwebhooks::WebhookEvent::AccountAlert, AccountReviewUpdated, AccountUpdated, AccountSettingsUpdated, BusinessCapabilityUpdated, PhoneNumberNameUpdated, SecurityUpdatedall seven are tenant event typesdone / done
150PolicyMarketing opt-out (user preferences)——webhooks/reference/user_preferences.mdwebhooks::WebhookEvent::UserPreferenceChanged; core::ErrorKind::MarketingOptedOut (131050)user_preference_changed events; 409 marketing_opted_outdone / done
152AccountsParent BSUID accounts: the portfolios that share parent BSUIDs——business-scoped-user-ids (§ Get parent BSUID account, served from api.facebook.com)client::waba::Business::parent_bsuid_account (a ParentBsuidAccount): the one request the client's credential rules (client::GraphRequest) let take a token to api.facebook.com, GET /{digits}/parent-bsuid-accounts over https on the default port, with no version segment; every other path, method, scheme or port on that host is refused before any request (roadmap L10a)—done / gap (M5c4)
156NumbersPayload encryption, connection status and webhook URL on a number——reference/whatsapp-business-phone-number/settings-api.md (payload_encryption: status, client_encryption_key, client_encryption_key_fingerprint, cloud_encryption_key); reference/whatsapp-business-phone-number/whatsapp-business-account-phone-number-api.md (connection_status, webhook_url)client::phone_numbers::PhoneNumber::enable_payload_encryption (status and the key, a client::phone_numbers::ClientEncryptionKey checked to be base64 and not an obvious private key, whose Debug shows only its length), disable_payload_encryption, and the answer of settings (client::phone_numbers::PayloadEncryption: status, optional so that an answer without it still parses, client_encryption_key_fingerprint, cloud_encryption_key, a client::phone_numbers::CloudEncryptionKey shown as a length in Debug; decode errors withhold the body); set_connection_status (client::phone_numbers::ConnectionStatus) and set_webhook_url (an absolute https URI, no user info or fragment, sent as given), fields of the number's own POST, one per call. The four setters are not replayed after a timeout. Neither page has an example: the bodies follow the schemas, and the module docs name what the pages leave out (roadmap L26)—done / gap (M5c3)

Plan to parity ​

roadmap.md cites every partial and gap row from a PR-sized item, each naming its crate, what it comes after and its decisive test:

  • The service's modular split (S1–S18 and U1–U4, design D26): a framework-free core with ports, CrateStack as the default API and store once the owner accepts design D20 (a), the axum API (permanent, the swap target) and the sqlx store kept, MongoDB later. Row 112, and the ground every service milestone below builds on.
  • The bot framework, a new library crate meta-whatsapp-bot: commands, middleware, compile-time plugins and markdown replies (B1, done; rows 17, 85–88), subcommands and flags (B1b, done; row 85), rich replies beyond text (B1c, done; row 17), paced broadcast (B2, done; rows 89, 92; D29), durable scheduling (B3, done; row 90; D29), and retention and auto-delete after the ConversationStore port change (B4, after L5, done; row 91).
  • Library gap batches (L4–L26), by crate and topic: every library partial or gap row not in the bot framework or payments.
  • Service milestones: M2a–M2f (inbox, SSE, webhooks-out, D25, erasure), M3a–M3f (Embedded Signup, OTP, coexistence), M4 (packaging), M5a–M5l (routes for the modules parity requires, the bot and broadcast APIs included).
  • Payments (India, Brazil; P1a–P1c, P2): in scope, last; India's messages, lookup, refunds and onboarding landed (P1a), and Brazil's messages (P1b).

How to keep this true ​

  • A pull request that changes a capability updates its row here in the same PR, with the symbol that now does it, and its category in categories.md and its row in coverage.md when their status moves. A row says done only when the code shows it; when in doubt it says unverified.
  • A new capability gets the next free row number, in the section it belongs to (numbers never move), and the counts at the top are recounted.
  • When a page marked (nm) reaches the mirror, read it: drop the mark, and correct the row if the page says otherwise.
  • The PR description says what happened to this table, as it does for the docs and skills (CONTRIBUTING.md § Docs and skills parity).

Released under the MIT License.