Docs

What happens behind each Cloud API call, the panel where you play the user, and every setting.

What it simulates

AreaCloud APIWhat happens
MessagesPOST /{phone-number-id}/messagesText, media, location, contacts, interactive, reactions, replies, templates (header, body, footer, buttons, carousel, limited-time offer, authentication). Receipts sent, delivered, read; typing indicator.
The userthe same call with X-Mock-FromThe user writes, replies, sends media and voice notes, gives receipts: the message enters as inbound and goes to your webhook.
Templates/{waba-id}/message_templates, /{template-id}Create, edit, delete, list. A review with a verdict and a wait you choose, the message_template_status_update webhook, Meta's send errors.
Media/{phone-number-id}/media, /{media-id}Upload (multipart, Meta's types and size limits), describe, download with a token, delete. Files stay on disk.
Phone numbers/{waba-id}/phone_numbers, request_code, verify_code, register, settingsBusiness, WABAs and numbers; the onboarding of a new number with the SMS code; two-step PIN; calling settings. An unregistered number cannot send (133010).
CallsPOST /{phone-number-id}/calls, call_permissionsThe Calling API with real WebRTC audio: either side calls, with the permission request and reply, pre_accept, accept, reject, terminate and the calls webhooks.
WebhooksPOST /{phone-number-id} (webhook_configuration)Per-number override verified with hub.challenge, signed with x-hub-signature-256, an optional response delay, a log of every attempt.
Everything elsethe rest of the specificationRouted, validated and answered with Meta's own example (Flows, groups, business profile, QR codes…).

Meta's OpenAPI file for v23.0 is vendored in spec/v23.0. Every request is validated against it, every answer is held to its response schemas, and a contract test sends every request example of the specification through the mock. Where the specification contradicts Meta's own examples, the correction is written down with its reason in errata.json.

Calls and numbers

The phone button of the chat header calls with real WebRTC audio, the browser's microphone and speaker. The user calls the business (POST /{phone}/calls with X-Mock-From and the offer) and the application hears calls / connect, answering pre_accept and accept with its SDP. The business calls the user: without the user's permission it sends the call_permission_request first; with it, connect with the offer, and the user's phone rings. Calls stay in the conversation as call log bubbles: answered with the duration, missed, declined.

With nothing typed, the send button is a microphone, as on the phone: it records a voice note (Ogg Opus, mono, 48 kHz) and sends it as audio with voice: true.

  • Create a number. POST /{waba-id}/phone_numbers with cc, phone_number and verified_name: it comes not verified, not registered.
  • Ask for the code. POST /{phone-number-id}/request_code; the code Meta would send shows on the number and in a toast.
  • Verify it. POST /{phone-number-id}/verify_code; a wrong code is (#136025).
  • Register it. POST /{phone-number-id}/register with the six-digit PIN; the first one is kept, another is (#133005).
  • Turn calling on. POST /{phone-number-id}/settings with calling; it needs the number registered and a webhook ((#138018)).
  • Take it off the Cloud API. POST /{phone-number-id}/deregister. A number off the Cloud API cannot send ((#133010)) nor call.

The panel

Open http://localhost:3010. It speaks English and Portuguese and keeps no private routes: every action is the Cloud API call your application would make.

Templates from files

Every JSON in templates/ is imported at start and when it changes, and templates/examples/ brings one example of every component Meta allows. Details in docs/templates.md.

Configuration

VariableDefaultWhat it does
PORT3010HTTP port
WEBHOOK_URL(empty)Default webhook; phone:url maps a number
WEBHOOK_SECRET(empty)Signs webhooks with x-hub-signature-256
WEBHOOK_LOCALHOST_REWRITE(empty)Host that replaces localhost in webhook URLs (host.docker.internal)
MOCK_WEBHOOK_RESPONSE_DELAY_MS5000Wait before each webhook delivery (also in the panel)
MOCK_DELIVERY_MODEautomanual: ticks only move when the user's phone gives a receipt
MOCK_TEMPLATE_REVIEW_DELAY_MS2000Default duration of a template review
WHAP_CALL_RING_MS60000How long a call rings before it ends unanswered
WHAP_DATA_DIR/app/data in the imageSQLite database and media files
WHAP_DB(file):memory: keeps nothing
MOCK_PHONE_NUMBER_ID, MOCK_WABA_ID, MOCK_BUSINESS_ID1234567890, 100000000000001, 100000000000002The ids of the default number, WABA and business
MOCK_ALLOWED_HOSTS(empty)Extra Host names accepted (a compose service name)

Development

bun install
bun run src/index.ts server          # http://localhost:3010
bun --bun vitest run --maxWorkers=2  # tests, including the contract test
bun tsc --noEmit

A new Graph version is a new spec/<version> folder and an entry in src/graph/versions.ts; the contract test runs every version. More in docs/panel.md and the README.

License

Apache-2.0, see LICENSE. Meta's OpenAPI specification in spec/ is MIT, Copyright (c) Meta Platforms, Inc. and affiliates.

Not affiliated with Meta or WhatsApp. An independent simulator of their public API for local development and testing. WhatsApp and Meta are trademarks of their respective owners.