Feature coverage
What meta-whatsapp-rs covers of Meta's WhatsApp Business Platform, in priority order. Doc paths are relative to https://developers.facebook.com/documentation/business-messaging/whatsapp/.
This is the per-feature list. parity.md compares the same code with Zaileys and Meta's Cloud API, capability by capability, and categories.md sums it up by Meta platform category (each of its rows links back to the rows here); roadmap.md plans what is missing. A PR that changes a feature updates its row here and in those tables.
Status: done = implemented and tested against the docs' request/response examples (each area went through an implementation pass and an adversarial review with mutation testing); partial = the listed subset only; planned = not yet; out of scope = deliberately not covered, with the reason. Verified against the docs as mirrored on 2026-09-24 (Graph API v25.0).
Grading follows the rule stated once in parity.md § How to read the table (so a row with a documented endpoint or field not wrapped is partial, naming it); this file's planned is parity.md's gap, and its out of scope parity.md's n/a.
| # | Feature | Priority | Module | Doc paths | Status |
|---|---|---|---|---|---|
| 1 | Embedded Signup (v4): launch options, session-info events, code exchange, debug_token, id verification against the token's grants and Meta's owner lookup, encrypted token vault (AES-256-GCM, key rotation, phone → WABA index; a token's expiry reported a configurable lead ahead from the record's clear metadata, nothing decrypted: TokenVault::token_expiry, roadmap L11e), tenant-bound signup sessions, subscribe app, register number, resume (after a restart from the saved session info, with no code: OnboardingRequest::for_resume); hosted Embedded Signup from the PARTNER_ADDED webhook (EmbeddedSignup::onboard_hosted: the business token from system_user_access_tokens with the appsecret_proof, then the same steps and vault record; roadmap L11a); an approval gate between verification and storage; per deployment, Solution Partner mode (the approval required and recorded, system user on the WABA, credit line shared with either of Meta's methods after checking it is not already and was not revoked, under a lease renewed before each post and a pending-share flag an operator can clear once Meta shows no live record and no funding the line's records do not explain, unless the operator acknowledged it (audited in the ledger), a revocation racing a share winning, a sealed credit ledger that outlives the token and rotates with it, revocation from the recorded owner, Meta's record of the recorded allocation or a signed webhook's business, typed CreditErrors, offboarding that revokes before it deletes) | most requested | meta_whatsapp_client::embedded_signup | embedded-signup/*, access-tokens, solution-providers/{share-and-revoke-credit-lines, manage-system-users}, webhooks/reference/account_update | partial (was "done" until 2026-09-26, by the grading above; parity.md rows 6, 121–123): the Tech Provider and Solution Partner flows and hosted Embedded Signup (embedded-signup/hosted-es, L11a) are done; so is the token-expiry signal (OPEN_QUESTIONS.md #8, L11e); the pre-verified number endpoints (embedded-signup/pre-verified-numbers) and multi-WABA onboarding are not (decided on 2026-09-26: OPEN_QUESTIONS.md #5, roadmap L11b, L11c). v2/v3 EsVersion values are deprecated (Meta: 2026-10-15) |
| 2 | Webhooks: verify token, X-Hub-Signature-256 (multi-secret, fail-closed; the axum route refuses a missing or malformed header before reading the body), every documented field, BSUID identities, normalized events, leased dedup, dead letters for events a sink refuses permanently (bounded by count, age and size, alerted, replayable, erased per contact: roadmap L21a), PII-free logs, axum router, SSE | very requested | meta-whatsapp-webhooks | webhooks/*, business-scoped-user-ids | done, every example payload on every page that prints a webhook body (88 pages, listed with their status in tests/conformance/manifest.rs) a fixture checked by tests/conformance.rs; the payments pages' payloads are row 32's (planned: roadmap P1c), two examples are malformed as printed (recorded there), webhooks/reference/pricing and webhooks/message_echoes are listed by Meta but answer "Page Not Found", and message_echoes / consumer_profile (undocumented payloads) arrive as Unknown; a standby echo's Send API body, template and Flow definitions stay JSON |
| 3 | Templates: list/get/create/edit/delete, library, migrate, compare, unpause, archive/unarchive in bulk, template groups (create, get, update, delete), TTL, named & positional params, media/carousel/LTO/coupon/call-permission/catalog/MPM/SPM/flow components, URL buttons' Android app deep links, typed send-time components | very requested | meta_whatsapp_client::templates | templates/*, marketing-messages/deep-links | partial (was "done" until 2026-09-26, parity.md rows 101, 153): archiving and unarchiving in bulk (Templates::archive, unarchive) and template groups' create, get, update and delete (Templates::create_group, get_group, update_group, delete_group) landed with roadmap L23; not wrapped, because no page documents them: the list of a WABA's template groups (GET /{WABA_ID}/template_groups, in Meta's changelog only) and the pages after the first of a group's templates |
| 4 | Authentication templates (copy code, one-tap, zero-tap), previews, bulk upsert, OTP service (E.164-bound, scoped to the sending number and a required tenant namespace, hashed, CAS-counted attempts, cooldown + 5/hour issue limit) | very requested | meta_whatsapp_client::authentication | templates/authentication-templates/* | done |
| 5 | Catalogs & commerce: commerce settings; catalog and product reads (the WABA's connected catalog, a catalog with its products and product sets, a set's products, one product; paged by cursor); catalog / single-product / multi-product / product-carousel messages; catalog, MPM, SPM and product-card-carousel templates; order webhooks | requested | meta_whatsapp_client::commerce, messages, templates, meta-whatsapp-webhooks | catalogs/*; Meta's Catalog API (Marketing and Graph API references, outside the WhatsApp docs) | done; uploading inventory (every catalog write, and connecting a catalog to a WABA) happens in Meta's Catalog API / Commerce Manager, not wrapped; the reads are commerce::Catalog, ProductSet, Product and waba::Waba::product_catalogs (roadmap L17) |
| 6 | In-App Signup (opt-in deep links, ToS acceptance, promo codes) | requested | meta_whatsapp_client::signups | in-app-signup | done |
| 7 | Messages: text, media, location, contacts, address, reactions, stickers, pin/unpin, interactive (buttons, list, CTA URL, location request, flow, media carousel, product carousel, voice call, call permission request, request contact info), contextual replies, mark read, typing indicators, link previews, group sends | core | meta_whatsapp_client::messages | messages/*, typing-indicators | done; the payments interactive types order_details and order_status are row 32's (India and Brazil typed, roadmap P1a, P1b) |
| 8 | Media: upload (MIME/size checked), URL, streaming download with SHA-256 verification (of media hosts, the token goes only to the Graph endpoint and lookaside.fbsbx.com; its one other URL is a single GET on api.facebook.com, roadmap L10a), delete, Resumable Upload (template header handles); conversion before an upload (a recording into a voice message's mono Ogg Opus, roadmap L19; a PNG or JPEG into a static WebP sticker within 100 000 bytes, or a JPEG or PNG within 5 000 000, roadmap L19b: core's MediaConverter port, re-exported here, Media::upload_converted, implemented by meta_whatsapp_adapters::media::FfmpegConverter over an installed ffmpeg, feature ffmpeg) | core | meta_whatsapp_client::media | business-phone-numbers/media, reference/media/*, messages/audio-messages, messages/sticker-messages | done; conversion makes no animated sticker yet (parity.md row 48, roadmap L19c) |
| 9 | Business-scoped user IDs & usernames: send by BSUID / parent BSUID, identities in every webhook, BSUID-keyed inbox, the parent BSUID account | core (mandatory 2026) | meta_whatsapp_core::recipient, meta-whatsapp-webhooks, meta_whatsapp_rs::inbox, meta_whatsapp_client::waba | business-scoped-user-ids | done (partial from 2026-09-26 until roadmap L10a, parity.md rows 66, 151, 152): sending by BSUID or parent BSUID, the identities in every webhook, the BSUID-keyed inbox, the business username calls and deleting a contact book entry (phone_numbers, roadmap L9), and the parent BSUID accounts API (Business::parent_bsuid_account, L10a: served from api.facebook.com, the one URL on that host the client's credential rules let carry a token) |
| 10 | Phone numbers: list/get/create, request & verify code, register/deregister, two-step PIN, settings, display names, conversational components, per-number webhook override, coexistence smb_app_data sync, the business username (adopt, read, reserved names, delete), deleting a contact book entry, search visibility, security notifications and number-change notices, payload encryption, the number's connection status and webhook URL, the Official Business Account request and status, business compliance information (India), health status (numbers and WABAs, one type with templates), bot details | core | meta_whatsapp_client::phone_numbers (the WABA's health in waba, the shared health types in common) | business-phone-numbers/*, display-names, business-scoped-user-ids, official-business-accounts, support/health-status, reference/whatsapp-business-phone-number/*, reference/whatsapp-business-bot/bot-details-api | done (partial from roadmap L9 until L26; parity.md rows 66, 129–133, 151 and 156): the payload-encryption settings (enable_payload_encryption, disable_payload_encryption, read back in settings) and the number's POST fields connection_status and webhook_url landed with L26. Of that POST's fields, whatsapp_business_api_data.pin and username are not sent: the PIN and the username have their own documented calls (set_two_step_pin, set_username). Where the pages stop, the module docs say so: the number fields, payload encryption, compliance information and bot details have no example (their bodies follow the schemas; what payload encryption's key, fingerprint and cloud key are, and what a connection status or webhook_url does, is not documented), the Official Business Account status is read as the guide's field (the reference's edge has no example), withdrawing an application documents no field, and the username errors 147001–147005 are ErrorKind::Unknown |
| 11 | Business profile (phone number and profile-node APIs) | core | meta_whatsapp_client::business_profile | business-profiles | done |
| 12 | WABA management: details, health status (Waba::health_status, roadmap L9), phone numbers, subscribed apps & callback override, assigned users, client/owned WABAs, customer bases; administration (roadmap L10b): WABA creation (Business::create_whatsapp_business_account), activities (Waba::activities), a WABA's Multi-Partner Solutions (Waba::solutions), Meta's campaign schedules (Waba::schedules, Waba::create_schedule), system users (Business::system_users), a client business's system user token (Business::system_user_access_token, with the appsecret_proof), a user's assigned WABAs (BusinessUser::assigned_whatsapp_business_accounts), the billing currency migration (Waba::set_payment_method_migration_intent, PaymentMethodMigration), message history events (MessageHistory::events) | core | meta_whatsapp_client::waba | whatsapp-business-accounts, webhooks/override, reference/business/whatsapp-business-accounts-api, reference/whatsapp-business-account/*, reference/user/assigned-whatsapp-business-accounts-api, pricing/change-billing-currency, reference/message-history/*, embedded-signup/hosted-es | partial (was "done" until 2026-09-26; parity.md rows 127, 146–148 done with L10b, row 90 partial for the bot's own schedules): GET /{BUSINESS_ID}/whatsapp_business_accounts is not wrapped (the owned list is read through owned_whatsapp_business_accounts; its business_type array filter has no documented encoding); the creation's deprecated On-Behalf-Of on_behalf_of_business_id is not sent; where a message history id or a schedule's audience_id comes from is documented nowhere |
| 13 | Marketing Messages API for WhatsApp: send (product policy, activity sharing, bid multiplier), onboarding status, client WABAs by onboarding status (paged and streamed), Cloud API marketing switch, partner Intent API; max price (the beta agreement, a template's max price, duplicating a template at another max price) and reach estimates | e-commerce | meta_whatsapp_client::marketing | marketing-messages/* | done (partial from 2026-09-26 until roadmap L13, parity.md row 137): signing the max price agreement (MarketingBusiness::sign_max_price_agreement) is a legal act, its own explicit call, which no other call makes (OPEN_QUESTIONS #45); Meta withdrew the partner allow list with the open beta (2026-10-01), so there is none to wrap; a GIF header's send-time parameter is not typed, for want of a page showing it (parity.md row 154) |
| 14 | WhatsApp Flows: management API, a typed Flow JSON model and builder (flows::flow_json, checked against the documented limits), business public key, data-endpoint crypto (aws-lc-rs RSA-OAEP + AES-GCM, feature flows-endpoint), endpoint signature check, uploaded-media decryption | CMS | meta_whatsapp_client::flows | flows/* | done; Flows metrics API skipped (deprecated by Meta 2026-04-30) |
| 15 | Analytics: messaging, conversation, pricing, template, template-group, call and group analytics; template insights; click-tracking opt-out | marketing | meta_whatsapp_client::analytics | analytics | done |
| 16 | QR codes & short links | marketing | meta_whatsapp_client::qr_codes | qr-codes | done |
| 17 | Block users (phone or BSUID, partial failures) | CMS | meta_whatsapp_client::block_users | block-users | done |
| 18 | Groups API: create, info, list, update, delete, invite links, participants, join requests, pin, picture | CMS | meta_whatsapp_client::groups | groups/* | done |
| 19 | Calling API signalling: settings, permissions, connect/pre-accept/accept/reject/terminate, per-call recording and transcription | later | meta_whatsapp_client::calling | calling/* | done (partial from 2026-09-26 until roadmap L14; parity.md row 135): the per-call recording and transcription objects on connect and accept (calling/call-recording, calling/call-transcription) are ConnectCall's fields and AcceptOptions for Calling::accept_with, off unless set; WebRTC media out of scope; media_update undocumented; the downloaded transcript document is not typed |
| 20 | Direct Send (beta): category, TTL, direct-send config on free-form sends | later | meta_whatsapp_client::messages | direct-send/* | done |
| 21 | Coexistence (WhatsApp Business app users): onboarding feature type, contacts/history sync (the last two steps of onboarding by default, sync_contacts and sync_history, each posted once per onboarding, turned off by OnboardingRequest::coexistence_sync; roadmap L11d), history / smb_app_state_sync / smb_message_echoes webhooks, echoes and synced history recorded by the inbox (direction per the docs, idempotent by message id, chunks in any order except that a revoke whose chunk comes before its message's leaves a tombstone in its place, malformed items skipped) | CMS | embedded_signup, phone_numbers, meta-whatsapp-webhooks, meta_whatsapp_rs::inbox | embedded-signup/onboarding-business-app-users, webhooks/reference/history, webhooks/reference/smb_message_echoes | done; synced history opens no local window and is never unread (ConversationStore::append_synced, one batch per chunk, device timestamps bounded by the sink's clock), a later media content fills its recorded placeholder unless it was revoked (fill_media_placeholder), and a revoke deletes only a message of its direction, leaving a tombstone (history only, never in the summary) when it comes first (revoke); the synced contacts (smb_app_state_sync) are stored and paged per number (ConversationStore::put_contact, remove_contact, contacts, since roadmap L5) and recorded by InboxSink since roadmap L8: an add under the BSUID, else the phone number without +, a remove under each key it names, kept so that an older add is refused; read back with Inbox::contacts and Inbox::contact; a change without a time or a usable id is skipped (RecordingSwitches::contacts turns it off) |
| 22 | Error codes → ErrorKind (retry safety, 24h window, opt-out, throttling, registration locks) | core | meta_whatsapp_core::error | support/error-codes | done |
| 23 | Typst documents (invoice, receipt, voucher) → PDF/PNG, deterministic, sandboxed | e-commerce | meta-whatsapp-typst | — | done |
| 24 | Pricing objects on status webhooks (per-message pricing) | analytics | meta-whatsapp-webhooks | pricing | done |
| 25 | Identity change check (identity_key_hash, setting) | security | meta-whatsapp-webhooks, phone_numbers | identity-change | done |
| 26 | CMS inbox: webhook events → ConversationStore (statuses scoped to their business number, content recorded exactly, U+0000 included, coexistence echoes and history: row 21), 24h-window-checked replies with the merchant's token; runnable examples in crates/meta-whatsapp-rs/examples/ (cms_inbox and embedded_signup servers with per-tenant bearer auth, otp_login, send_message, invoice_document) | CMS | meta_whatsapp_rs::inbox | — | done; the window counts the calls that reopen it on Meta's side and the customer's messages seen in standby, recorded as window events (roadmap L7, OPEN_QUESTIONS.md #32), a reply is refused locally while another app owns the thread under Conversation Routing (row 29), and InboxSink records the links between a customer's identities (a phone number to its BSUID, a BSUID or number change) for erasures; a revoke matches its number and direction, not its conversation, and a revoked message keeps its content (both decided by the owner, 2026-09-25) |
| 27 | Adapters: reqwest transport; memory, Postgres, SQLite, Redis stores (key/value and conversation, each) with executable conformance suites; channel/broadcast/fan-out/filter/fn/tracing sinks | core | meta-whatsapp-adapters | — | done; SQLite (feature sqlite, the library compiled in, BEGIN IMMEDIATE write transactions, its own migrations) and the Redis conversation store (one hash tag per store, every write one Lua script) since roadmap L8, each keying messages by business number and id with a separate index for today's rule, an id once per store (OPEN_QUESTIONS.md #33); the conversation stores also keep window events, thread ownership and synced contacts, look a message up by business number and id, keep the links between a person's identities, erase a person on one number (every key identities connects; their group messages redacted or deleted, with_erasure_mode) and purge by age under a retention set per store (with_retention; roadmap L5, Postgres migration 4) and cap each conversation's history (trim_conversations, roadmap B4: no migration), all in the conformance suite; the Postgres conversation store keeps U+0000 in message content (BYTEA and json columns, so SQL-side search is on bytes and payload fields are not indexable) and, like the Postgres key/value store's keys, refuses it in identifiers; Redis requires noeviction; Redis over TLS (rediss://) through adapters::store::RedisTls, on an explicit aws-lc-rs provider, never the process default (roadmap L21c; an integrator-built connection still works; Cluster and Sentinel over TLS not covered) |
| 28 | Solution partner APIs: credit lines, partner-led business verification, multi-partner solutions, WABA/number migration | partners | meta_whatsapp_client::credit_lines, meta_whatsapp_client::business_verification, meta_whatsapp_client::partners | solution-providers/* | done: credit lines done (list, share and attach in one call or two, verify, find, revoke, status; used by Embedded Signup's Solution Partner mode). Partner-led business verification done (submit with documents checked locally, list submissions, business verification status; the outcome is the account_update PARTNER_CLIENT_CERTIFICATION_STATUS_UPDATE webhook, meta_whatsapp_webhooks::fields::PartnerClientCertificationInfo). Multi-Partner Solutions done (partners::solutions: create, accept, reject, send, accept and reject a deactivation request, a solution and its partners, an app's solutions and connected client businesses, and a client's business token through a solution, withheld from Debug and decode errors; a WABA's solutions are roadmap L10b's). Migration intents done (partners::migration: set_solution_migration_intent, read an intent); WABA and number migration otherwise run through Embedded Signup, Meta Business Suite, Templates::migrate_from and the number migration flag. Not wrapped: POST /{WABA_ID}/migration_intent, from an unindexed reference page without an example that the guides do not use |
| 29 | Conversation routing / handover (standby, thread control) | later | meta-whatsapp-webhooks, meta_whatsapp_client::thread_control | conversation-routing/*, webhooks/reference/messaging-handovers, webhooks/reference/standby | done: the webhooks are typed (messaging_handovers → ThreadControlChanged, standby → StandbyObserved, conversation_context on MessageReceived); the Thread control API is wrapped (ThreadControl::pass, take, release: POST /{phone-number-id}/thread_control, exact JSON from conversation-routing/thread-control, none replayed; roadmap L15), and so is the Routing Roles API (ThreadControl::routing_roles: GET /{phone-number-id}/routing_roles, answering RoutingRoles with roles and is_standby, both examples of conversation-routing/routing-roles as exact JSON; roadmap L15a); the inbox records ownership from the handovers (mapped from their phone number to the customer's conversation) and standby copies, standby messages as window events, and refuses a reply locally while another app owns the thread, with an override (OPEN_QUESTIONS.md #44, roadmap L7); it does not store conversation_context |
| 30 | Account model evolution: Messaging Accounts and WhatsApp accounts (WAAC), generally available from H2 2026 (phase 2, H1 2027; phase 3, H1 2028) | later | meta_whatsapp_client::messages | account-model-evolution/* | partial: a send names the Messaging Account it bills (OutboundMessage::messaging_account_id, roadmap L16a), which Meta requires when the access token can send from several on the number, from phase 2; existing WABA ids name Messaging Accounts, so the WABA calls keep working. The WAAC and Messaging Account endpoints, the WAAC id and the whatsapp_account webhook are not published yet (roadmap L16b) |
| 31 | CTWA welcome message sequences: create, update, list, get, delete | later | meta_whatsapp_client::marketing::WelcomeSequences | ctwa/welcome-message-sequences | done (roadmap L13); naming a sequence in an ad creative is Meta's Marketing API |
| 32 | Payments (India UPI, Brazil Pix/Boleto) | — | meta_whatsapp_client::payments | payments/* | partial (roadmap P1a, 2026-11-01): India's order_details and order_status messages typed (meta_whatsapp_client::payments: UPI intent links, payment links, payment gateways with their BillDesk, Razorpay, PayU and Zaakpay fields, the older payment_configuration; every documented rule checked first; amounts {value, offset}), the payment lookup and refunds (Client::payments), onboarding (Client::payment_configurations: list, get, create, regenerate the OAuth link, delete), and the payment_configuration_update webhook; Brazil's messages (P1b): BRL orders (the order optional, a reference_id of 60, payment_type br) paid by dynamic Pix code (copy and paste or Pix with redirect), payment link, Boleto or one-click card (offsite_card_pay), and order_status with a payment update (pending allowed), each currency checked by its own pages' rules and the other's fields refused. Planned: the templates' payment buttons, and the payment status webhook and payment_method reply, which keep the conformance manifest's two payments pages OutOfScope until then (P1c); the service's routes (P2). Not wrapped: the data endpoint of POST /{WABA_ID}/payment_configuration/{CONFIGURATION_NAME} (its answer is undocumented), the alpha third-party validation (encrypted_payment_gateway_data), the examples' header provider; not checked locally: India's subtotal (the examples break the rule; Brazil's is checked), the expiration's 300-second minimum and the beneficiaries of shipped goods (the client cannot tell shipped from collected) |
| 33 | HTTP service for apps not written in Rust (docs/design/server.md): one deployment per Meta app, tenants, tenant/platform/admin keys (SHA-256 digests, constant-time compare, shown once, expiry, suspend and revoke, no cache), fail-closed configuration, public and internal listeners (header read timeout, connection caps, request deadline), Postgres tables and migrations under an advisory lock (expand-only), the authorization order (key, tenant, scope, ownership before the vault), admin API (tenants, keys, platform keys, attaching an own WABA verified with Meta and subscribed, reading a binding, D4 unbind that deletes the token, vault key rotation) and CLI (first admin key, list and revoke keys, vault rotate, and doctor: every configuration refusal by variable, the backend and its migration histories, the app's webhook subscription and fields, each bound WABA's token reach and subscription, read-only, non-zero on a failure), numbers and business profile routes, disconnect; sends (text, media by id or https link, location, contacts, reaction, template, interactive button/list/cta_url; strict E.164 recipients, BSUIDs, groups), read receipts and typing indicators, media upload (type and size checked first), download verified against Meta's SHA-256 before the first byte (or aborted mid-stream, the last chunk held back) and delete, media and template ids checked to be the path's number's or WABA's (Meta's phone_number_id; the WABA's own template list), per-tenant shares of the media and stream slots, template list (cached 60 s per tenant's WABA), get, create (checked locally, no key of Meta's JSON dropped) and delete, Idempotency-Key on sends, uploads and template creation (tenant-scoped, over the operation and its canonical input, replayed, released when nothing was sent, leased, 24 h), per-tenant token-bucket rate limits by the operation's class; Meta's webhooks in (POST /webhooks/meta: signature against every app secret before the body, 3 MiB, 64 deliveries read and 4 recorded at once per replica, 15 s per body, the library's leased dedup shared by every replica, routing to the tenant owning the number or WABA since before the event as an allow-list, operator-only rows for unowned numbers, replays and unknown, unparsed and unreviewed types, inbox then event outbox, idempotent redelivery, errors and bodies that are not webhooks deduplicated for an hour only (then recorded again as a new event), the routing checked again at the insert, atomically (on Postgres under a lock on the binding), ids derived from the event under the app secret), the event outbox (a sequence per tenant, committed in order; retention purge on one replica at a time; a deleted tenant's events deleted with it), GET /v1/events (scope events: after cursor, types, phone_number_id, limit, 8 MiB pages, 410 cursor_expired), event data pinned by snapshots over Meta's examples, webhook metrics; error model on ErrorKind::as_str (section 5.2 read by the tests), health, Prometheus metrics, request logs with the key id, audit events, committed OpenAPI 3.1 document | TypeScript/CMS callers | meta-whatsapp-server (binary crate), composing meta-whatsapp-server-http (listeners, authentication, the error renderer, webhooks in, media, operations), meta-whatsapp-server-api-axum (the /v1 routes and the OpenAPI document) and the backends meta-whatsapp-server-store-postgres and meta-whatsapp-server-store-memory by its features (roadmap S7), on meta-whatsapp-server-core (its framework-free core: domain, authorization, error model, storage ports) | whatsapp-business-accounts, business-profiles, reference/whatsapp-business-account/{phone-number-management-api, subscribed-apps-api}, messages/send-messages, messages/*-messages, messages/mark-message-as-read, typing-indicators, business-phone-numbers/media, reference/media/*, templates/template-management, templates/overview, reference/whatsapp-business-account/message-template-api, webhooks/{overview, create-webhook-endpoint} | partial: milestones M1a, M1b and M1c (acceptance tests M1.1 to M1.7). Inbox routes, GET /v1/events/{id}, SSE, webhooks-out, the service's number events (M2a–M2e); Embedded Signup, OTP, authentication templates, tenant settings (per-tenant rate limits among them), coexistence sync (M3a–M3f); the documents route, Docker image and TypeScript client (M4) are planned. Event retention defaults to 7 days (design D10, decided on 2026-09-26: configurable per store); per-tenant sequences (design D21), deleting a deleted tenant's events (D22) and the hour keyless events are deduplicated for (D23) are coordinator's decisions that stand under the owner's delegation, and the owner confirms D21 and D22 before the first release; the routes for the modules parity requires (M5a–M5l) are planned too (roadmap); the service's framework-free core, its storage ports and the Backend bundle are extracted (roadmap S1), and the ports' contracts are right for any backend (S2: the outbox checks the routing again with each insert, memory included; referential rules; purges under one leased turn per round; one clock; typed contention; capabilities that act only on the binding and the vault record they were made from), and their conformance suites are in core (S3: meta_whatsapp_server_core::conformance, which the memory backend and, live, the Postgres one pass), and the service is composed from a bundle alone (S4: AppState::from_backend and serve::serve_with take an Arc<dyn Backend>, housekeeping runs over it, the command line opens it through a BackendFactory, so an integrator's backend is served with no change to the server), and the admin operations are the core's services (S5a: meta_whatsapp_server_core::admin, the handlers thin adapters over them), as are the messages and the idempotency engine's run (S5b: meta_whatsapp_server_core::messages over a request type free of utoipa, and idempotency::run), the numbers and the templates (S5c: meta_whatsapp_server_core::numbers and meta_whatsapp_server_core::templates, the template cache among them), and the media (S5d: meta_whatsapp_server_core::media, a download streamed through the core as a framework-free byte stream that ends before its last chunk on a digest mismatch), and the webhook pipeline (S5e: meta_whatsapp_server_core::inbound, a delivery verified before any store is touched, each event under its dedup lease, routed by the records and recorded with no listener; the listener's part, the body's limits and the refusals' log, the server's), and what every adapter shares is adapter-neutral (S6: meta_whatsapp_server_core::operation, the tenant operations by id, whose class a request is charged and whose canonical input, not the path, an idempotency key covers; error codes as constants generated from the code table; the vault's admin writes only through the admin services), and the service is split into crates the binary composes by its Cargo features (S7: meta-whatsapp-server-store-memory and -store-postgres, each passing the conformance suite in its own tests, the Postgres one live; meta-whatsapp-server-http; meta-whatsapp-server-api-axum, whose openapi/v1.json is byte-identical; the features api-axum, store-memory and store-postgres; the migrations moved byte for byte, so a database main migrated starts the split binary with no migration run again), and a WABA's moves are recorded since S2b (each backend keeps its last unbinding from another tenant, a tenant deleted and created again under its id counting as another; on Postgres an additive migration 4), so 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 by default (undated_after_move, D36: WA_SERVER_UNDATED_AFTER_MOVE, …_WINDOW), a dated one reaches its holder only from the second after its binding's (plus WA_SERVER_MOVE_SKEW_MARGIN), a first onboarding keeps the events of its first second, and a capability made before a binding was refreshed for its own tenant marks nothing (the bind generation in the binding's epoch); an unbind made by an earlier release (before the upgrade, or during its rolling deploy) is not recorded, and a binding made before migration 4 keeps S2's rules (its history unknown); event ids change with an app secret rotation for events recorded again after their row was purged; one transaction per delivery (instead of per event) is not done; a number moved to another tenant keeps its inbox history under it (M2a's inbox reads filter by binding epoch: D10 hides it, never purges); the design's TOML configuration file and OpenTelemetry export are not implemented; the template cache and rate limits are per replica; that Meta's phone_number_id check accepts media received by webhook (not only uploaded) is undocumented (OPEN_QUESTIONS.md #43); attaching a token is not checked for reach over other tenants' WABAs (debug_token), only each id a route takes; template shapes the library cannot carry (payment buttons' order_details and payment_request, optimization_spec, the pre-v21 one-tap button fields) are refused, 422 on the key (a URL button's app_deep_link is carried since roadmap L24); a template id is looked for in its WABA's first 5 pages of 100 of its name, through the list's undocumented name= filter |
| 34 | Bot framework (parity with Zaileys' framework features): commands (prefixes, case-insensitive names and aliases by default, quoted arguments, usage hints, metadata; subcommands one level deep with their own guards and cooldowns; --flag and --flag=value arguments on a command that declares one, an undeclared one refused with the usage, while a command with none reads every word as an argument; image and video captions; reply buttons, list rows and template quick-reply buttons as commands; an unknown-command hook), guards (banned senders, checked before anything else runs; private-only and group-only, owner-only, per-user cooldowns on KvStore, told once per period), middleware that sees the matched command (Logging without content, MarkRead with a typing indicator), compile-time plugins with categories, a generated and configurable help, Meta's command menu, reactions, listeners by event kind or message type, Markdown replies in WhatsApp formatting split at 4096 characters (tables as padded columns, else header: value lines, bounded by width and length so one wide cell cannot multiply the messages); rich replies of Markdown and typed parts, in order, one message each (an image by link under the renderer's URL rule, suggestions as reply buttons up to 3 or a list up to 10 whose taps can run commands, product cards as a carousel), every message checked before the first send and a failed send stopping the rest; paced broadcasts (one message, or one per recipient, from one number, each person once: a per-number rate under Meta's throughput, 80 a second by default, evenly spaced; progress, cancel and a report per recipient, kept or streamed to a sink; the pair limit deferring one recipient, throughput slowing the pacer, a throughput upgrade's minute of maintenance outlasted, the per-user marketing limit reported, number-wide refusals and a shared template's own stopping the run; resent only when Meta provably refused the send, Error::may_resend, every retry paced; a number's rate settable while running), with the bot's read receipts, typing indicators and replies, and the client's group operations, optionally in the same budget; durable jobs (a message or a broadcast sent at a time, cancelled, retried with backoff, re-run, surviving a restart: a typed store on KvStore with a minute-bucketed due index, claims by compare-and-swap under a lease, each recipient marked before its send so nobody is sent twice and an outcome unknown is never resent, a 131049 status webhook making a re-run skip that recipient for 24 hours); retention and auto-delete of the stored chat history by age and a cap per chat, through its ConversationStore, once or on the bot's timer (RetentionPolicy, RetentionSweeper); the parser, access list, cooldown store, refusals, error handling, Markdown rules, help format, rate limiter, slow-down rule, clock, broadcast policy and report sink are traits with defaults | bots | meta-whatsapp-bot (meta_whatsapp_rs::bot, feature bot) | messages/text-messages, messages/contextual-replies, messages/reaction-messages, messages/image-messages, messages/interactive-reply-buttons-messages, messages/interactive-list-messages, catalogs/interactive-product-carousel-messages, messages/mark-message-as-read, typing-indicators, business-phone-numbers/conversational-components, reference/whatsapp-business-account/conversational-automation-api, business-scoped-user-ids, groups/groups-messaging, throughput, about-the-platform (pair rate limits), templates/marketing-templates/per-user-limits, messaging-limits | done (parity.md rows 17 and 85–92; roadmap B1, B1b, B1c, B2, B2a, B2b, B3, B4): retention sweeps every number of a store unless given the bot's numbers (RetentionSweeper::number), and a cap counts every row of a chat, revokes' tombstones too; the job store is not behind a trait (its KvStore adapter swaps) and schedules are one-off (no recurrence); a rich reply's image goes by link only (an uploaded media id through Ctx::send), and Meta's group page lists no interactive messages, so buttons, lists and carousels sent to a group may be refused; the pacer counts one process's sends unless the replicas share a KvStore and pace with KvRateLimiter (B2b: opt-in, their clocks assumed synchronized, failing closed when the store does), and not Meta's daily messaging limit, nor the number's inbound messages, which Meta counts in its throughput; a throughput upgrade is followed only when asked (ThroughputUpgrades, B2a), in memory with a TokenBucket (a restart is back at the configured rate) or, with a KvRateLimiter, for every replica until the number's record expires; no hot reload (plugins are compiled in, design D28); Meta documents no escape syntax, so the default leaves literal *, _, ~ as written (they may format) and the opt-in WordJoinerEscape (U+2060 around them) rests on how WhatsApp clients parse and is copied along with the text; Meta does not say whether the 4096 limit counts characters or UTF-16 units, so parts are cut to fit both; the typing indicator shows for every received message, commands or not; Meta's group page lists no reactions |