Migrate from Z-API to the official WhatsApp API
In short: to move a bot or an automation from Z-API's QR-code product to Meta's official WhatsApp Cloud API, connect the number to ZapClaw through Coexistence, send through ZapClaw's REST API instead of /send-text, and read ZapClaw's webhook instead of ReceivedCallback. The number keeps working in the WhatsApp Business app on your phone. Below: the steps, the text and media send fields and the main webhook fields side by side, and what has no equivalent on the official API.
ZapClaw connects your number to Meta's Cloud API through Coexistence, with no QR-code session and no Meta app of your own.
Business plan: US$ 29 per month, with unlimited numbers
Start for free See the field mappingWhy move to the official API
- WhatsApp's terms. Z-API's documentation says that once you read its QR code you are using a WhatsApp Web session. WhatsApp's Terms of Service forbid using the service through automated means in ways WhatsApp has not authorized, and name auto-messaging among the communications they do not permit. Meta's Cloud API is the API Meta offers businesses for this.
- The app stays on the phone. With Coexistence your team keeps answering in the WhatsApp Business app while your system uses the API on the same number, and the chats stay in sync.
- Many numbers, one account. ZapClaw's paid plans take unlimited numbers for one price per account, and the Agency plan keeps each client's numbers, contacts, templates, API keys and webhook apart.
- Webhooks you can verify. Z-API's webhook documentation describes no signature on the callbacks it sends. ZapClaw signs every body with HMAC-SHA256 and keeps every event for replay from the dashboard.
Meta's policies still apply on the official API: Meta can restrict or disable an account that breaks them, and ZapClaw's API then answers 403 with the reason (errors).
What you need to connect
- The WhatsApp Business app on the number, version 2.24.17 or newer, in regular use.
- Two-factor authentication on the personal Facebook account that authorizes the connection.
- The number not connected to another provider. If it is, disconnect it there first.
- A computer to run the connection, with your phone at hand to confirm it in the app.
You do not need a company tax ID or a verified business to connect. The full checklist.
The move, step by step
- Map your code first. Change your send calls and your webhook handler with the tables below, or put the shim in front of your flow so it keeps its Z-API calls while you rewrite it.
- Back up your chats in the WhatsApp Business app and check the requirements above.
- Connect the number. Create your ZapClaw account and connect the number in Meta's window from the dashboard. Meta then unlinks every linked device from the account, the Z-API session included. Disconnect the instance in Z-API afterwards, so nobody reads its QR code again and two systems answer the same customer.
- Set the webhook. In the dashboard, under Webhook, paste your public HTTPS URL, pick the events and copy the signing secret. Tick Messages sent from the WhatsApp Business app if your flow read callbacks with
fromMe: true. - Create an API key and send with it in the
X-API-Keyheader tohttps://api.zapclaw.app. Thefromfield names the number, so one key sends from every number of the account, with no instance id or token in the path. - Create your templates. Outside the 24-hour window, and to write first, Meta accepts only an approved template. Create them in the dashboard or with
POST /api/v1/send/templates, and passlanguagewhen you send one: the API's default ispt_BR. - Test. Press Send test event on the dashboard's Webhook page, then write to your number from another phone and check that your flow receives the message and replies.
Sending: Z-API calls and their ZapClaw equivalent
Z-API's paths start with https://api.z-api.io/instances/{instanceId}/token/{token}, shortened below to …. ZapClaw's start with https://api.zapclaw.app.
| Z-API | ZapClaw | |
|---|---|---|
| Send text | POST …/send-text |
POST /api/v1/send/text |
| Send media | …/send-image, …/send-video, …/send-audio, …/send-sticker, …/send-document/{extension} |
POST /api/v1/send/media |
| Authentication | Instance id and token in the path, plus an optional Client-Token header |
X-API-Key header |
| Your number | The instance, in the path | from, in the body |
| Recipient | phone: a number or a group id |
to: a number, digits only |
| Text | message |
text, up to 4,096 characters |
| Media file | image, video, audio, sticker or document: a link or base64 |
type, plus link (a public https URL) or mediaId from POST /api/v1/media |
| Document name | The extension in the path, fileName in the body |
filename, with its extension |
| Caption | caption |
caption (image, video, document) |
| Reply to a message | messageId, on text and media sends |
replyTo: the wamid of the message |
| Delays | delayMessage, delayTyping |
No equivalent. POST /api/v1/send/typing shows a typing indicator on a message you received. |
| View once | viewOnce |
Not supported on a Coexistence number |
| Edit a sent message | editMessageId |
No equivalent |
| Answer | zaapId, messageId, id |
201 with message.id and message.wamid |
Location, contact cards and reactions go through POST /api/v1/send/location, /send/contacts and /send/reaction (API reference). The shim further down does not convert those calls yet: send them straight to ZapClaw.
Receiving: ReceivedCallback and its ZapClaw equivalent
Z-API posts each message as a flat object with type: "ReceivedCallback". ZapClaw posts event, timestamp, data and account to one URL for all your numbers.
| Z-API | ZapClaw | |
|---|---|---|
| Event | type: "ReceivedCallback" |
event: "message.received", also in the X-ZapClaw-Event header |
| Which of your numbers | instanceId, connectedPhone |
account.phone_number_id, account.display_phone_number |
| Sender | phone |
data.from, and data.user_id, the contact's durable id |
| Sent by you | fromMe: true |
A separate message.echo event, with data.to, for messages sent from the WhatsApp Business app or WhatsApp Web (opt-in) |
| Message id | messageId |
data.message_id, a wamid |
| Contact name | senderName, chatName |
data.contact_name |
| Time | momment: milliseconds in the documentation's examples |
data.timestamp: seconds, as a string |
| Text | text.message |
data.text.body |
| Image | image.imageUrl, image.mimeType, image.caption |
data.image.media_url, data.image.mime_type, data.image.caption |
| Audio | audio.audioUrl, audio.ptt |
data.audio.media_url, data.audio.voice |
| Video | video.videoUrl, video.caption |
data.video.media_url, data.video.caption |
| Document | document.documentUrl, document.fileName |
data.document.media_url, data.document.filename |
| Sticker | sticker.stickerUrl |
data.sticker.media_url |
| Location | location.latitude, location.longitude |
data.location.latitude, data.location.longitude |
| Button reply | buttonsResponseMessage.buttonId |
data.interactive.button_reply.id; a template's quick reply arrives as data.button.payload |
| List reply | listResponseMessage.selectedRowId |
data.interactive.list_reply.id |
| Quoted message | referenceMessageId |
data.context.id |
| Groups | isGroup, participantPhone |
Always a one-to-one chat |
| Delivery status | MessageStatusCallback, with SENT, RECEIVED, READ, READ_BY_ME or PLAYED |
message.status, with data.status set to sent, delivered, read or failed |
| Send result | DeliveryCallback, with error when the send failed |
message.status with failed, and Meta's reason in data.errors |
| Media storage | Z-API keeps files for 30 days | Meta keeps received media for around 30 days; download it with your X-API-Key |
| Request authentication | None described in the webhook documentation | X-ZapClaw-Signature: an HMAC-SHA256 of the raw body, keyed with your signing secret (how to verify it) |
Z-API reports a failed send in its DeliveryCallback webhook, in the error field; its MessageStatusCallback has no failed status. On ZapClaw a message.status with failed carries Meta's reason in data.errors: move your DeliveryCallback error handling there. Deliveries can arrive out of order: when order matters, sort by the top-level sequence.
What has no equivalent, and what to do instead
- Groups. Groups keep working in the WhatsApp Business app, but Meta's Cloud API does not support them on a Coexistence number: sending to a group id and receiving group messages through the API both stop. Keep group automations off the number you move.
- Free text at any time. A text or media message goes only within 24 hours of the contact's last message to you. Outside that window ZapClaw answers
422withmeta_code: 131047: send an approved template instead. - Writing first. The first message to someone who has not written to you in the last 24 hours has to be an approved template. Meta bills templates to you directly, separately from your ZapClaw plan.
- Status, channels and broadcast lists. ZapClaw has no endpoint for status or channels, and Meta lists broadcast lists among what Coexistence does not support.
- View once, disappearing messages and calls. Meta turns view once and disappearing messages off in one-to-one chats. Voice and video calls keep working in the app, but the Cloud API does not support them on a Coexistence number.
- Editing a sent message, send delays, polls and mentions. ZapClaw has no endpoint for them. Buttons and lists go through
POST /api/v1/send/interactive. - Base64 in the send body. Upload the file with
POST /api/v1/mediaand send themediaIdit returns, or send a public httpslink. - Old message ids. Ids from Z-API are not Cloud API ids, so a reply cannot quote a message from before the move. New ids start with
wamid. - Contact ids. Store
data.user_idas the durable key of a contact: a contact who adopts a WhatsApp username can arrive withdata.fromset tonull. - Volume. A Coexistence number is fixed at 20 messages per second, a Meta limit that cannot be raised, and bulk broadcasting does not fit.
- WhatsApp for Windows and WearOS. Meta does not let them link again after the move, and messages sent from them produce no webhook.
Keep using the WhatsApp Business app on the phone: Meta disconnects a Coexistence number whose app goes about 14 days without use, and ZapClaw tells your webhook with an account.account_update event (details).
A short adapter
Two functions for Node.js 18 or newer, with no dependency: the first sends where your code called send-text, the second turns a ZapClaw message.received into the ReceivedCallback fields your flow already reads. Check the X-ZapClaw-Signature header before you trust a body.
// Sending: what was POST .../instances/{instanceId}/token/{token}/send-text
const sendText = async ({ phone, message }) => {
const res = await fetch('https://api.zapclaw.app/api/v1/send/text', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-API-Key': process.env.ZAPCLAW_API_KEY,
},
body: JSON.stringify({ from: process.env.ZAPCLAW_FROM, to: phone, text: message }),
});
if (!res.ok) throw new Error(`ZapClaw answered ${res.status}`);
const sent = (await res.json()).message;
return { zaapId: sent.id, messageId: sent.wamid, id: sent.wamid };
};
// Receiving: the ReceivedCallback fields, taken from a message.received body
const fromZapClaw = (body) => ({
phone: body.data.from,
messageId: body.data.message_id,
senderName: body.data.contact_name,
momment: Number(body.data.timestamp) * 1000,
text: body.data.text ? { message: body.data.text.body } : undefined,
});
The full version is on GitHub: zapclaw/migrate-to-whatsapp-official-api converts text, media, statuses and webhooks and ships a small shim. A call it does not convert, such as a location or a contact card, is refused with a clear error code: send those calls straight to ZapClaw's API. Point your flow's Z-API base URL at the shim: it forwards the calls it converts to ZapClaw and turns ZapClaw's signed webhooks back into ReceivedCallback, MessageStatusCallback and, for a failed send, DeliveryCallback, so you can move first and rewrite later. MIT license.
If your setup is different
- Z-API Omni. Z-API describes Omni as a technical layer on Meta's official API, and also lists a WhatsApp Web channel in it. If your number uses Omni's channel on Meta's API, it is already on that API through Z-API, and a number cannot be on two providers at once: disconnect it in Z-API first, then connect it to ZapClaw. If it uses Omni's WhatsApp Web channel, follow the steps above.
- n8n or Make. Replace the Z-API steps with a webhook trigger that receives ZapClaw's events and an HTTP request that calls the send API: n8n and Make.
- Chatwoot. Chatwoot's own WhatsApp Cloud channel talks to ZapClaw instead of Meta: Chatwoot on the official WhatsApp API.
- Several instances. If you ran one Z-API instance per number, one ZapClaw account holds them all:
account.phone_number_idtells them apart on one webhook URL, andfrompicks the number when you send.
What it costs
- Free: 500 messages a month and 1 number, with no card and no end date. At the limit, sending pauses and new messages are held until you subscribe.
- Business: US$ 29 per month, with 20,000 messages included and unlimited numbers.
- Agency: US$ 49 per month for agencies, with 3 clients and 60,000 messages included and unlimited numbers.
Meta bills its WhatsApp message fees to you directly, with no ZapClaw markup. Every plan and price.
Related
- ZapClaw vs Z-API: the two side by side, before you decide.
- Unofficial WhatsApp APIs and Meta's official API, compared: what WhatsApp's terms say, with sources.
- The official WhatsApp API with ZapClaw: what you get and the requirements.
- The webhook reference, with every event and field.
- Moving from another tool: Evolution API or WAHA.
Sources
Checked on 15 October 2026. Z-API changes its API over time, so check its documentation before you move.
- Z-API, introduction (in Portuguese): the QR code and the WhatsApp Web session.
- Z-API, security and Client-Token: the instance id and token in the path, and the optional header.
- Z-API send pages: text, image, document, audio, video, sticker and reply.
- Z-API webhooks: introduction, on message received (files kept 30 days), callback examples, message status, and on message send with its examples (
DeliveryCallbackand itserrorfield). - Z-API Omni (in Portuguese): how Z-API describes it, and its channels.
- WhatsApp Terms of Service: automated access and auto-messaging.
- Meta, onboarding WhatsApp Business app users: Coexistence, linked devices, throughput and what it does not support.
- ZapClaw: webhook reference, errors, OpenAPI file and pricing.
- zapclaw/migrate-to-whatsapp-official-api: the full mapping, with a source for every row.
Move your Z-API flows to the official API
Start on the free plan: one number, no card. Move to the Business plan when you need more numbers or more messages.
Start for free Read the webhook docs