Capability status
Per-capability status of WATS against the WhatsApp Cloud API, tagged live-validated, shape-only, or planned.
active · reviewed 2026-06-21
Three tags. live-validated: exercised against live Meta Graph/webhooks. shape-only: implemented and tested against MockTransport and synthetic webhooks; Meta has not seen it. planned: not built.
| Capability | pywa | wats | Status |
|---|---|---|---|
| Client construction | pywa.WhatsApp(...) | GraphClient + WhatsApp facade; construction-time validation. | live-validated |
| Scoped sub-clients | phone_id / WABA-bound methods | PhoneNumberClient, WABAClient; ids validated at construction. | live-validated |
| Handler routing | decorator registration | TypedRouter: registration-order dispatch, handles with unregister(), observer hooks. | live-validated |
| Filters | & / | / ~ operator filters | filtersTyped: and/or/not/custom plus message/status/template/group built-ins. | shape-only — pure local predicates; nothing here talks to Meta. |
| Listeners | listen waiters | wa.listen({ type, from?, filter?, timeoutMs?, signal? }) over a bounded registry. | shape-only. |
| Sent-result waiters | SentMessage.wait_for_reply etc. | startChat(...) returns waitForReply, waitForClick, waitForSelection, waitForFlowCompletion, waitUntilDelivered, waitUntilRead, and waitUntilFailed, all resolved from observed webhooks only. | shape-only — process-local listener registry, no persistence. |
| Webhook verification | server config | Challenge echo + constant-time X-Hub-Signature-256 HMAC over the raw body. | live-validated |
| Webhook adapter | built-in server | Runtime-neutral WebhookAdapter with fetch / Bun / Node wrappers; identical status-code taxonomy. | live-validated |
| Webhook normalization | update classes | normalizeWebhookEnvelope → TypedUpdate union; per-entry skip taxonomy, dedup, event cap. | live-validated for message and status families; deeper families shape-only. |
| Update parser | n/a (internal) | Strict envelope validation, safety limits, malformed-skip counters. | live-validated via the webhook path. |
| Error model | WhatsAppError subclasses | GraphApiError hierarchy + registry seeded with pywa's 66 codes; instanceof narrowing. | live-validated |
| Shared domain types | Python classes | Closed discriminated unions for messages, statuses, contacts, webhook values. | live-validated |
| Graph transport | wa.api | Transport seam: createFetchTransport, createMockTransport, defineEndpoint registry. | live-validated |
| Retry/backoff | client options | Opt-in createReliableTransport: jittered backoff, Retry-After, no POST retries by default. | shape-only. |
| Pagination | iterator helpers | paginate / paginateAll async generators over cursor pages. | shape-only. |
| Crypto provider | n/a (internal) | CryptoProvider seam with Node and WebCrypto adapters. | live-validated via live HMAC verification. |
| Text sends | send_text / send_message | sendText, WhatsApp.startChat to arbitrary recipients. | live-validated |
| Media sends | send_image etc. | sendImage / sendVideo / sendAudio / sendDocument / sendSticker by media id or link. | live-validated for image by media id; other media composers shape-only. |
| Other composers | location, contacts, reactions, buttons, lists, CTA, catalog, mark-read, typing | Matching helpers on PhoneNumberClient and the facade. | shape-only. |
| One-call media send | file/bytes polymorphism | PhoneNumberClient.uploadAndSendImage / uploadAndSendVideo / uploadAndSendAudio / uploadAndSendDocument / uploadAndSendSticker upload in-memory Blob / ArrayBuffer / Uint8Array bodies, then send by returned media id. The Node/Bun-only @wats/graph/node-media subpath adds filesystem-path helpers. | shape-only. |
| Media runtime | media helpers | uploadMedia, downloadMedia, downloadMediaBytes, deleteMedia, decryptEncryptedMedia, upload sessions. | live-validated — upload/metadata/send/delete passed live; decrypt and sessions shape-only. |
| Template send | send_template | sendTemplate with parameter-count validation; sendMarketingTemplate for marketing messages. | live-validated — approved-template send produced the full sent/delivered/read chain; marketing send shape-only. |
| Template management | create_template etc. | List/create/get/update/delete callables, component builders, template-group analytics, compareTemplates, unpauseTemplate, migrateTemplates, archive/unarchive helpers, auth-template upsert, and library-template create fields. | live-validated for list reads; mutations/comparison/unpause/migration/archive/auth-upsert/library-template fields shape-only. |
| Flows | Flow management | List/get/create/update/publish/delete/deprecate/assets callables, bounded Flow JSON validation, typed FlowJSON DSL builders (screens/components/actions), metrics, migration, response builders. | shape-only — live read returned empty; mutations/metrics/migration never exercised. |
| Flow encrypted data-channel | richer Flow runtime | decryptRequest/encryptResponse (RSA-OAEP + AES-GCM, IV-flip response) and a framework-agnostic handleFlowRequest dispatch (ping/error-ack/close), backed by @wats/crypto. | shape-only — local crypto round-trip; no hosted endpoint or live publish. Media-upload (CBC+HMAC) decryption is implemented shape-only. |
| Calling | initiate_call etc. | Initiate/pre-accept/accept/reject/terminate callables, call-button message and deep-link helpers, typed calling webhooks and filters. See Calling Reference. | shape-only — no live call sessions yet. |
| Call permissions + calling-settings mutation | permission models, SIP/settings writes | getCallPermissions (typed permission/action/limit models), calling settings sub-object on updatePhoneNumberSettings (status, call hours, call icons, callback permission, SIP servers), call_permission_reply webhook fields (isPermanent, responseSource, plus fromUserId/fromParentUserId). | shape-only — no live calls; WebRTC/media orchestration still planned. |
| Business/admin reads | get_business_account etc. | getWabaInfo, listSubscribedApps, listPhoneNumbers, getPhoneNumberInfo, getBusinessProfile, getCommerceSettings. | live-validated |
| Phone-number settings read | settings getter | getPhoneNumberSettings (storage config, optional SIP credentials as sipUserPassword) plus getWabaInfo calling health as healthStatus.canReceiveCallSip. | shape-only — not exercised in the live campaign. |
| Business/admin mutations | profile/commerce/settings updates | updateBusinessProfile, updateCommerceSettings, updatePhoneNumberSettings, Block API, OBA/display-name helpers, the phone registration lifecycle (createPhoneNumber, requestVerificationCode, verifyPhoneNumber, registerPhoneNumber, deregisterPhoneNumber, setTwoStepVerificationPin), business public-key helpers (getBusinessPublicKey, setBusinessPublicKey), and QR code CRUD (createQrCode, listQrCodes, getQrCode, updateQrCode, deleteQrCode). | shape-only. |
| Admin: tokens, catalog CRUD | established | Embedded Signup token exchange is shape-only. Catalog/product inventory CRUD is not built. | planned for catalog CRUD. |
| Groups | no pywa equivalent | Types, endpoint callables, GroupClient, webhook normalization, filters, facade helpers, opt-in service routes. | shape-only — live listGroups passed; createGroup blocked by a Meta entitlement on the test number, so the mutation matrix is unproven. |
| Config | constructor args | @wats/config: versioned YAML/JSON profiles with env-secret references, never raw secrets. | shape-only — local-only by design. |
| CLI | n/a | wats init/setup/doctor/openapi/serve/onboarding/upgrade; credential-safe by default. | shape-only — local tooling; live serve is the operator's opt-in. |
| Service | n/a | createWatsServiceApp: health/readiness, webhook delegation, authenticated message routes, Graph-failure diagnostics. | live-validated for webhook ingress and text send; other routes shape-only. |
| Message event-store projection (outbound) | n/a | @wats/persistence wats_messages + wats_message_status_events tables and recordMessage / appendMessageStatus / getMessage / listMessages APIs; read-only GET /messages and GET /messages/{messageId} service routes; wats messages list/show CLI inspection; Postgres adapter at @wats/persistence/postgres (shape-only, mock-client tested). | shape-only — local SQLite + MockTransport only; Postgres mock-client tested; no live Meta calls this slice. |
| OpenAPI document | n/a | OpenAPI 3.1 for WATS service routes, served at /openapi.json. | shape-only. |
| Internal utilities | n/a | @wats/internal-utils shared helpers. | shape-only — never leaves the process. |
| OpenTelemetry observability | n/a | Not built; router/adapter observer hooks exist as seams. | planned. |
Live-validation run log: campaign log.
Notes
- Webhook media ids are downloadable for 7 days after receipt (Meta reduced the window from 30 days to 7, effective 2025-10-09). Download promptly and persist to your own storage if you need media later; WATS does not persist it for you.
- A send accepted with HTTP 200 proves Meta took the request, not that anyone received it. Delivered/read state comes only from observed status webhooks.