YASend API

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.sent or message.failed webhook;
  • 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 queued and is retried with backoff. error shows the last reason, and nextAttemptAt shows when the next try is due.
  • Expire. If a message still can't go before expiresAt, it fails with EXPIRED.
  • Are never sent twice. If WhatsApp might already have received a message but we can't confirm it, the send fails with DELIVERY_UNKNOWN instead 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/messages and GET /v1/messages/{id} return the delivery record only: the id, status, error and times, with to empty, 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}/groups lists your groups;
  • POST /v1/sessions/{id}/groups creates 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