Messages
Send and read messages. Free-form messages need an open 24-hour WhatsApp window; outside it, send an approved template.
/messagesBetaGET/messages/{message_id}BetaGET/conversations/{conversation_id}/messagesAvailablePOST/conversations/{conversation_id}/messagesAvailableSend a message to a phone number
Section titled “Send a message to a phone number”/v1/messagesBetaThe one call most integrations need. Address the recipient by channel identity; Waasl finds or creates the contact and the conversation for you.
- Inside the 24-hour window you can send any
kind. - Outside it (or to a brand-new contact) WhatsApp only accepts
template.
Sending is asynchronous: the response is the stored message with status: queued.
Follow delivery through the message.status_updated webhook.
Headers
| Field | Type | Description |
|---|---|---|
Idempotency-Key |
string | A unique key (UUID recommended). Retrying with the same key returns the original result instead of acting twice. Kept for 24 hours. |
Request body
| Field | Type | Description |
|---|---|---|
channel_id |
string | Which number to send from. Optional when the workspace has one channel. |
to required |
string | Recipient identity (E.164 for WhatsApp). |
contact_id |
string | Alternative to to. |
kind required |
“text” | “image” | “video” | “file” | “voice” | “template” | “location” | “interactive” | “reaction” | |
body |
string | Text, or the caption for media. |
media_id |
string | From POST /media. |
reply_to_id |
string | Quote an earlier message. |
template |
object | |
template.name |
string | |
template.language |
string | |
template.params |
string[] | Values for {{1}}, {{2}}… in order. |
template.header_media_id |
string | For image/video/document headers (coming soon). |
interactive |
object | Coming soon. |
interactive.type |
“buttons” | “list” | |
interactive.buttons |
object[] | |
location |
object | Coming soon. |
location.latitude |
number | |
location.longitude |
number | |
location.name |
string | |
location.address |
string |
curl -X POST "https://api.waasl.io/v1/messages" \ -H "Authorization: Bearer $WAASL_API_KEY" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{ "channel_id": "ch_wa_7Yt2", "to": "+96550001234", "kind": "template", "template": { "name": "order_ready", "language": "ar", "params": [ "سارة", "4821" ] }}'const res = await fetch("https://api.waasl.io/v1/messages", { method: "POST", headers: { Authorization: `Bearer ${process.env.WAASL_API_KEY}`, "Content-Type": "application/json", "Idempotency-Key": crypto.randomUUID() }, body: JSON.stringify({ "channel_id": "ch_wa_7Yt2", "to": "+96550001234", "kind": "template", "template": { "name": "order_ready", "language": "ar", "params": [ "سارة", "4821" ] } }),});if (!res.ok) throw new Error((await res.json()).code);const data = await res.json();import os, uuidimport requests
r = requests.post( "https://api.waasl.io/v1/messages", headers={"Authorization": f"Bearer {os.environ['WAASL_API_KEY']}", "Idempotency-Key": str(uuid.uuid4())}, json={ "channel_id": "ch_wa_7Yt2", "to": "+96550001234", "kind": "template", "template": { "name": "order_ready", "language": "ar", "params": ["سارة", "4821"] } },)r.raise_for_status()print(r.json())Response 201 — Message accepted and queued
{ "id": "msg_Vb3kQ9xP", "conversation_id": "string", "direction": "in", "kind": "text", "body": "string", "caption": "string", "media_id": "string", "filename": "string", "mime": "string", "size_bytes": 0, "reply_to_id": "string", "actor_kind": "contact", "actor_id": "string", "status": "queued", "error": "Re-engagement message (131047)", "created_at": "2026-10-01T08:15:00Z"}Errors: 409 The resource’s state doesn’t allow this · 422 The request body is invalid · 429 Too many requests
Retrieve a message
Section titled “Retrieve a message”/v1/messages/{message_id}BetaIncludes the latest delivery status and, for failures, Meta’s error.
Path parameters
| Field | Type | Description |
|---|---|---|
message_id required |
string |
curl "https://api.waasl.io/v1/messages/msg_Vb3kQ9xP" \ -H "Authorization: Bearer $WAASL_API_KEY"const res = await fetch("https://api.waasl.io/v1/messages/msg_Vb3kQ9xP", { headers: { Authorization: `Bearer ${process.env.WAASL_API_KEY}` },});if (!res.ok) throw new Error((await res.json()).code);const data = await res.json();import osimport requests
r = requests.get( "https://api.waasl.io/v1/messages/msg_Vb3kQ9xP", headers={"Authorization": f"Bearer {os.environ['WAASL_API_KEY']}"},)r.raise_for_status()print(r.json())Response 200 — The message
{ "id": "msg_Vb3kQ9xP", "conversation_id": "string", "direction": "in", "kind": "text", "body": "string", "caption": "string", "media_id": "string", "filename": "string", "mime": "string", "size_bytes": 0, "reply_to_id": "string", "actor_kind": "contact", "actor_id": "string", "status": "queued", "error": "Re-engagement message (131047)", "created_at": "2026-10-01T08:15:00Z"}Errors: 404 Not found in this workspace
List messages in a conversation
Section titled “List messages in a conversation”/v1/conversations/{conversation_id}/messagesAvailableEach page is oldest-first; the first page holds the newest messages and cursor walks back in time.
Path parameters
| Field | Type | Description |
|---|---|---|
conversation_id required |
string |
Query parameters
| Field | Type | Description |
|---|---|---|
cursor |
string | The next_cursor from the previous page. |
curl "https://api.waasl.io/v1/conversations/cv_3PzA91/messages" \ -H "Authorization: Bearer $WAASL_API_KEY"const res = await fetch("https://api.waasl.io/v1/conversations/cv_3PzA91/messages", { headers: { Authorization: `Bearer ${process.env.WAASL_API_KEY}` },});if (!res.ok) throw new Error((await res.json()).code);const data = await res.json();import osimport requests
r = requests.get( "https://api.waasl.io/v1/conversations/cv_3PzA91/messages", headers={"Authorization": f"Bearer {os.environ['WAASL_API_KEY']}"},)r.raise_for_status()print(r.json())Response 200 — A page of messages
{ "data": [ { "id": "msg_Vb3kQ9xP", "conversation_id": "string", "direction": "in", "kind": "text", "body": "string", "caption": "string", "media_id": "string", "filename": "string", "mime": "string", "size_bytes": 0, "reply_to_id": "string", "actor_kind": "contact", "actor_id": "string", "status": "queued", "error": "Re-engagement message (131047)", "created_at": "2026-10-01T08:15:00Z" } ], "next_cursor": "MjAyNi0xMC0wMVQwODoxNTowMC4wMDBafGN2XzNQekE5MQ", "has_more": true}Reply in a conversation
Section titled “Reply in a conversation”/v1/conversations/{conversation_id}/messagesAvailableSends into an existing conversation. Fails with 409 and a precise code when the
conversation can’t take a message: window_closed, conversation_closed,
contact_blocked or bot_in_control.
Path parameters
| Field | Type | Description |
|---|---|---|
conversation_id required |
string |
Headers
| Field | Type | Description |
|---|---|---|
Idempotency-Key |
string | A unique key (UUID recommended). Retrying with the same key returns the original result instead of acting twice. Kept for 24 hours. |
Request body
| Field | Type | Description |
|---|---|---|
kind required |
“text” | “image” | “video” | “file” | “voice” | “template” | “location” | “interactive” | “reaction” | |
body |
string | Text, or the caption for media. |
media_id |
string | From POST /media. |
reply_to_id |
string | Quote an earlier message. |
template |
object | |
template.name |
string | |
template.language |
string | |
template.params |
string[] | Values for {{1}}, {{2}}… in order. |
template.header_media_id |
string | For image/video/document headers (coming soon). |
interactive |
object | Coming soon. |
interactive.type |
“buttons” | “list” | |
interactive.buttons |
object[] | |
location |
object | Coming soon. |
location.latitude |
number | |
location.longitude |
number | |
location.name |
string | |
location.address |
string |
curl -X POST "https://api.waasl.io/v1/conversations/cv_3PzA91/messages" \ -H "Authorization: Bearer $WAASL_API_KEY" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{ "kind": "text", "body": "Thanks Sara — your plan is renewed until 30 Oct ✅"}'const res = await fetch("https://api.waasl.io/v1/conversations/cv_3PzA91/messages", { method: "POST", headers: { Authorization: `Bearer ${process.env.WAASL_API_KEY}`, "Content-Type": "application/json", "Idempotency-Key": crypto.randomUUID() }, body: JSON.stringify({ "kind": "text", "body": "Thanks Sara — your plan is renewed until 30 Oct ✅" }),});if (!res.ok) throw new Error((await res.json()).code);const data = await res.json();import os, uuidimport requests
r = requests.post( "https://api.waasl.io/v1/conversations/cv_3PzA91/messages", headers={"Authorization": f"Bearer {os.environ['WAASL_API_KEY']}", "Idempotency-Key": str(uuid.uuid4())}, json={ "kind": "text", "body": "Thanks Sara — your plan is renewed until 30 Oct ✅" },)r.raise_for_status()print(r.json())Response 201 — Message accepted and queued
{ "id": "msg_Vb3kQ9xP", "conversation_id": "string", "direction": "in", "kind": "text", "body": "string", "caption": "string", "media_id": "string", "filename": "string", "mime": "string", "size_bytes": 0, "reply_to_id": "string", "actor_kind": "contact", "actor_id": "string", "status": "queued", "error": "Re-engagement message (131047)", "created_at": "2026-10-01T08:15:00Z"}Errors: 409 The resource’s state doesn’t allow this · 422 The request body is invalid