API & Webhook Documentation
Send and receive WhatsApp messages through the ZapClaw gateway.
ZapClaw bridges WhatsApp Business numbers and your systems: send messages with a REST API, and receive inbound messages and delivery status updates on your own webhook URL. A machine-readable OpenAPI specification is available at openapi.yaml.
Last updated: 2026-05-22
- Getting started
- Endpoint reference — every public endpoint at a glance
- Coexistence limitations — what the gateway cannot do
- API — authentication, send endpoints, media, profile, account features, analytics, errors, rate limits
- Webhook — configuration, event payloads, signature verification, retries
- Errors — every HTTP status the API can return
- Use ZapClaw with Chatwoot — opt-in passthrough mode for Chatwoot self-hosted
- Changelog
Getting started
Base URL
https://api.zapclaw.app
Authentication
Every REST API request is authenticated with an API key sent in the
X-API-Key header. Create and manage keys in the ZapClaw dashboard under
API Keys. The full key is shown only once, on creation — store it securely.
X-API-Key: zc_live_xxxxxxxxxxxxxxxxxxxxxxxx
Requests carry a JSON body and should include Content-Type: application/json.
Endpoint reference
Every public endpoint at a glance. All paths are relative to the base URL and
sit under the /api/v1 base path. Every endpoint is authenticated
with the X-API-Key header.
| Method | Path | Description |
|---|---|---|
POST | /send/text | Send a text message |
POST | /send/template | Send an approved template message |
POST | /send/media | Send an image, video, document, audio or sticker message |
POST | /send/location | Send a static location pin |
POST | /send/contacts | Send one or more contact cards |
POST | /send/reaction | React to a message with an emoji |
POST | /send/interactive | Send an interactive message (buttons, list, call-to-action URL) |
POST | /send/typing | Show a typing indicator (also marks the message as read) |
POST | /send/read | Mark an inbound message as read |
GET | /send/messages | List recent messages |
GET | /send/messages/{id} | Get a single message by its ZapClaw id |
GET | /send/numbers | List connected WhatsApp numbers |
GET | /send/templates | List approved templates |
POST | /send/templates | Create a message template |
POST | /send/templates/{id} | Edit a template by its Meta template id |
DELETE | /send/templates/{name} | Delete a template by name |
GET | /media/{mediaId} | Download received media |
POST | /media | Upload a media file |
DELETE | /media/{mediaId} | Delete uploaded media from Meta |
GET | /profile | Read the business profile |
PATCH | /profile | Update business profile text fields |
POST | /profile/name | Submit a new display name |
POST | /profile/photo | Set the profile picture |
GET | /block | List blocked users |
POST | /block | Block users |
DELETE | /block | Unblock users |
GET | /qr-codes | List QR codes / managed links |
POST | /qr-codes | Create a QR code / managed link |
DELETE | /qr-codes/{code} | Delete a QR code |
GET | /automation | Read conversational automation config |
POST | /automation | Update conversational automation config |
GET | /analytics/pricing | Per-message pricing analytics |
Coexistence limitations
ZapClaw connects WhatsApp Business numbers through Meta's Coexistence (Coex) mode, which lets a business keep using the WhatsApp Business app on a phone while the same number is also reachable through the Cloud API. Coexistence carries a few hard constraints that you should design around. They are imposed by Meta and cannot be lifted by ZapClaw.
message.received or message.status event for it.
This is an unavoidable data gap: your records may be missing messages the
business sent from those surfaces. Replies sent from the WhatsApp Business
app on Android or iOS are reported normally.
API
Conventions
- from — the sender: one of your connected WhatsApp numbers, in E.164 digits
(e.g.
5511999999999). It identifies which number the message is sent from. - to — the recipient. Accepts either an E.164 phone number
(preferred when known) or a Meta Business-Scoped User ID — the
CC.alphanumericstring ZapClaw delivers asuser_idin inbound webhooks. ZapClaw auto-detects which form you sent. Phone is preferred because it preserves your right to keep receiving the phone in webhooks for that recipient; use the BSUID only for username-only contacts (June 2026+). - All responses are JSON. Successful sends return HTTP
201.
Send a text message
Body parameters
| Field | Type | Required | Description |
|---|---|---|---|
from | string | yes | Sender number (E.164) |
to | string | yes | Recipient number (E.164) |
text | string | yes | Message body, up to 4096 characters |
previewUrl | boolean | no | When true, WhatsApp renders a link preview for the first URL found in text. Defaults to false. |
replyTo | string | no | The wamid of a message to quote. The message is delivered as a quoted reply in WhatsApp. |
Request
curl -X POST https://api.zapclaw.app/api/v1/send/text \
-H "X-API-Key: zc_live_xxx" \
-H "Content-Type: application/json" \
-d '{
"from": "5511999999999",
"to": "5511888888888",
"text": "Hello from ZapClaw"
}'
Response 201
{
"message": {
"id": "9f1c2d3e-...",
"to": "5511888888888",
"type": "text",
"wamid": "wamid.HBg...",
"status": "sent",
"sentAt": "2026-05-21T14:00:00.000Z"
}
}
Send a template message
Template messages can be sent at any time (outside the 24-hour customer service window). The template must be approved on your WhatsApp Business Account.
Body parameters
| Field | Type | Required | Description |
|---|---|---|---|
from | string | yes | Sender number (E.164) |
to | string | yes | Recipient number (E.164) |
templateName | string | yes | Approved template name |
language | string | no | Template language code (default pt_BR) |
components | array | no | Template components to fill variables (Meta format) |
replyTo | string | no | The wamid of a message to quote. The message is delivered as a quoted reply in WhatsApp. |
Request
curl -X POST https://api.zapclaw.app/api/v1/send/template \
-H "X-API-Key: zc_live_xxx" \
-H "Content-Type: application/json" \
-d '{
"from": "5511999999999",
"to": "5511888888888",
"templateName": "order_update",
"language": "pt_BR",
"components": [
{ "type": "body",
"parameters": [ { "type": "text", "text": "#1234" } ] }
]
}'
Send a media message
The media is referenced either by a public link or by a
mediaId handle obtained from Upload media.
Provide exactly one of the two.
Set type to sticker to send a WhatsApp sticker.
A sticker takes a link or a mediaId like the other
media types, but it does not accept a caption. Stickers must be
WebP images.
Body parameters
| Field | Type | Required | Description |
|---|---|---|---|
from | string | yes | Sender number (E.164) |
to | string | yes | Recipient number (E.164) |
type | string | yes | One of: image, video, document, audio, sticker |
link | string | one of | Public https URL of the media file |
mediaId | string | one of | Media handle from POST /api/v1/media |
caption | string | no | Caption (image, video, document). Not supported for sticker or audio. |
filename | string | no | File name (document only) |
replyTo | string | no | The wamid of a message to quote. The message is delivered as a quoted reply in WhatsApp. |
Exactly one of link or mediaId is required.
Sending both, or neither, returns HTTP 400.
Request — by public URL
curl -X POST https://api.zapclaw.app/api/v1/send/media \
-H "X-API-Key: zc_live_xxx" \
-H "Content-Type: application/json" \
-d '{
"from": "5511999999999",
"to": "5511888888888",
"type": "image",
"link": "https://example.com/photo.jpg",
"caption": "Optional caption"
}'
Request — by uploaded media handle
curl -X POST https://api.zapclaw.app/api/v1/send/media \
-H "X-API-Key: zc_live_xxx" \
-H "Content-Type: application/json" \
-d '{
"from": "5511999999999",
"to": "5511888888888",
"type": "image",
"mediaId": "1029384756102938",
"caption": "Optional caption"
}'
Request — sticker
curl -X POST https://api.zapclaw.app/api/v1/send/media \
-H "X-API-Key: zc_live_xxx" \
-H "Content-Type: application/json" \
-d '{
"from": "5511999999999",
"to": "5511888888888",
"type": "sticker",
"link": "https://example.com/sticker.webp"
}'
Send a location
Sends a static location pin. The recipient sees a map preview they can open in their maps app.
Body parameters
| Field | Type | Required | Description |
|---|---|---|---|
from | string | yes | Sender number (E.164) |
to | string | yes | Recipient number (E.164) |
latitude | number | yes | Latitude, between -90 and 90 |
longitude | number | yes | Longitude, between -180 and 180 |
name | string | no | Name of the location (for example a venue name) |
address | string | no | Street address of the location |
replyTo | string | no | The wamid of a message to quote. The message is delivered as a quoted reply in WhatsApp. |
Request
curl -X POST https://api.zapclaw.app/api/v1/send/location \
-H "X-API-Key: zc_live_xxx" \
-H "Content-Type: application/json" \
-d '{
"from": "5511999999999",
"to": "5511888888888",
"latitude": -23.561414,
"longitude": -46.655881,
"name": "Avenida Paulista",
"address": "Av. Paulista, 1578 - São Paulo, SP"
}'
Response 201
{
"message": {
"id": "9f1c2d3e-...",
"to": "5511888888888",
"type": "location",
"wamid": "wamid.HBg...",
"status": "sent",
"sentAt": "2026-05-22T14:00:00.000Z"
}
}
Send contacts
Sends one or more contact cards. The contacts array follows the
WhatsApp Cloud API contacts object shape and is passed through to Meta
unchanged.
Body parameters
| Field | Type | Required | Description |
|---|---|---|---|
from | string | yes | Sender number (E.164) |
to | string | yes | Recipient number (E.164) |
contacts | array | yes | A non-empty array of WhatsApp Cloud API contact objects |
replyTo | string | no | The wamid of a message to quote. The message is delivered as a quoted reply in WhatsApp. |
Request
curl -X POST https://api.zapclaw.app/api/v1/send/contacts \
-H "X-API-Key: zc_live_xxx" \
-H "Content-Type: application/json" \
-d '{
"from": "5511999999999",
"to": "5511888888888",
"contacts": [
{
"name": {
"formatted_name": "Maria Silva",
"first_name": "Maria",
"last_name": "Silva"
},
"phones": [
{ "phone": "+5511777777777", "type": "WORK", "wa_id": "5511777777777" }
]
}
]
}'
Response 201
{
"message": {
"id": "9f1c2d3e-...",
"to": "5511888888888",
"type": "contacts",
"wamid": "wamid.HBg...",
"status": "sent",
"sentAt": "2026-05-22T14:00:00.000Z"
}
}
React to a message
Reacts to a message with an emoji. An empty or omitted emoji
removes a reaction you previously sent.
Body parameters
| Field | Type | Required | Description |
|---|---|---|---|
from | string | yes | Sender number (E.164) |
to | string | yes | Recipient number (E.164) |
messageId | string | yes | The wamid of the message being reacted to |
emoji | string | no | The reaction emoji. Empty or omitted removes a previous reaction. |
Request
curl -X POST https://api.zapclaw.app/api/v1/send/reaction \
-H "X-API-Key: zc_live_xxx" \
-H "Content-Type: application/json" \
-d '{
"from": "5511999999999",
"to": "5511888888888",
"messageId": "wamid.HBg...",
"emoji": "👍"
}'
Response 201
{
"message": {
"id": "9f1c2d3e-...",
"to": "5511888888888",
"type": "reaction",
"wamid": "wamid.HBg...",
"status": "sent",
"sentAt": "2026-05-22T14:00:00.000Z"
}
}
Send an interactive message
Sends an interactive message: reply buttons, a list, or a call-to-action URL.
The interactive object follows the WhatsApp Cloud API interactive
object shape and is passed through to Meta unchanged.
Body parameters
| Field | Type | Required | Description |
|---|---|---|---|
from | string | yes | Sender number (E.164) |
to | string | yes | Recipient number (E.164) |
interactive | object | yes | The WhatsApp Cloud API interactive object |
replyTo | string | no | The wamid of a message to quote. The message is delivered as a quoted reply in WhatsApp. |
Request — reply buttons
curl -X POST https://api.zapclaw.app/api/v1/send/interactive \
-H "X-API-Key: zc_live_xxx" \
-H "Content-Type: application/json" \
-d '{
"from": "5511999999999",
"to": "5511888888888",
"interactive": {
"type": "button",
"body": { "text": "Did your order arrive?" },
"action": {
"buttons": [
{ "type": "reply",
"reply": { "id": "yes", "title": "Yes" } },
{ "type": "reply",
"reply": { "id": "no", "title": "No" } }
]
}
}
}'
Request — list
curl -X POST https://api.zapclaw.app/api/v1/send/interactive \
-H "X-API-Key: zc_live_xxx" \
-H "Content-Type: application/json" \
-d '{
"from": "5511999999999",
"to": "5511888888888",
"interactive": {
"type": "list",
"header": { "type": "text", "text": "Support" },
"body": { "text": "How can we help you today?" },
"footer": { "text": "Pick an option" },
"action": {
"button": "Choose a topic",
"sections": [
{
"title": "Orders",
"rows": [
{ "id": "track", "title": "Track an order",
"description": "See where your package is" },
{ "id": "cancel", "title": "Cancel an order",
"description": "Cancel a recent purchase" }
]
},
{
"title": "Account",
"rows": [
{ "id": "billing", "title": "Billing question" }
]
}
]
}
}
}'
Response 201
{
"message": {
"id": "9f1c2d3e-...",
"to": "5511888888888",
"type": "interactive",
"wamid": "wamid.HBg...",
"status": "sent",
"sentAt": "2026-05-22T14:00:00.000Z"
}
}
Show a typing indicator
Shows a typing indicator to the contact. This also marks the referenced inbound message as read. The indicator clears after about 25 seconds, or as soon as the next message is sent, whichever comes first.
Body parameters
| Field | Type | Required | Description |
|---|---|---|---|
from | string | yes | The connected number that received the message (E.164) |
messageId | string | yes | The wamid of the inbound message to respond to |
Request
curl -X POST https://api.zapclaw.app/api/v1/send/typing \
-H "X-API-Key: zc_live_xxx" \
-H "Content-Type: application/json" \
-d '{
"from": "5511999999999",
"messageId": "wamid.HBg..."
}'
Response 200
{ "status": "typing" }
Upload media
Uploads a file and returns a media_id handle that can then be sent
with Send a media message. Useful when the file is not
reachable at a public URL. The request is multipart/form-data.
Form fields
| Field | Type | Required | Description |
|---|---|---|---|
file | binary | yes | The media file to upload |
from | string | yes | A connected WhatsApp number (E.164) |
Size limits
| Type | Maximum size |
|---|---|
| Image | 5 MB |
| Video | 16 MB |
| Audio | 16 MB |
| Document | 100 MB |
A file larger than the cap for its type returns HTTP 413.
Request
curl -X POST https://api.zapclaw.app/api/v1/media \
-H "X-API-Key: zc_live_xxx" \
-F "from=5511999999999" \
-F "file=@/path/to/photo.jpg"
Response 201
{
"media_id": "1029384756102938",
"mime_type": "image/jpeg",
"file_size": 12345
}
Download received media
Downloads the bytes of media received on an inbound WhatsApp message — an
image, audio, video or document. The response is the raw file, streamed with
the correct Content-Type header.
Inbound media messages arrive on your webhook with a ready-to-use
media_url field that points at this endpoint — fetch that URL with
your API key rather than building the path by hand.
Request
curl https://api.zapclaw.app/api/v1/media/1029384756102938 \
-H "X-API-Key: zc_live_xxx" \
-o received-photo.jpg
Errors
| Status | Meaning |
|---|---|
404 | The media id is unknown to your account |
410 | The media has aged out on Meta's side (around 30 days) |
Delete uploaded media
Deletes a media file you uploaded to Meta with Upload media. Use this to release a media handle once you no longer need it. Uploaded media also expires on Meta's side automatically after about 30 days.
Query parameters
| Param | Required | Description |
|---|---|---|
from | yes | A connected WhatsApp number (E.164) |
Request
curl -X DELETE "https://api.zapclaw.app/api/v1/media/1029384756102938?from=5511999999999" \
-H "X-API-Key: zc_live_xxx"
Response 200
{ "deleted": true }
Mark a message as read
Sends a read receipt for an inbound message, marking it as read in the sender's WhatsApp app.
Body parameters
| Field | Type | Required | Description |
|---|---|---|---|
from | string | yes | The connected number that received the message (E.164) |
messageId | string | yes | The inbound message wamid (Meta message id) to mark as read |
Request
curl -X POST https://api.zapclaw.app/api/v1/send/read \
-H "X-API-Key: zc_live_xxx" \
-H "Content-Type: application/json" \
-d '{
"from": "5511999999999",
"messageId": "wamid.HBg..."
}'
Response 200
{ "status": "read" }
List connected numbers
Lists your connected WhatsApp numbers. Use this to discover valid
from values for the send endpoints.
Request
curl https://api.zapclaw.app/api/v1/send/numbers \
-H "X-API-Key: zc_live_xxx"
Response 200
{
"numbers": [
{
"phoneNumberId": "964283400112599",
"displayPhoneNumber": "+1 555 161-7409",
"displayName": "My Business",
"label": "Support line",
"qualityRating": "GREEN",
"status": "active"
}
]
}
List messages
Lists recent messages — useful for delivery status lookups.
Query parameters
| Param | Required | Description |
|---|---|---|
from | no | Filter by sender number (E.164) |
limit | no | Max results, 1–100 (default 20) |
curl "https://api.zapclaw.app/api/v1/send/messages?limit=20" \
-H "X-API-Key: zc_live_xxx"
Get a single message
Looks up a single message by its ZapClaw id. Returns the same message shape
as List messages, or HTTP 404 if no
message with that id belongs to your account.
Request
curl https://api.zapclaw.app/api/v1/send/messages/9f1c2d3e-... \
-H "X-API-Key: zc_live_xxx"
Response 200
{
"message": {
"id": "9f1c2d3e-...",
"direction": "outbound",
"to": "5511888888888",
"type": "text",
"wamid": "wamid.HBg...",
"status": "delivered",
"sentAt": "2026-05-21T14:00:00.000Z",
"createdAt": "2026-05-21T14:00:00.000Z"
}
}
List templates
Lists approved templates available to send. Optional query param from (E.164)
selects which connected number's templates to return.
Request
curl "https://api.zapclaw.app/api/v1/send/templates?from=5511999999999" \
-H "X-API-Key: zc_live_xxx"
Response 200
Each template carries an id field: the Meta template id. Pass it
to Edit a template.
{
"templates": [
{
"id": "1234567890123456",
"name": "order_update",
"language": "pt_BR",
"category": "UTILITY",
"status": "APPROVED",
"bodyText": "Your order {{1}} has shipped."
}
]
}
Create a template
Creates a message template and submits it to Meta for review. A newly
created template is not sendable until Meta approves it; poll
List templates or watch for an
account.<field> webhook to learn when its status changes.
Body parameters
| Field | Type | Required | Description |
|---|---|---|---|
from | string | yes | A connected WhatsApp number (E.164) |
name | string | yes | Template name (lowercase, digits and underscores) |
category | string | yes | Template category: UTILITY, MARKETING or AUTHENTICATION |
language | string | yes | Template language code (for example pt_BR) |
components | array | yes | Non-empty array of template components (Meta format) |
Request
curl -X POST https://api.zapclaw.app/api/v1/send/templates \
-H "X-API-Key: zc_live_xxx" \
-H "Content-Type: application/json" \
-d '{
"from": "5511999999999",
"name": "order_shipped",
"category": "UTILITY",
"language": "pt_BR",
"components": [
{ "type": "BODY",
"text": "Your order {{1}} has shipped." }
]
}'
Response 201
{
"template": {
"id": "1234567890123456",
"status": "PENDING",
"category": "UTILITY"
}
}
Edit a template
Edits an existing template, identified by its Meta template id
(the id field returned by List
templates). Provide at least one of category or
components. An edited template is re-submitted to Meta for
review.
Body parameters
| Field | Type | Required | Description |
|---|---|---|---|
from | string | yes | A connected WhatsApp number (E.164) |
category | string | one of | New template category |
components | array | one of | New template components (Meta format) |
At least one of category or components is required.
Request
curl -X POST https://api.zapclaw.app/api/v1/send/templates/1234567890123456 \
-H "X-API-Key: zc_live_xxx" \
-H "Content-Type: application/json" \
-d '{
"from": "5511999999999",
"components": [
{ "type": "BODY",
"text": "Your order {{1}} is on its way." }
]
}'
Response 200
{ "updated": true }
Delete a template
Deletes a template by its name.
Query parameters
| Param | Required | Description |
|---|---|---|
from | yes | A connected WhatsApp number (E.164) |
Request
curl -X DELETE "https://api.zapclaw.app/api/v1/send/templates/order_shipped?from=5511999999999" \
-H "X-API-Key: zc_live_xxx"
Response 200
{ "deleted": true }
Read the business profile
Reads the WhatsApp business profile for one of your connected numbers.
Query parameters
| Param | Required | Description |
|---|---|---|
from | yes | A connected WhatsApp number (E.164) |
Request
curl "https://api.zapclaw.app/api/v1/profile?from=5511999999999" \
-H "X-API-Key: zc_live_xxx"
Response 200
{
"profile": {
"about": "Open Monday to Friday, 9am to 6pm",
"address": "Av. Paulista, 1578 - São Paulo, SP",
"description": "We sell handmade leather goods.",
"email": "contato@example.com",
"websites": [ "https://example.com" ],
"vertical": "RETAIL",
"profile_picture_url": "https://scontent.whatsapp.net/..."
}
}
Update the business profile
Updates the business profile text fields. Provide at least one field beyond
from. To change the display name, use
Submit a display name; to change the photo, use
Set the profile picture.
Body parameters
| Field | Type | Required | Description |
|---|---|---|---|
from | string | yes | A connected WhatsApp number (E.164) |
about | string | no | The profile "about" text |
address | string | no | Business street address |
description | string | no | Business description |
email | string | no | Contact email |
websites | array | no | Up to two website URLs |
vertical | string | no | Business industry vertical (Meta enum) |
Request
curl -X PATCH https://api.zapclaw.app/api/v1/profile \
-H "X-API-Key: zc_live_xxx" \
-H "Content-Type: application/json" \
-d '{
"from": "5511999999999",
"about": "Open Monday to Friday, 9am to 6pm",
"email": "contato@example.com",
"websites": [ "https://example.com" ]
}'
Response 200
{ "updated": true }
Submit a display name
Submits a new display name for one of your connected numbers. A display name change is reviewed by Meta before it takes effect, so the response confirms the request was submitted, not that the name is already live.
Body parameters
| Field | Type | Required | Description |
|---|---|---|---|
from | string | yes | A connected WhatsApp number (E.164) |
name | string | yes | The new display name |
Request
curl -X POST https://api.zapclaw.app/api/v1/profile/name \
-H "X-API-Key: zc_live_xxx" \
-H "Content-Type: application/json" \
-d '{
"from": "5511999999999",
"name": "My Business"
}'
Response 200
{ "submitted": true }
Set the profile picture
Sets the business profile picture. The request is multipart/form-data.
The image file is capped at 5 MB.
Form fields
| Field | Type | Required | Description |
|---|---|---|---|
file | binary | yes | The profile picture image |
from | string | yes | A connected WhatsApp number (E.164) |
Request
curl -X POST https://api.zapclaw.app/api/v1/profile/photo \
-H "X-API-Key: zc_live_xxx" \
-F "from=5511999999999" \
-F "file=@/path/to/logo.jpg"
Response 200
{ "updated": true }
List blocked users
Lists the users currently blocked on one of your connected numbers.
Query parameters
| Param | Required | Description |
|---|---|---|
from | yes | A connected WhatsApp number (E.164) |
Request
curl "https://api.zapclaw.app/api/v1/block?from=5511999999999" \
-H "X-API-Key: zc_live_xxx"
Response 200
{
"blocked": [
{ "wa_id": "5511777777777" }
]
}
Block users
Blocks one or more users on a connected number. A blocked user can no longer send messages to that number.
result.
Body parameters
| Field | Type | Required | Description |
|---|---|---|---|
from | string | yes | A connected WhatsApp number (E.164) |
numbers | array | yes | Non-empty array of recipient numbers (E.164) to block |
Request
curl -X POST https://api.zapclaw.app/api/v1/block \
-H "X-API-Key: zc_live_xxx" \
-H "Content-Type: application/json" \
-d '{
"from": "5511999999999",
"numbers": [ "5511777777777" ]
}'
Response 200
{
"result": {
"added_users": [
{ "input": "5511777777777", "wa_id": "5511777777777" }
]
}
}
Unblock users
Unblocks one or more previously blocked users on a connected number.
Body parameters
| Field | Type | Required | Description |
|---|---|---|---|
from | string | yes | A connected WhatsApp number (E.164) |
numbers | array | yes | Non-empty array of recipient numbers (E.164) to unblock |
Request
curl -X DELETE https://api.zapclaw.app/api/v1/block \
-H "X-API-Key: zc_live_xxx" \
-H "Content-Type: application/json" \
-d '{
"from": "5511999999999",
"numbers": [ "5511777777777" ]
}'
Response 200
{
"result": {
"removed_users": [
{ "input": "5511777777777", "wa_id": "5511777777777" }
]
}
}
List QR codes
Lists the QR codes (managed links) created on one of your connected numbers.
A QR code links to a wa.me conversation that opens with a
prefilled message.
Query parameters
| Param | Required | Description |
|---|---|---|
from | yes | A connected WhatsApp number (E.164) |
Request
curl "https://api.zapclaw.app/api/v1/qr-codes?from=5511999999999" \
-H "X-API-Key: zc_live_xxx"
Response 200
{
"qrCodes": [
{
"code": "4O4ABCDEFG123",
"prefilled_message": "Hi, I would like to know more",
"deep_link_url": "https://wa.me/message/4O4ABCDEFG123",
"qr_image_url": "https://scontent.whatsapp.net/v/..."
}
]
}
Create a QR code
Creates a QR code (managed link) for one of your connected numbers.
Body parameters
| Field | Type | Required | Description |
|---|---|---|---|
from | string | yes | A connected WhatsApp number (E.164) |
prefilledMessage | string | yes | The message text prefilled when a user opens the link |
imageFormat | string | no | Format of the QR image: PNG (default) or SVG |
Request
curl -X POST https://api.zapclaw.app/api/v1/qr-codes \
-H "X-API-Key: zc_live_xxx" \
-H "Content-Type: application/json" \
-d '{
"from": "5511999999999",
"prefilledMessage": "Hi, I would like to know more",
"imageFormat": "PNG"
}'
Response 201
{
"qrCode": {
"code": "4O4ABCDEFG123",
"prefilled_message": "Hi, I would like to know more",
"deep_link_url": "https://wa.me/message/4O4ABCDEFG123",
"qr_image_url": "https://scontent.whatsapp.net/v/..."
}
}
Delete a QR code
Deletes a QR code by its code identifier.
Query parameters
| Param | Required | Description |
|---|---|---|
from | yes | A connected WhatsApp number (E.164) |
Request
curl -X DELETE "https://api.zapclaw.app/api/v1/qr-codes/4O4ABCDEFG123?from=5511999999999" \
-H "X-API-Key: zc_live_xxx"
Response 200
{ "deleted": true }
Read conversational automation
Reads the conversational automation configuration for a connected number: the welcome message toggle, slash commands, and ice breaker prompts.
Query parameters
| Param | Required | Description |
|---|---|---|
from | yes | A connected WhatsApp number (E.164) |
Request
curl "https://api.zapclaw.app/api/v1/automation?from=5511999999999" \
-H "X-API-Key: zc_live_xxx"
Response 200
{
"automation": {
"enable_welcome_message": true,
"commands": [
{ "command_name": "menu",
"command_description": "Show the main menu" }
],
"prompts": [ "Track my order", "Talk to support" ]
}
}
Update conversational automation
Updates the conversational automation configuration. Provide at least one
field beyond from.
Body parameters
| Field | Type | Required | Description |
|---|---|---|---|
from | string | yes | A connected WhatsApp number (E.164) |
enableWelcomeMessage | boolean | no | Whether the welcome message is shown when a customer opens a new conversation |
commands | array | no | Array of { command_name, command_description } objects |
prompts | array | no | Ice breaker prompts: an array of strings, maximum 4 |
Request
curl -X POST https://api.zapclaw.app/api/v1/automation \
-H "X-API-Key: zc_live_xxx" \
-H "Content-Type: application/json" \
-d '{
"from": "5511999999999",
"enableWelcomeMessage": true,
"commands": [
{ "command_name": "menu",
"command_description": "Show the main menu" }
],
"prompts": [ "Track my order", "Talk to support" ]
}'
Response 200
{ "updated": true }
Pricing analytics
Returns Meta's per-message pricing analytics for a time window. Use it to
reconcile billing alongside the pricing and
conversation objects on the
message.status webhook.
Query parameters
| Param | Required | Description |
|---|---|---|
from | yes | A connected WhatsApp number (E.164) |
start | yes | Start of the window, a Unix timestamp (epoch seconds) |
end | yes | End of the window, a Unix timestamp (epoch seconds) |
granularity | no | Bucket size: DAILY (default) or MONTHLY |
Request
curl "https://api.zapclaw.app/api/v1/analytics/pricing?from=5511999999999&start=1746057600&end=1748736000&granularity=DAILY" \
-H "X-API-Key: zc_live_xxx"
Response 200
{
"analytics": {
"data_points": [
{
"start": 1746057600,
"end": 1746144000,
"cost": 12.34,
"volume": 420
}
]
}
}
Rate limits
The REST API allows 1000 requests per 15 minutes per API key.
Exceeding the limit returns HTTP 429.
Webhook
Configuring the webhook
In the ZapClaw dashboard, under Webhook, set the URL where ZapClaw should deliver events and choose which events to receive. ZapClaw generates a signing secret — use it to verify that requests genuinely came from ZapClaw.
ZapClaw sends an HTTP POST with a JSON body to your URL for each event.
Respond with any 2xx status to acknowledge.
The events are message.received (inbound message),
message.status (delivery status) and the
account.<field> family (WABA account events). Account events
are always delivered regardless of your event subscription.
Event: message.received
Delivered when one of your numbers receives an inbound WhatsApp message.
{
"event": "message.received",
"sequence": 1042,
"timestamp": "2026-05-21T14:00:00.000Z",
"data": {
"from": "5511888888888",
"user_id": "BR.1A2B3C4D5E6F7G8H9I0J",
"contact_name": "Maria Silva",
"contact_username": "maria_lima",
"message_id": "wamid.HBg...",
"type": "text",
"timestamp": "1747836000",
"text": { "body": "Hello" }
},
"account": {
"phone_number_id": "964283400112599",
"display_name": "My Business",
"display_phone_number": "+1 555 161-7409"
}
}
The top-level sequence is a strictly increasing integer
assigned to every forwarded message.received event in the
order ZapClaw received it from Meta. Because data.timestamp
has only second granularity, messages sent in quick succession can share
a timestamp; order by sequence to break the tie reliably.
user_id field on every webhook on
2026-03-31 — the Business-Scoped User ID. From June 2026 end users can adopt a @username and hide
their phone from businesses they haven't interacted with. When that
happens, from may be null on first-touch events.
Treat user_id as the durable identifier; keep
from only as a display attribute. ZapClaw stores both for
you when known and surfaces contact_username when the user
has set one.
data fields
The Always column marks fields present on every
message.received event. Fields marked Conditional
appear only in the situation described.
| Field | Always | Description |
|---|---|---|
user_id | yes | Meta Business-Scoped User ID (BSUID) of the sender. Format CC.alphanumeric where CC is an ISO country code. Stable per business — use this as your primary key for contacts. |
from | conditional | Sender's phone number in E.164. Present whenever Meta knows it. May be null for username-only contacts on first-touch (June 2026+). |
message_id | yes | Meta message id (wamid) |
type | yes | Message type: text, image, audio, video, document, sticker, and others |
timestamp | yes | Meta's send time, Unix epoch seconds as a string (see Timestamp formats) |
contact_name | conditional | Sender's WhatsApp profile name. Present on every event but may be null when the name is unknown. |
contact_username | conditional | Sender's @username, when they've adopted one (June 2026+). null otherwise. |
| media object ( image, audio, video, document, sticker) | conditional | Present only on media messages. The key matches type. See Media messages. |
text | conditional | Present only when type is text. Holds { "body": "..." }. |
context | conditional | Present only when the message is a reply to (or quote of) another message. See Replies. |
referral | conditional | Present only when the message came from a click-to-WhatsApp ad. Carries the ad attribution. |
identity | conditional | Present only when the contact's identity changed (security notification). |
errors | conditional | Present only when Meta reported an error for the message. |
Timestamp formats
Webhook payloads carry two timestamps in two different formats. Do not confuse them.
| Field | Format | Example |
|---|---|---|
Top-level timestamp | ISO 8601 string (the moment ZapClaw delivered the event) | 2026-05-22T17:30:00.000Z |
data.timestamp | Unix epoch seconds, as a string (comes straight from Meta) | "1716394200" |
timestamp is an ISO 8601 string, while
data.timestamp is epoch seconds expressed as a string. Parse each
one with the matching format.
Media messages
For media messages (image, audio, video,
document, sticker), the media object inside data
includes a ready-to-use media_url field: a direct
download URL you can fetch with your API key. Use
media_url instead of the raw Meta id, which cannot be
resolved on its own.
The media object always carries id, mime_type,
sha256 and media_url. Audio messages also carry a
voice boolean (true for a voice note). Document
messages also carry a filename. The caption field
appears on image, video and document messages when the sender added one.
Image
{
"event": "message.received",
"timestamp": "2026-05-22T14:00:00.000Z",
"data": {
"from": "5511888888888",
"contact_name": "Maria Silva",
"message_id": "wamid.HBg...",
"type": "image",
"timestamp": "1747836000",
"image": {
"id": "1029384756102938",
"mime_type": "image/jpeg",
"sha256": "q1w2e3...",
"caption": "Here is the photo",
"media_url": "https://api.zapclaw.app/api/v1/media/1029384756102938"
}
},
"account": {
"phone_number_id": "964283400112599",
"display_name": "My Business",
"display_phone_number": "+1 555 161-7409"
}
}
Audio
Audio messages carry a voice boolean: true when the
message is a voice note, false for a regular audio file.
{
"event": "message.received",
"timestamp": "2026-05-22T14:00:00.000Z",
"data": {
"from": "5511888888888",
"contact_name": "Maria Silva",
"message_id": "wamid.HBg...",
"type": "audio",
"timestamp": "1747836000",
"audio": {
"id": "2938475610293847",
"mime_type": "audio/ogg; codecs=opus",
"sha256": "a1s2d3...",
"voice": true,
"media_url": "https://api.zapclaw.app/api/v1/media/2938475610293847"
}
},
"account": {
"phone_number_id": "964283400112599",
"display_name": "My Business",
"display_phone_number": "+1 555 161-7409"
}
}
Video
{
"event": "message.received",
"timestamp": "2026-05-22T14:00:00.000Z",
"data": {
"from": "5511888888888",
"contact_name": "Maria Silva",
"message_id": "wamid.HBg...",
"type": "video",
"timestamp": "1747836000",
"video": {
"id": "3847561029384756",
"mime_type": "video/mp4",
"sha256": "z1x2c3...",
"caption": "Watch this",
"media_url": "https://api.zapclaw.app/api/v1/media/3847561029384756"
}
},
"account": {
"phone_number_id": "964283400112599",
"display_name": "My Business",
"display_phone_number": "+1 555 161-7409"
}
}
Document
Document messages carry a filename with the original file name.
{
"event": "message.received",
"timestamp": "2026-05-22T14:00:00.000Z",
"data": {
"from": "5511888888888",
"contact_name": "Maria Silva",
"message_id": "wamid.HBg...",
"type": "document",
"timestamp": "1747836000",
"document": {
"id": "4756102938475610",
"mime_type": "application/pdf",
"sha256": "p1o2i3...",
"filename": "invoice-1234.pdf",
"caption": "Your invoice",
"media_url": "https://api.zapclaw.app/api/v1/media/4756102938475610"
}
},
"account": {
"phone_number_id": "964283400112599",
"display_name": "My Business",
"display_phone_number": "+1 555 161-7409"
}
}
Replies (quoted messages)
When the contact replies to a specific message, data includes a
context object. Its id is the wamid of the message
that was replied to: use it to look up which of your messages the contact
answered, and from is the number that sent the quoted message.
{
"event": "message.received",
"timestamp": "2026-05-22T14:00:00.000Z",
"data": {
"from": "5511888888888",
"contact_name": "Maria Silva",
"message_id": "wamid.HBg...",
"type": "text",
"timestamp": "1747836000",
"text": { "body": "Yes, that works for me" },
"context": {
"from": "5511999999999",
"id": "wamid.HBgQUVO_THE_QUOTED_MESSAGE"
}
},
"account": {
"phone_number_id": "964283400112599",
"display_name": "My Business",
"display_phone_number": "+1 555 161-7409"
}
}
Event: message.status
Delivered when an outbound message changes status (sent, delivered, read, failed).
{
"event": "message.status",
"timestamp": "2026-05-21T14:00:05.000Z",
"data": {
"message_id": "wamid.HBg...",
"status": "delivered",
"recipient_id": "5511888888888",
"timestamp": "1747836005"
},
"account": { "phone_number_id": "964283400112599" }
}
Billing fields: conversation & pricing
When Meta includes billing details on a status change, the data
object also carries a conversation object and a
pricing object, passed through from Meta unchanged. Use them to
reconcile billing per message. They are present only when Meta supplies them
(typically on the sent or delivered status of a
billable message), so treat both as conditional.
{
"event": "message.status",
"timestamp": "2026-05-21T14:00:05.000Z",
"data": {
"message_id": "wamid.HBg...",
"status": "sent",
"recipient_id": "5511888888888",
"timestamp": "1747836005",
"conversation": {
"id": "abcd1234efgh5678",
"origin": { "type": "utility" },
"expiration_timestamp": "1747922405"
},
"pricing": {
"billable": true,
"pricing_model": "PMP",
"category": "utility",
"type": "regular"
}
},
"account": { "phone_number_id": "964283400112599" }
}
For an aggregated view of message cost over a time window, see the Pricing analytics endpoint.
Event: account.<field>
WABA-level events about your WhatsApp Business Account (account bans and
restrictions, phone number quality changes, and template status updates) are
forwarded as account.<field> events, where <field>
is the Meta webhook field (for example account.account_update).
The data object is the raw Meta webhook value for that field.
Its shape depends on the field.
{
"event": "account.account_update",
"field": "account_update",
"timestamp": "2026-05-21T14:00:00.000Z",
"data": {
"phone_number": "+1 555 161-7409",
"event": "ACCOUNT_RESTRICTION",
"restriction_info": [
{ "restriction_type": "RESTRICTED_BIZ_INITIATED_MESSAGING",
"expiration_timestamp": "1748000000" }
]
},
"account": {
"phone_number_id": "964283400112599",
"display_name": "My Business",
"display_phone_number": "+1 555 161-7409"
}
}
Request headers
Every webhook POST carries these headers:
| Header | Description |
|---|---|
X-ZapClaw-Event | The event type, e.g. message.received. |
X-ZapClaw-Delivery | Unique id of this delivery attempt. |
X-ZapClaw-Signature | HMAC-SHA256 signature of the body (see below). |
X-ZapClaw-Redelivery | Present only on a manual replay triggered from the dashboard. Its value is the id of the original delivery being replayed. |
message_id) as the original. If you deduplicate by
message_id, check for X-ZapClaw-Redelivery: when it
is present, process the event even though you have seen that
message_id before, since the replay was requested on purpose.
Signature verification
Every webhook request includes an X-ZapClaw-Signature header: an
HMAC-SHA256 of the raw request body, keyed with your webhook signing secret, prefixed
with sha256=. Always verify it before trusting the payload.
Node.js
const crypto = require('crypto');
function verify(rawBody, signature, secret) {
const expected = 'sha256=' + crypto
.createHmac('sha256', secret)
.update(rawBody)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature), Buffer.from(expected));
}
Python
import hmac, hashlib
def verify(raw_body: bytes, signature: str, secret: str) -> bool:
expected = 'sha256=' + hmac.new(
secret.encode(), raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, signature)
Retries & acknowledgement
Respond with any 2xx status to acknowledge a webhook. If your endpoint
returns a non-2xx status, times out (10 seconds), or is unreachable, ZapClaw retries the
delivery up to 3 times with a backoff of 0s, 30s and 120s. Delivery
attempts are logged and visible in the dashboard.
Delivery guarantees
The message_id (Meta wamid) is stable and globally unique. It is
safe to use as a deduplication key. ZapClaw also deduplicates internally, so it
will not POST the same event to your URL twice, even though Meta resends
webhooks aggressively.
message.received events by the top-level sequence
(a strictly increasing integer); it disambiguates messages that share the
same second-granularity data.timestamp. Keep your handler
idempotent.
Errors
Errors return a non-2xx HTTP status with a JSON body of this shape:
{ "error": "human-readable message" }
The table below lists every HTTP status the API can return.
| Status | When it occurs |
|---|---|
200 | Success. Returned by read endpoints and by POST /send/read and POST /send/typing. |
201 | Success. A resource was created: a message accepted and sent (the POST /send/* message endpoints), an uploaded file (POST /media), a created template (POST /send/templates) or a created QR code (POST /qr-codes). |
202 | Accepted. The request was accepted for asynchronous processing on Meta's side: a display name change (POST /profile/name) or a template create or edit (POST /send/templates, POST /send/templates/{id}) that is queued for Meta review. |
400 | Invalid request: a missing or malformed field, an unknown from number, or both link and mediaId supplied to POST /send/media. |
401 | Missing or invalid API key. |
404 | The requested message or media was not found, or does not belong to your account. |
410 | Received media has aged out on Meta's side (around 30 days) and can no longer be downloaded. |
413 | An uploaded file exceeds the size cap for its type. |
429 | Rate limit exceeded (1000 requests / 15 minutes per API key). |
500 | An unexpected error on the ZapClaw side. |
502 | The Meta API returned a failure or could not be reached. |
Use ZapClaw with Chatwoot
If you run Chatwoot
self-hosted as your conversation manager, ZapClaw ships a drop-in
compatibility mode that lets Chatwoot's native WhatsApp Cloud channel
talk to ZapClaw instead of graph.facebook.com directly.
You skip the Meta App + Business Verification + Tech Provider review
that Chatwoot otherwise requires, and your customers connect through
our Embedded Signup in minutes.
The mode is opt-in per webhook configuration. Native consumers (custom backends, CRMs, automations on n8n / Make / Zapier) keep receiving the normalized ZapClaw JSON envelope — see Webhook.
Which mode do I want?
| Native | Chatwoot | |
|---|---|---|
| Best for | Custom backend, CRM, n8n / Make / Zapier | Chatwoot self-hosted |
| Inbound payload | ZapClaw JSON envelope ({ event, data, account }) | Raw Meta envelope ({ object, entry[] }) |
| Inbound signature header | X-ZapClaw-Signature | X-Hub-Signature-256 |
| Outbound API | /api/v1/send/* with X-API-Key | /v25.0/{phone_id}/messages with Authorization: Bearer |
| Media URLs | We enrich messages with media_url | Resolve via GET /v25.0/{media_id} |
| Chatwoot env changes | none | 2 env vars + restart |
| Code changes on your side | You write the webhook handler | Zero — Chatwoot already has one |
Inbound: ZapClaw → Chatwoot
In the ZapClaw dashboard, open Webhook and switch
Integration mode to Chatwoot (Meta passthrough).
Set the URL to your Chatwoot base
(e.g. https://chatwoot.your-domain.com) — ZapClaw appends
/webhooks/whatsapp/<phone_number_id> automatically.
ZapClaw then posts the raw Meta payload — exactly the
{ "object": "whatsapp_business_account", "entry": [...] }
envelope Chatwoot expects to receive from Meta — and signs it with
X-Hub-Signature-256 using your webhook signing secret.
On your Chatwoot self-hosted server, paste the env vars block
ZapClaw shows on the webhook page into your .env (or
docker-compose environment) and restart Chatwoot:
# From the Chatwoot setup panel in your ZapClaw dashboard:
WHATSAPP_APP_SECRET=whsec_REPLACE_WITH_YOUR_SECRET
WHATSAPP_CLOUD_BASE_URL=https://api.zapclaw.app
WHATSAPP_APP_SECRET is what Chatwoot uses for the
signature check on inbound webhooks; WHATSAPP_CLOUD_BASE_URL
makes Chatwoot's outbound calls hit ZapClaw instead of Meta. The
same two values come up in the ZapClaw dashboard in a ready-to-copy
block.
Outbound: Chatwoot → ZapClaw
ZapClaw exposes a Meta-compatible REST surface under
/v25.0/... (and any other version segment — we accept
them all and route to the version ZapClaw is pinned to). Chatwoot's
WhatsappCloudService works against it without code
changes once WHATSAPP_CLOUD_BASE_URL is set.
Use a ZapClaw API key as the access token Chatwoot stores for the
inbox. The Meta-compat endpoints accept the key as either
X-API-Key (our native style) or Authorization: Bearer
(Meta style — what Chatwoot sends).
Endpoints exposed
These mirror the Graph API exactly — same request bodies, same response shapes, same error envelope.
| Method | Path | Use |
|---|---|---|
POST | /v25.0/{phone_number_id}/messages | Send any message type (text, template, media, interactive, reaction, location, contacts) |
POST | /v25.0/{phone_number_id}/media | Upload an outbound media file (multipart) |
GET | /v25.0/{media_id} | Resolve an inbound media id to a ZapClaw download URL |
GET | /v25.0/{waba_id}/message_templates | List message templates for a WABA |
Quick smoke test (cURL)
Before configuring Chatwoot, you can sanity-check the Meta-compat endpoint with cURL. Both shapes below work — they go through the same code path:
# With Meta-style Authorization (what Chatwoot sends)
curl -X POST https://api.zapclaw.app/v25.0/<phone_number_id>/messages \
-H "Authorization: Bearer zc_live_xxx" \
-H "Content-Type: application/json" \
-d '{
"messaging_product": "whatsapp",
"to": "5511999999999",
"type": "text",
"text": { "body": "Hello from ZapClaw" }
}'
# 200 OK
# {
# "messaging_product": "whatsapp",
# "contacts": [{ "input": "5511999999999", "wa_id": "5511999999999" }],
# "messages": [{ "id": "wamid.HBg...", "message_status": "accepted" }]
# }
Step-by-step setup
- Sign up at ZapClaw and connect your WhatsApp number through our Embedded Signup. You will not need to create a Meta app yourself.
- In API Keys, create a key — copy it once, you will paste it into Chatwoot.
- In Webhook, set the URL to your Chatwoot base
(e.g.
https://chatwoot.your-domain.com) and switch Integration mode to Chatwoot (Meta passthrough). Save. ZapClaw now shows a Chatwoot setup panel with:- a ready-to-copy two-line env var block,
- the Phone Number ID + Business Account ID for every connected number,
- shortcuts to API Keys and back here.
- On the Chatwoot self-hosted host, paste the env var block into
your
.env(ordocker-composeenvironment) and restart Chatwoot. - In Chatwoot, create an inbox of type WhatsApp →
WhatsApp Cloud. Fill in:
- Phone Number ID — from the credentials card on the ZapClaw webhook page.
- Business Account ID — same place.
- API key / Access token — your ZapClaw API key.
- Webhook verify token — anything; ZapClaw doesn't use this field (Meta is upstream of us).
- Send a test message from the Chatwoot inbox to a WhatsApp number. The conversation appears on both sides — inbound arrives in Chatwoot, outbound shows up in the ZapClaw dashboard's Conversations.
Troubleshooting
Most failures fit one of these patterns. ZapClaw logs every webhook delivery — open Logs in the dashboard to see the raw response Chatwoot returned, which usually pinpoints the issue faster than the Chatwoot logs do.
- Chatwoot rejects every webhook with 401 / "signature
mismatch". Double-check that
WHATSAPP_APP_SECRETon Chatwoot matches the Signing secret on the ZapClaw webhook page byte for byte. Trailing whitespace and accidental quotes count. Re-paste from the dashboard's copy button to be safe. - Chatwoot returns 404 for every webhook. The
URL we POST to is
<your-base>/webhooks/whatsapp/<phone_number_id>. That path only exists in Chatwoot once an inbox of type "WhatsApp Cloud" has been created for that phone number ID. Create the inbox first, then re-test. - Outbound returns 404 from ZapClaw. The phone
number ID passed to
/v25.0/{phone_number_id}/messagesmust belong to the same ZapClaw user as the API key used. Check both on the WhatsApp Numbers page; mismatched cross-account use is refused at the tenant guard. - Chatwoot keeps calling
graph.facebook.com. Older Chatwoot releases (pre 3.x) ignore the env var. Patchapp/services/whatsapp/providers/whatsapp_cloud_service.rbto readENV.fetch('WHATSAPP_CLOUD_BASE_URL', 'https://graph.facebook.com')inapi_base_path, rebuild the image, and restart. - Inbox stuck in "Connecting…" or "Pending". Usually Chatwoot trying to call Meta directly before the env var change took effect. Restart the Chatwoot worker pods, not just the web pods — the Sidekiq workers handle webhook subscription.
- Authentication templates return error 131062. Meta forbids sending one-tap / zero-tap / copy-code OTP templates when the recipient is addressed by BSUID instead of phone. Pass the recipient as a phone number for auth templates; utility and marketing templates accept both.
- Media downloads return 410. Inbound media is
served by
GET /v25.0/{media_id}, which proxies our own short-lived Meta URL. If Chatwoot caches the media id more than ~30 days it will eventually 410 — that is a Meta-side retention limit, not ours.
Changelog
Most recent changes first.
- Chatwoot passthrough: added an opt-in
webhook integration mode that delivers the raw Meta payload
signed with
X-Hub-Signature-256, plus a Meta-compatible REST surface under/v25.0/.... See Use ZapClaw with Chatwoot. - Coexistence limitations: documented the fixed throughput cap (about 5 messages per second), the webhook data gap for messages sent from WhatsApp for Windows or WearOS, and the features Coexistence does not support (voice/video calls, the OBA green badge, ephemeral/view-once/group messages). See Coexistence limitations.
- Status webhook billing fields: the
message.statuswebhookdatanow includes Meta'sconversationandpricingobjects, when present, for billing reconciliation. - Template management: added
POST /api/v1/send/templates(create),POST /api/v1/send/templates/{id}(edit) andDELETE /api/v1/send/templates/{name}(delete).GET /api/v1/send/templatesnow returns anidfield (the Meta template id) per template. - Pricing analytics: added
GET /api/v1/analytics/pricingfor per-message pricing analytics over a time window. - Conversational automation: added
GET /api/v1/automationandPOST /api/v1/automationto read and update the welcome message toggle, slash commands and ice breaker prompts. - QR codes: added
POST /api/v1/qr-codes,GET /api/v1/qr-codesandDELETE /api/v1/qr-codes/{code}to manage QR codes and managed links. - Block API: added
POST /api/v1/block,DELETE /api/v1/blockandGET /api/v1/blockto block, unblock and list blocked users. - Business profile: added
GET /api/v1/profile,PATCH /api/v1/profile,POST /api/v1/profile/nameandPOST /api/v1/profile/phototo read and update the WhatsApp business profile. - Media delete: added
DELETE /api/v1/media/{mediaId}to delete uploaded media from Meta. - New message types: added
POST /api/v1/send/location(location pin) andPOST /api/v1/send/contacts(contact cards);POST /api/v1/send/medianow acceptstype: "sticker"; andPOST /api/v1/send/textaccepts an optionalpreviewUrlboolean to enable link previews. - Webhook redelivery: deliveries can be replayed from the dashboard; a replay carries the
X-ZapClaw-Redeliveryheader so it is not confused with the original. - Webhook ordering:
message.receivednow carries a top-levelsequence, a strictly increasing integer for ordering messages that share the same second-granularitydata.timestamp. - Media download & upload: added
GET /api/v1/media/{mediaId}to download received media andPOST /api/v1/mediato upload files for sending. - Message & number lookups: added
GET /api/v1/send/messages/{id}for single-message lookup,GET /api/v1/send/numbersfor connected numbers, andPOST /api/v1/send/readfor read receipts. - Rich messaging: added
POST /api/v1/send/typing(typing indicator),POST /api/v1/send/reaction(emoji reactions),POST /api/v1/send/interactive(interactive messages), and the optionalreplyTofield for quoted replies on the text, template and media send endpoints. - Enriched webhook payload:
message.receivednow includesmedia_urlon media objects,contact_name, replycontext, and click-to-WhatsAppreferralattribution. - Account events: added the
account.<field>webhook event family for WABA-level alerts (bans, restrictions, phone number quality, template status).