Wafast Docs
PlaygroundSign in
IntroductionAuthenticationQuick StartSend messageSend Media & FileSend to a listSend locationSend pollSend contactSend Voice NoteSend VideoTyping IndicatorMark as readMessage reactionForward messageEdit messageDelete messageDelivery statusInbox / Message listUpload MediaGroupsGroup membersCheck numberOTPNumber listWebhook (Message Incoming)Error codesMigrate from FonnteKeeping your number healthy

Introduction

Wafast is a WhatsApp API: send and receive WhatsApp messages over a simple REST API. Every endpoint returns JSON.

Base URLhttps://wafa.st
FormatJSON (request & response)
ModeMulti-device
AuthBearer token / ?token=

Every successful response looks like { "status": true, ... }. A failed response looks like { "status": false, "error": "..." }.

You need a connected number first. Add one and scan the QR in dashboard, then create an API key.

Authentication

Every request needs an API key. There are three ways to send it — pick one:

  • Authorization: Bearer YOUR_API_KEY (recommended)
  • Authorization: YOUR_API_KEY (raw token — Fonnte-compatible)
  • Query string ?token=YOUR_API_KEY

Format key: live wafast…, test wafasttest… (validates the request without actually sending). Older bls_live_… / bls_test_… keys still work. Limit is about 60 requests per minute per key on send endpoints.

Create and revoke API keys in the tab API Key in the dashboard. For security the key is only shown once when you create it — store it somewhere safe. If you lose it, create a new one and revoke the old. Keep it private: anyone holding this key can send messages as you.

Quick Start

Send your first WhatsApp message in one request:

curl -X POST https://wafa.st/api/v1/send \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "target": "628123456789",
    "message": "Hello from Wafast 👋"
  }'

Replace YOUR_API_KEY with your own key. target = the recipient number in international format (no + and no leading 0).

Send message

POST/api/v1/send

Parameters

ParameterTypeRequiredDescription
targetstringYesRecipient number in international format. Separate several numbers with commas.
messagestringYes*Enter the message text. Required when there is no mediaUrl.
devicestringNoName/ID number. Empty = the first connected number.
mediaUrlstringNoPublic URL of the image/file to send.
mediaTypestringNo"image" for pictures, "file" for documents.
filenamestringNoName file (for document).
delaystringNoGap between messages when sending to several numbers, e.g. "5" or "5-10" (seconds, randomised).

Example

curl -X POST https://wafa.st/api/v1/send \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "target": "628123456789",
    "message": "Hello from Wafast 👋"
  }'

Successful response

json
{
  "status": true,
  "detail": "1 message queued",
  "id": ["[email protected]_3EB0..."],
  "process": "sent",
  "device": "CS Utama",
  "target": ["628123456789"]
}

Send Media & File

Add mediaUrl (a public URL). For pictures set mediaType=image; for documents set mediaType=file.

bash
# Picture (with a caption)
curl -X POST https://wafa.st/api/v1/send \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -d '{
    "target": "628123456789",
    "message": "This month's offer",
    "mediaUrl": "https://example.com/offer.jpg",
    "mediaType": "image"
  }'

# Document (PDF, etc.)
curl -X POST https://wafa.st/api/v1/send \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -d '{
    "target": "628123456789",
    "mediaUrl": "https://example.com/invoice.pdf",
    "mediaType": "file",
    "filename": "invoice-001.pdf"
  }'

Send to a list

Send to several numbers at once: separate them target with commas. Use delay so there is a random gap between messages, which keeps the number healthier.

bash
curl -X POST https://wafa.st/api/v1/send \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -d '{
    "target": "628111111111,628222222222,628333333333",
    "message": "Hi, we have a special offer for you.",
    "delay": "5-10"
  }'
For larger sends, go in batches and leave a gap. See Keeping your number healthy.

Send location

POST/api/v1/send/location

Parameters

ParameterTypeRequiredDescription
targetstringYesRecipient number 62xxx.
latitudenumberYesGaris lintang, mis. -6.2088.
longitudenumberYesGaris bujur, mis. 106.8456.
titlestringNoThe location label shown in the message.
bash
curl -X POST https://wafa.st/api/v1/send/location \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -d '{
    "target": "628123456789",
    "latitude": -6.2088,
    "longitude": 106.8456,
    "title": "Kantor Pusat"
  }'

