Your first message
Send a WhatsApp text, read the thread back, and give the customer blue ticks.
Send a text
curl -X POST https://developers.wasync.app/api/v1/messages \
-H "Authorization: Bearer $WASYNC_API_KEY" \
-H "Content-Type: application/json" \
-d '{"connectionId":"conn_8f3a21","to":"40700000000","text":"Hi from my app 👋"}'{
"messageId": "cmqj3k2ab0001xyz",
"waMessageId": "[email protected]_3EB0A1B2C3",
"status": "sent"
}to is international digits only, no + — e.g. 40700000000. text is at
most 4096 characters. For media, supply media instead of text:
{ filename, mimetype, data (base64, ≤ 16 MB), caption? } — exactly one of the
two. Voice notes use mimetype: audio/ogg and a voice-*.ogg filename. Uses
whatsapp.send.
Set your HTTP timeout to at least 180 seconds
A qr send normally takes 1–7 s, up to about 20 s for long texts, and the
self-heal path can take ~2.5 minutes. A cloud_api send is about 1 s. A slow
send is not a failure — a client that gives up at 30 s and retries sends the
customer the message twice. Always pass an idempotencyKey (a UUID per logical
send) and retry with the same one.
Two ids — join on the right one
The response carries two identifiers and they are not interchangeable.
| Id | Where it appears | Use it for |
|---|---|---|
messageId (a cuid) | messageId on POST, id on GET, message.id in both webhooks | The only joinable id. Store it on send, dedupe on it, match message.status events against it. |
waMessageId | waMessageId (REST), wa_id (webhooks), wa_message_id (media send) | Support tickets and debugging. It can be null — never join or dedupe on it. |
One degraded case is worth coding for: { "messageId": null, "waMessageId": "…", "status": …, "persisted": false } with HTTP 200 means the message was
delivered but was not persisted. Do not retry it.
Read the thread back
curl "https://developers.wasync.app/api/v1/messages?connectionId=conn_8f3a21&limit=20" \
-H "Authorization: Bearer $WASYNC_API_KEY"{
"messages": [
{
"id": "cmqj3k2ab0001xyz",
"waMessageId": "[email protected]_3EB0A1B2C3",
"connectionId": "conn_8f3a21",
"direction": "outgoing",
"text": "Hi from my app 👋",
"mediaUrl": null,
"mediaType": null,
"status": "delivered",
"errorMessage": null,
"createdAt": "2026-07-22T10:00:00.000Z"
}
],
"nextCursor": "cmqj3k2ab0001xyz",
"hasMore": true
}Newest first, cursor-paginated: limit is 1–100 (default 20), and cursor
takes the previous response's nextCursor. Uses whatsapp.read.
Give the customer blue ticks
curl -X POST https://developers.wasync.app/api/v1/messages/read \
-H "Authorization: Bearer $WASYNC_API_KEY" \
-H "Content-Type: application/json" \
-d '{"connectionId":"conn_8f3a21","phone":"40700000000"}'Call this when an operator opens the conversation in your UI — otherwise
nobody ever opens the chat in WhatsApp, the customer's messages stay on double
grey ticks and the thread looks ignored. Body is
{ connectionId, phone?, messageId? } with at least one of phone /
messageId (the cuid of an incoming message; messageId wins when both are
given). It uses whatsapp.send, not whatsapp.read, because it transmits a
receipt to the customer. Idempotent, no key, no rate limit. Returns
{ "ok": true, "marked": 3 }.