# ZapClaw > ZapClaw is a lightweight WhatsApp message gateway. It connects WhatsApp Business > numbers to the official Meta Cloud API and bridges messages to and from external > systems: send messages via a REST API, and receive inbound messages and delivery > status updates on your own webhook URL. Last updated: 24 July 2026. Portuguese summary (resumo em português): https://zapclaw.app/llms-pt.txt Sign up: https://app.zapclaw.app/register ## Who it fits, and who it does not Fits: developers, makers and agencies who already built or are building an automation, a chatbot or an AI agent on WhatsApp and need the number not to be blocked. ZapClaw runs on the official Meta Cloud API rather than on an unofficial reverse-engineered library, and the integration model is a webhook in and a REST call out, so moving an existing bot means changing the endpoint and the auth header, not rewriting it. Also fits agencies: numbers are unlimited on the paid plans and each client is isolated from the others. Does not fit: bulk broadcasting, anyone who needs more than 20 messages per second, and anyone who depends on groups, voice or video calls, or the Official Business Account green badge. None of that is supported under Coexistence, and the limitation is Meta's, not ZapClaw's. ## Pricing Amounts in Brazilian reais, charged by ZapClaw. Meta bills its own conversation fees separately and directly, and ZapClaw does not mark them up. - Free: R$ 0. 500 messages/month, 1 WhatsApp number, 1 message per second, 3-day message logs, 2 API keys. - Negócio: R$ 99/month. 10,000 messages included, unlimited WhatsApp numbers, 20 messages per second, 90-day logs, unlimited API keys. Extra messages from R$ 2.80 per 1,000. - Agência: R$ 149/month. 3 clients included, 30,000 messages (10,000 per client, pooled), unlimited numbers, 20 messages per second per client, 90-day logs. Extra messages at R$ 1.50 per 1,000; extra clients from R$ 30 each. Annual billing takes 25% off the base fee (R$ 891 and R$ 1,341, charged once); the message allowance still resets monthly. Going over the allowance behaves differently by plan, which is the detail most often got wrong: on Free, sending PAUSES until the month resets and nothing is charged. On a paid plan the overage is BILLED, not blocked, drawn from a prepaid balance you top up in advance; sending only pauses if that balance runs out. On every plan, RECEIVING never stops: a billing pause affects outbound only, and inbound messages keep arriving and keep being delivered to your webhook. Full pricing: https://zapclaw.app/pricing.html Key facts for integrators: - Base URL: `https://api.zapclaw.app` - Authentication: `X-API-Key` header. Keys are created in the ZapClaw dashboard (format `zc_live_` + 32 characters) and shown only once. - Rate limit: 1000 requests / 15 minutes per API key. - Phone numbers use E.164 digits (e.g. `5511999999999`). ## Documentation - [API & Webhook documentation](https://zapclaw.app/docs.html): full reference for sending text/template/media/sticker/location/contacts/reaction/interactive messages, typing indicators and read receipts, quoted replies, uploading/downloading/deleting media, managing templates, reading and updating the business profile, blocking users, managing QR codes, configuring conversational automation, pricing analytics, webhook payloads, and HMAC signature verification. - [OpenAPI specification](https://zapclaw.app/openapi.yaml): machine-readable OpenAPI 3.0 spec of the public REST API. All endpoints are under the `/api/v1` base path and use `X-API-Key` header auth. Message send endpoints return HTTP 201; `/send/read` and `/send/typing` return 200. ## Chatwoot compatibility ZapClaw ships a drop-in compatibility mode for [Chatwoot](https://www.chatwoot.com/) self-hosted: Chatwoot's native WhatsApp Cloud channel can talk to ZapClaw instead of `graph.facebook.com` directly, skipping the Meta App Review, Business Verification, and Tech Provider onboarding that integrating Chatwoot with Meta directly requires. Setup is two env vars and a restart on the Chatwoot side, no code changes. Native and Chatwoot are independent webhook destinations and can both be active on the same number at once (e.g. Chatwoot for human support, a native endpoint for automation). See [Use ZapClaw with Chatwoot](https://zapclaw.app/docs.html#chatwoot) for the full setup guide and a field-by-field comparison of the two destinations. ## Coexistence limitations ZapClaw connects WhatsApp numbers through Meta Coexistence (Coex) mode. These limits are imposed by Meta and cannot be lifted by ZapClaw: - Throughput is fixed at 20 messages per second, regardless of the WhatsApp messaging tier. This is a Meta limit on Coexistence numbers and cannot be raised. A higher tier raises the daily unique-recipient count but not the per-second send rate. The ZapClaw Free plan is additionally limited to 1 message per second; paid plans run at the full 20. - Messages a business sends from WhatsApp for Windows or WearOS do NOT generate webhooks. ZapClaw cannot forward `message.received`/`message.status` events for them. This is an unavoidable data gap; replies sent from the WhatsApp Business app on Android or iOS are reported normally. - The API does not support voice/video calls, the Official Business Account green badge (OBA), or ephemeral / view-once / group messages. ## REST API endpoints - `POST /api/v1/send/text`: send a text message. Body: `from`, `to`, `text`, optional `previewUrl` (boolean; enables a link preview for the first URL in `text`), optional `replyTo`. - `POST /api/v1/send/template`: send an approved template. Body: `from`, `to`, `templateName`, optional `language`, optional `components`, optional `replyTo`. - `POST /api/v1/send/media`: send media. Body: `from`, `to`, `type` (image/video/document/audio/sticker), exactly one of `link` (public https URL) or `mediaId` (handle from `POST /api/v1/media`), optional `caption`, optional `filename`, optional `replyTo`. A `sticker` (WebP image) takes a `link` or `mediaId` but no `caption`. - `POST /api/v1/send/location`: send a static location pin. Body: `from`, `to`, `latitude` (-90..90), `longitude` (-180..180), optional `name`, optional `address`, optional `replyTo`. Returns 201 `{ "message": {...} }`. - `POST /api/v1/send/contacts`: send one or more contact cards. Body: `from`, `to`, `contacts` (non-empty array of WhatsApp Cloud API contact objects, passed through unchanged), optional `replyTo`. Returns 201 `{ "message": {...} }`. - `POST /api/v1/send/reaction`: react to a message with an emoji. Body: `from`, `to`, `messageId` (wamid being reacted to), optional `emoji`. An empty or omitted `emoji` removes a previous reaction. Returns `{ "message": {...} }`. - `POST /api/v1/send/interactive`: send an interactive message (reply buttons, list, or call-to-action URL). Body: `from`, `to`, `interactive` (a WhatsApp Cloud API interactive object, passed through unchanged), optional `replyTo`. - `POST /api/v1/send/typing`: show a typing indicator to the contact; also marks the message as read. Body: `from`, `messageId` (inbound message wamid). Returns `{ "status": "typing" }`. The indicator clears after ~25s or when the next message is sent. - `POST /api/v1/send/read`: mark an inbound message as read (send a read receipt). Body: `from`, `messageId` (inbound message wamid). Returns `{ "status": "read" }`. - `GET /api/v1/send/numbers`: list the caller's connected WhatsApp numbers; use to discover valid `from` values. Returns `phoneNumberId`, `displayPhoneNumber`, `displayName`, `label`, `qualityRating`, `status` per number. - `GET /api/v1/send/messages`: list recent messages; query params `from`, `limit`. - `GET /api/v1/send/messages/{id}`: look up a single message by its ZapClaw id; 404 if not found. - `GET /api/v1/send/templates`: list approved templates; query param `from`. Each template includes an `id` field (the Meta template id), plus `name`, `language`, `category`, `status`, `bodyText`. - `POST /api/v1/send/templates`: create a message template (submitted to Meta for review, not sendable until approved). Body: `from`, `name`, `category` (UTILITY/MARKETING/AUTHENTICATION), `language`, `components` (non-empty array, Meta format). Returns 201 `{ "template": {...} }`. - `POST /api/v1/send/templates/{id}`: edit a template in place by its Meta template `id` (from `GET /api/v1/send/templates`). Takes the same fields as template creation, plus a raw `components` array. Only the fields you send change: the rest of the template is kept, so sending just `bodyText` leaves the header, footer and buttons alone. Send a field as `null` to remove it. Editable only while the template is APPROVED, REJECTED or PAUSED (a PENDING one answers 409 `TEMPLATE_IN_REVIEW`; use `/templates/recreate`). `name` and `language` cannot change; `category` only changes while REJECTED. An approved template allows one edit per 24h and 10 per 30 days, and Meta re-approves the edit automatically rather than sending it back to review. Returns `{ "updated": true, "template": {...} }`. - `DELETE /api/v1/send/templates/{name}`: delete a template by `name`; query param `from`. Returns `{ "deleted": true }`. - `POST /api/v1/media`: upload a file (multipart/form-data; fields `file`, `from`). Returns `{ media_id, mime_type, file_size }`. Size caps: image 5 MB, video 16 MB, audio 16 MB, document 100 MB; over-cap returns 413. - `GET /api/v1/media/{mediaId}`: download received WhatsApp media; streams the raw bytes with the correct `Content-Type`. 404 if unknown to the account, 410 if the media has aged out on Meta's side (~30 days). - `DELETE /api/v1/media/{mediaId}`: delete uploaded media from Meta; query param `from`. Returns `{ "deleted": true }`. - `GET /api/v1/profile`: read the business profile; query param `from`. Returns `{ "profile": { about, address, description, email, websites, vertical, profile_picture_url } }`. - `PATCH /api/v1/profile`: update business profile text fields. Body: `from`, plus at least one of `about`, `address`, `description`, `email`, `websites`, `vertical`. Returns `{ "updated": true }`. - `POST /api/v1/profile/name`: submit a new display name (goes to Meta review). Body: `from`, `name`. Returns `{ "submitted": true }`. - `POST /api/v1/profile/photo`: set the profile picture (multipart/form-data; fields `file`, `from`; image capped at 5 MB). Returns `{ "updated": true }`. - `GET /api/v1/block`: list blocked users; query param `from`. Returns `{ "blocked": [...] }`. - `POST /api/v1/block`: block users. Body: `from`, `numbers` (non-empty E.164 array). Returns `{ "result": {...} }`. Meta only allows blocking a user who messaged the business within the last 24 hours. - `DELETE /api/v1/block`: unblock users. Body: `from`, `numbers`. Returns `{ "result": {...} }`. - `GET /api/v1/qr-codes`: list QR codes / managed links; query param `from`. Returns `{ "qrCodes": [...] }`. - `POST /api/v1/qr-codes`: create a QR code / managed link. Body: `from`, `prefilledMessage`, optional `imageFormat` (`PNG` default or `SVG`). Returns 201 `{ "qrCode": { code, prefilled_message, deep_link_url (wa.me link), qr_image_url } }`. - `DELETE /api/v1/qr-codes/{code}`: delete a QR code; query param `from`. Returns `{ "deleted": true }`. - `GET /api/v1/automation`: read conversational automation config; query param `from`. Returns `{ "automation": { enable_welcome_message, commands, prompts } }`. - `POST /api/v1/automation`: update conversational automation. Body: `from`, plus at least one of `enableWelcomeMessage` (boolean), `commands` (array of `{ command_name, command_description }`), `prompts` (ice breakers, array of strings, max 4). Returns `{ "updated": true }`. - `GET /api/v1/analytics/pricing`: per-message pricing analytics. Query params: `from`, `start` and `end` (Unix timestamps), optional `granularity` (`DAILY` default or `MONTHLY`). Returns `{ "analytics": {...} }`. `replyTo` (text, template, media, location, contacts, interactive) is the wamid of a message to quote; the message is delivered as a quoted reply in WhatsApp. ## Errors Errors return a non-2xx HTTP status with a JSON body `{ "error": "message" }`. Statuses: 200 (success on read endpoints, `/send/read`, `/send/typing`, and the profile/block/qr-codes/automation update endpoints); 201 (resource created: a message sent, an uploaded file via `POST /media`, a created template via `POST /send/templates`, or a created QR code via `POST /qr-codes`); 202 (accepted for asynchronous Meta processing: a display name change, or a template create/edit queued for Meta review); 400 (invalid request, unknown `from` number, or both `link` and `mediaId` supplied); 401 (missing or invalid API key); 402 (the plan's included messages are used up and the plan does not bill overage, so sending is paused; body carries `code: "message_quota_exceeded"`, `used` and `included`); 403 (either Meta has restricted the WhatsApp Business Account so it cannot send, or the key belongs to a client archived in a ZapClaw agency account); 404 (message, media or template not found, or not owned by the account); 410 (received media aged out on Meta's side, ~30 days); 413 (uploaded file over the size cap); 422 (the 24h customer service window is closed; send a template to reopen); 429 (rate limit exceeded); 500 (unexpected ZapClaw error); 502 (the Meta API returned a failure or could not be reached); 503 (Meta unreachable after retries; safe to retry). Inbound messages are never refused for billing reasons. A 402 pauses outbound sending only; messages keep arriving and are still delivered to your webhook. ## Webhook ZapClaw POSTs JSON events to a webhook URL configured in the dashboard. Events: `message.received` (inbound message), `message.status` (delivery status) and the `account.` family (WABA account events: bans, restrictions, phone number quality changes, template status updates). Account events are always delivered regardless of the configured event subscription. `message.received` `data` fields. Always present: `from`, `message_id` (Meta wamid), `type`, `timestamp`. Conditional: `contact_name` (sender's WhatsApp profile name, present but may be null); `text` (only when `type` is `text`, holds `{ "body": "..." }`); the media object (only on media types); `context` (only on replies); `referral` (only on click-to-WhatsApp ads). Media messages (image/audio/video/document/sticker): the media object inside `data` (keyed by `type`) always carries `id`, `mime_type`, `sha256`, and a ready-to-use `media_url` (a direct `GET /api/v1/media/{id}` URL to fetch with the API key; use it instead of the raw Meta `id`). Audio also carries a `voice` boolean (true for a voice note). Document also carries a `filename`. `caption` appears on image, video and document when the sender added one. Replies: when the contact replies to a message, `data` includes a `context` object `{ "from": "", "id": "" }`. Use `context.id` to know which message the user replied to. `message.status` events report outbound delivery status (sent, delivered, read, failed). When Meta supplies billing details, the `data` object also includes a `conversation` object and a `pricing` object (passed through from Meta unchanged), for per-message billing reconciliation. Both are conditional, present typically on the `sent` or `delivered` status of a billable message. For an aggregated view of cost over a time window, use `GET /api/v1/analytics/pricing`. ZapClaw forwards every field Meta includes in a message or status. Beyond the documented fields, that can include `identity` and `errors` on a message and `biz_opaque_callback_data` on a status; any field Meta adds later is passed through unchanged. Timestamp formats: the top-level `timestamp` is an ISO 8601 string (when ZapClaw delivered the event, e.g. `2026-05-22T17:30:00.000Z`). `data.timestamp` comes from Meta and is Unix epoch seconds as a string (e.g. `"1716394200"`). Do not confuse the two; parse each with its matching format. Headers on every webhook POST: `X-ZapClaw-Event` (event type), `X-ZapClaw-Delivery` (unique delivery attempt id), `X-ZapClaw-Signature` (`sha256=` plus an HMAC-SHA256 of the raw body keyed with your webhook signing secret). Respond 2xx to acknowledge; failed deliveries are retried 3 times (0s, 30s, 120s). `X-ZapClaw-Redelivery` is present only on a manual replay triggered from the dashboard; its value is the original delivery id. A redelivery reuses the original payload (same `message_id`), so if you dedup by `message_id`, treat the presence of `X-ZapClaw-Redelivery` as a signal to process the event anyway. Delivery guarantees: `message_id` (Meta wamid) is stable and globally unique and is safe to use for deduplication. ZapClaw also dedups internally, so it will not POST the same event twice. However delivery order is NOT guaranteed: events are delivered concurrently and retries can reorder them. Every `message.received` event carries a top-level `sequence` (a strictly increasing integer assigned in the order ZapClaw received the message from Meta); order messages by `sequence`, since `data.timestamp` has only second granularity and ties on rapid-fire messages. ## Changelog Most recent first: - Chatwoot compatibility: a Meta-compatible REST surface plus webhook passthrough that lets Chatwoot self-hosted's native WhatsApp Cloud channel point at ZapClaw instead of Meta directly, no App Review needed on the Chatwoot side. Native and Chatwoot webhooks are independent destinations and can run at the same time on the same number. - Coexistence limitations documented: fixed 20 messages/second throughput, the webhook data gap for messages sent from WhatsApp for Windows or WearOS, and the unsupported features (voice/video calls, OBA green badge, ephemeral/view-once/ group messages). - Status webhook billing fields: `message.status` `data` now includes Meta's `conversation` and `pricing` objects when present. - Template management: `POST /api/v1/send/templates` (create), `POST /api/v1/send/templates/{id}` (edit in place, keeping the template id and its history), `DELETE /api/v1/send/templates/{name}` (delete); `GET /api/v1/send/templates` now returns an `id` (Meta template id). - Pricing analytics: `GET /api/v1/analytics/pricing`. - Conversational automation: `GET /api/v1/automation`, `POST /api/v1/automation`. - QR codes: `POST /api/v1/qr-codes`, `GET /api/v1/qr-codes`, `DELETE /api/v1/qr-codes/{code}`. - Block API: `POST /api/v1/block`, `DELETE /api/v1/block`, `GET /api/v1/block`. - Business profile: `GET /api/v1/profile`, `PATCH /api/v1/profile`, `POST /api/v1/profile/name`, `POST /api/v1/profile/photo`. - Media delete: `DELETE /api/v1/media/{mediaId}`. - New message types: `POST /api/v1/send/location`, `POST /api/v1/send/contacts`; `POST /api/v1/send/media` accepts `type: sticker`; `POST /api/v1/send/text` accepts an optional `previewUrl` boolean for link previews. - Media download/upload endpoints (`GET /api/v1/media/{mediaId}`, `POST /api/v1/media`). - Single-message lookup (`GET /api/v1/send/messages/{id}`), connected-numbers (`GET /api/v1/send/numbers`), read receipts (`POST /api/v1/send/read`). - Typing indicator (`POST /api/v1/send/typing`), reactions (`POST /api/v1/send/reaction`), interactive messages (`POST /api/v1/send/interactive`), quoted replies (optional `replyTo` on the text/template/media send endpoints). - Enriched webhook payload: `media_url`, `contact_name`, `context`, `referral`. - `account.` webhook events for WABA-level alerts. ## Legal - [Privacy Policy](https://zapclaw.app/privacy.html) - [Terms of Service](https://zapclaw.app/terms.html) - [Data Deletion](https://zapclaw.app/data-deletion.html)