Send poll

POST/api/v1/send/poll

Parameters

ParameterTypeRequiredDescription
targetstringYesRecipient number 62xxx.
namestringYesPertanyaan / judul polling.
optionsstring[]YesPilihan jawaban (at least 2). list
multipleAnswersbooleanNotrue = the reader may pick more than one option.
bash
curl -X POST https://wafa.st/api/v1/send/poll \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -d '{
    "target": "628123456789",
    "name": "Jam meeting paling cocok?",
    "options": ["09.00", "13.00", "16.00"],
    "multipleAnswers": false
  }'

Send contact

POST/api/v1/send/contact

Parameters

ParameterTypeRequiredDescription
targetstringYesRecipient number 62xxx.
namestringYesContact name to send.
phonestringYesNumber contact format 62xxx.
bash
curl -X POST https://wafa.st/api/v1/send/contact \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -d '{
    "target": "628123456789",
    "name": "Tim Support",
    "phone": "628999888777"
  }'

Send Voice Note

POST/api/v1/send/voice

Parameters

ParameterTypeRequiredDescription
targetstringYesRecipient number 62xxx.
urlstringYesPublic URL of the audio file (mp3/ogg).
bash
curl -X POST https://wafa.st/api/v1/send/voice \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -d '{
    "target": "628123456789",
    "url": "https://example.com/voice-note.ogg"
  }'

Send Video

POST/api/v1/send/video

Parameters

ParameterTypeRequiredDescription
targetstringYesRecipient number 62xxx.
urlstringYesPublic URL of the video file (mp4).
captionstringNoCaption text for the video.
bash
curl -X POST https://wafa.st/api/v1/send/video \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -d '{
    "target": "628123456789",
    "url": "https://example.com/demo.mp4",
    "caption": "A short product walkthrough"
  }'

Typing Indicator

POST/api/v1/typing

Show or hide the "typing" indicator to the person you are chatting with.

Parameters

ParameterTypeRequiredDescription
targetstringYesRecipient number 62xxx.
actionstringYes"start" mulai mengetik, "stop" berhenti.
bash
curl -X POST https://wafa.st/api/v1/typing \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -d '{
    "target": "628123456789",
    "action": "start"
  }'

Mark as read

POST/api/v1/seen

Mark a message from the other side as read (blue ticks).

Parameters

ParameterTypeRequiredDescription
targetstringYesChat number 62xxx.
messageIdstringNoID message tertentu. Empty = tandai chat.
bash
curl -X POST https://wafa.st/api/v1/seen \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -d '{
    "target": "628123456789",
    "messageId": "[email protected]_3EB0..."
  }'

Message reaction

POST/api/v1/reaction

React to a message with an emoji. Send an empty emoji ("") to remove the reaction.

Parameters

ParameterTypeRequiredDescription
messageIdstringYesID message that ingin direaksi.
emojistringYesEmoji reaksi, mis. "👍". Empty = delete reaksi.
bash
curl -X POST https://wafa.st/api/v1/reaction \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -d '{
    "messageId": "[email protected]_3EB0...",
    "emoji": "👍"
  }'

Forward message

POST/api/v1/message/forward

Parameters

ParameterTypeRequiredDescription
targetstringYesRecipient number 62xxx.
messageIdstringYesID message that diteruskan.
bash
curl -X POST https://wafa.st/api/v1/message/forward \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -d '{
    "target": "628999888777",
    "messageId": "[email protected]_3EB0..."
  }'

Edit message

POST/api/v1/message/edit

Parameters

ParameterTypeRequiredDescription
targetstringYesChat number 62xxx.
messageIdstringYesID of the message to edit (must be an outgoing message).
textstringYesNew text that replaces the message.
bash
curl -X POST https://wafa.st/api/v1/message/edit \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -d '{
    "target": "628123456789",
    "messageId": "[email protected]_3EB0...",
    "text": "Sorry, the price has been updated to $15.00"
  }'

