Sending messages
POST /v1/messages sends text or media to a person, a group or a channel.
{
"sessionId": "8d4c1f0e-3b9a-4f57-9a3e-2c1d0b7a6e55",
"to": "963912345678",
"text": "Your order #1042 has shipped."
}
| Field | |
|---|---|
sessionId |
The session to send from. Optional with a session key, or when the account has one session |
to |
A phone number in international format (digits only), or a JID: …@s.whatsapp.net (person), …@g.us (group), …@newsletter (a channel you administer) |
text |
Text, or the caption of media |
imageUrl, videoUrl, documentUrl, audioUrl |
An https URL that YASend downloads and sends. Use one per message |
caption, fileName |
A caption for the media, and the file name shown for a document |
quotedMessageId |
Reply to a message (its WhatsApp id) |
templateKey |
Marks a templated message, e.g. otp_login. See OTPs |
expiresInSeconds |
Async only: give up if the message isn't sent within this time (30 s – 72 h, default 24 h) |
Sync or async
Sync (default). The call returns once WhatsApp has accepted the message.
{ "id": "5b0c…", "status": "sent", "waMessageId": "3EB0C4…", "tookMs": 4210, "mode": "sync", … }
This usually takes 3–10 seconds, because YASend paces each number's messages like a person would. That pacing is what keeps your number from being banned.
Async (?async=true). You get 202 Accepted immediately:
curl -X POST "https://api.yasend.dev/v1/messages?async=true" \
-H "Authorization: Bearer $YASEND_KEY" -H "Content-Type: application/json" \
-d '{"sessionId": "'$SESSION_ID'", "to": "963912345678", "text": "Hi!"}'
# 202 → {"id": "5b0c…", "status": "queued", "expiresAt": "…", …}
YASend then sends the message for you and reports the outcome:
- with the
message.sentormessage.failedwebhook; - with
GET /v1/messages/{id}if you prefer to poll.
Async sends:
- Go out in order. One at a time per session, oldest first.
- Wait out temporary problems. If the session is reconnecting, or a daily quota or warm-up cap is
reached, the message stays
queuedand is retried with backoff.errorshows the last reason, andnextAttemptAtshows when the next try is due. - Expire. If a message still can't go before
expiresAt, it fails withEXPIRED. - Are never sent twice. If WhatsApp might already have received a message but we can't confirm it,
the send fails with
DELIVERY_UNKNOWNinstead of being retried. For an OTP, a duplicate is worse than a clear failure. - Can be cancelled while queued with
POST /v1/messages/{id}/cancel.
Use async for anything bulk-ish, and for any request your users are waiting on.
Message status
status |
|
|---|---|
queued |
Async: waiting its turn, or waiting to retry |
sending |
Being sent now |
sent |
Accepted by WhatsApp. waMessageId is set |
failed |
Not sent. See error.code |
canceled |
Cancelled while queued |
sent means WhatsApp accepted the message. Delivered and read receipts arrive later as
message.status webhooks, keyed by waMessageId.
GET /v1/messages lists your recent sends (newest first). Filter with sessionId and status, and page
with before=<nextBefore>.
We don't keep your messages. Once a message is sent (or fails, or is cancelled) we wipe its text, media links and recipient.
GET /v1/messagesandGET /v1/messages/{id}return the delivery record only: the id, status, error and times, withtoempty, and the record itself is deleted after about 3 days. Keep your own copy if you need one.
Idempotency
Network errors happen. To retry a send safely, add an Idempotency-Key header: 1–255 printable
characters, unique per message. Your own order or OTP-request id works well.
curl -X POST https://api.yasend.dev/v1/messages -H "Idempotency-Key: otp-req-7f3a91" ...
| Repeat | Result |
|---|---|
| Same key, same body | The original message (Idempotent-Replayed: true). It is never sent again, and a failed send replays its failure |
| Same key, different body | 422 IDEMPOTENCY_KEY_REUSED |
| Same key while the first sync request is still running | 409 IDEMPOTENCY_IN_PROGRESS |
Groups and channels
Send to a group with its JID, e.g. [email protected]:
GET /v1/sessions/{id}/groupslists your groups;POST /v1/sessions/{id}/groupscreates one.
Send to a channel you administer with its …@newsletter JID.
After sending
These endpoints act on a message by its WhatsApp id (waMessageId):
| Call | |
|---|---|
GET /v1/sessions/{id}/messages/{waMessageId} |
The last delivery status (no text, no recipient: we don't keep them), for about 2 days |
PATCH /v1/sessions/{id}/messages/{waMessageId} {"text": "…"} |
Edit a sent text (recent messages: the connection remembers the chat, we don't) |
DELETE /v1/sessions/{id}/messages/{waMessageId} |
Delete for everyone |
POST /v1/sessions/{id}/messages/{waMessageId}/decrypt-media |
Download a received photo, video or document. Then GET /v1/sessions/{id}/media/{fileName}. The file is kept for about an hour: fetch it right away |