Delete message

POST/api/v1/message/delete

Delete an outgoing message for everyone.

Parameters

ParameterTypeRequiredDescription
targetstringYesChat number 62xxx.
messageIdstringYesID of the message to delete (must be an outgoing message).
bash
curl -X POST https://wafa.st/api/v1/message/delete \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -d '{
    "target": "628123456789",
    "messageId": "[email protected]_3EB0..."
  }'

Delivery status

GET/api/v1/message/status

Check the delivery status of an outgoing message (sent, delivered, read, failed).

Parameters (query)

ParameterTypeRequiredDescription
idstringYesWhatsApp message id, from the id field in the /send response.
bash
curl "https://wafa.st/api/v1/message/[email protected]_3EB0..." \
  -H "Authorization: Bearer YOUR_API_KEY"
json
{
  "status": true,
  "id": "[email protected]_3EB0...",
  "device": "CS Utama",
  "deliveryStatus": "delivered",
  "to": "628123456789",
  "ackAt": "2026-06-26T10:00:05.000Z",
  "sentAt": "2026-06-26T10:00:00.000Z"
}

Inbox / Message list

GET/api/v1/messages

Fetch a list of messages (incoming by default). Useful for polling the inbox without webhooks.

Parameters (query)

ParameterTypeRequiredDescription
directionstringNoin (default) | out | all.
limitnumberNoDefault 30, maks 200.
devicestringNoID/name number tertentu.
sincestringNoISO timestamp — fetch anything newer.
group0/1No1 for ikutkan message group.
bash
curl "https://wafa.st/api/v1/messages?direction=in&limit=20" \
  -H "Authorization: Bearer YOUR_API_KEY"

Upload Media

POST/api/v1/media

Upload a file (max 16 MB) and use the returned URL as mediaUrl in /send. Useful when the file does not have a public URL yet.

Form-data

ParameterTypeRequiredDescription
filefileYesFile that diupload (multipart/form-data, maks 16 MB).
bash
curl -X POST https://wafa.st/api/v1/media \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -F "[email protected]"
# -> { "status": true, "url": "...", "name": "brosur.jpg", "mime": "image/jpeg", "size": 12345 }

Groups

See your groups or create a new one.

GET/api/v1/groups
bash
curl https://wafa.st/api/v1/groups \
  -H "Authorization: Bearer YOUR_API_KEY"
json
{
  "status": true,
  "groups": [
    { "id": "[email protected]", "name": "Tim Sales", "participants": 12 }
  ]
}
POST/api/v1/groups

Parameters (create group)

ParameterTypeRequiredDescription
namestringYesNew group name.
participantsstring[]YesNumber member awal (62xxx). list
bash
curl -X POST https://wafa.st/api/v1/groups \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -d '{
    "name": "Tim Proyek A",
    "participants": ["628111111111", "628222222222"]
  }'

Group members

POST/api/v1/groups/participants

Manage group members: add, remove, promote to admin or demote.

Parameters

ParameterTypeRequiredDescription
groupIdstringYesGroup ID, e.g. [email protected].
actionstringYes"add", "remove", "promote" or "demote".
participantsstring[]YesThe number to act on (e.g. 15551234567).
bash
curl -X POST https://wafa.st/api/v1/groups/participants \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -d '{
    "groupId": "[email protected]",
    "action": "add",
    "participants": ["628333333333"]
  }'

Check number WhatsApp

GET/api/v1/contacts/check

Check whether a number is on WhatsApp.

Parameters (query string)

ParameterTypeRequiredDescription
phonestringYesNumber that dicek, format 62xxx.
bash
curl "https://wafa.st/api/v1/contacts/check?phone=628123456789" \
  -H "Authorization: Bearer YOUR_API_KEY"
json
{
  "status": true,
  "phone": "628123456789",
  "exists": true
}

OTP

Send and verify OTP codes over WhatsApp. Codes are generated server-side and expire.

POST/api/v1/otp/send

Parameters (send)

ParameterTypeRequiredDescription
targetstringYesRecipient number 62xxx.
lengthnumberNoPanjang kode (default 6).
templatestringNoTemplate message, gunakan {code} sebagai placeholder.
bash
curl -X POST https://wafa.st/api/v1/otp/send \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -d '{
    "target": "628123456789",
    "length": 6,
    "template": "Your verification code: {code}"
  }'
POST/api/v1/otp/verify

Parameters (verify)

ParameterTypeRequiredDescription
targetstringYesRecipient number 62xxx.
codestringYesKode OTP that diterima user.
bash
curl -X POST https://wafa.st/api/v1/otp/verify \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -d '{
    "target": "628123456789",
    "code": "123456"
  }'
json
{ "status": true, "verified": true, "detail": "OTP valid" }

Number list

GET/api/v1/devices

Check the connection status of all your numbers.

bash
curl https://wafa.st/api/v1/devices \
  -H "Authorization: Bearer YOUR_API_KEY"
json
{
  "status": true,
  "devices": [
    { "name": "CS Utama", "phoneNumber": "628...", "status": "connected", "messageCount": 1240 }
  ]
}

Webhook - Message Incoming

Every number can have its own webhook URL (set it when you add the number). When a message comes in, Wafast sends an HTTP POST to your URL:

json
{
  "device": "CS Utama",
  "from": "628123456789",
  "body": "hi, is this in stock",
  "type": "text",
  "timestamp": 1750000000,
  "id": "[email protected]_3EB0..."
}

Header on every request

  • X-Wafast-Event — event type: message, message.ack, etc.
  • X-Wafast-Signature: sha256=<hex> — only if the device has a signing secret. HMAC-SHA256 of the raw body using that secret. Verify it before you trust the payload.

Delivery status event (message.ack)

json
{ "device": "CS Utama", "event": "message.ack", "id": "[email protected]_3EB0...", "status": "delivered" }

Verify the signature (Node)

javascript
import crypto from "crypto";
app.post("/webhook", express.raw({ type: "application/json" }), (req, res) => {
  const sig = req.headers["x-wafast-signature"] || "";
  const expected = "sha256=" + crypto.createHmac("sha256", WEBHOOK_SECRET).update(req.body).digest("hex");
  if (sig && !crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected))) return res.status(401).end();
  const evt = JSON.parse(req.body.toString());
  // hand off to your own LLM (we do not provide a built-in AI)
  res.json({ ok: true });
});
A webhook must always answer quickly (e.g. 200 OK). Do the heavy work (LLM, database) asynchronously. A failed delivery is retried once, and every attempt is recorded on the Webhook page.

Error codes

Errors return { "status": false, "error": "..." } with HTTP code:

HTTPMeaningTypical cause
400Bad RequestParameter required empty / is not valid
401UnauthorizedAPI key is wrong, revoked, or missing
404Not FoundThe requested number, message, or group was not found
409Conflictnumber not connected (scan the QR code first)
422UnprocessableInvalid parameter format (e.g. poll options < 2)
429Too Many Requestsover 60 requests per minute for this key — wait a moment
502Bad Gatewaythe engine failed to process the request

Migrate from Fonnte / Wablas

The Wafast API is intentionally close to Fonnte so moving over is easy. Parameter mapping:

FonnteWafastNotes
targettargetSame. Commas for several numbers.
messagemessageSame.
urlmediaUrlMedia URL (the alias url is also accepted).
delaydelaySame, format "5-10".
Authorization: TOKENAuthorization: KEYA raw token is still accepted.
Most existing integrations work after changing just the base URL and token. The advanced endpoints (location, polls, groups, OTP) are Wafast additions.

Keeping your number healthy

  • Use a number that has been active for a while, not a brand-new one.
  • Use a random delay (e.g. 5-15) when sending to several numbers, and avoid very large sends at once.
  • Only message people who know you or are expecting to hear from you: notifications, OTP codes, replies.
  • Vary the wording; avoid sending identical text to everyone.
  • Warm up a new number: chat normally for a few days before using it through the API.
Keep your sending pattern natural so your number stays in good standing with WhatsApp. It is the pattern that matters, not the API.

Ready to try it?

Open playground