Introduction
Wafast is a WhatsApp API: send and receive WhatsApp messages over a simple REST API. Every endpoint returns JSON.
https://wafa.stJSON (request & response)Multi-deviceBearer token / ?token=Every successful response looks like { "status": true, ... }. A failed response looks like { "status": false, "error": "..." }.
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.
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
/api/v1/sendParameters
| Parameter | Type | Required | Description |
|---|---|---|---|
target | string | Yes | Recipient number in international format. Separate several numbers with commas. |
message | string | Yes* | Enter the message text. Required when there is no mediaUrl. |
device | string | No | Name/ID number. Empty = the first connected number. |
mediaUrl | string | No | Public URL of the image/file to send. |
mediaType | string | No | "image" for pictures, "file" for documents. |
filename | string | No | Name file (for document). |
delay | string | No | Gap 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
{
"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.
# 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.
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"
}'Send location
/api/v1/send/locationParameters
| Parameter | Type | Required | Description |
|---|---|---|---|
target | string | Yes | Recipient number 62xxx. |
latitude | number | Yes | Garis lintang, mis. -6.2088. |
longitude | number | Yes | Garis bujur, mis. 106.8456. |
title | string | No | The location label shown in the message. |
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
/api/v1/send/pollParameters
| Parameter | Type | Required | Description |
|---|---|---|---|
target | string | Yes | Recipient number 62xxx. |
name | string | Yes | Pertanyaan / judul polling. |
options | string[] | Yes | Pilihan jawaban (at least 2). list |
multipleAnswers | boolean | No | true = the reader may pick more than one option. |
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
/api/v1/send/contactParameters
| Parameter | Type | Required | Description |
|---|---|---|---|
target | string | Yes | Recipient number 62xxx. |
name | string | Yes | Contact name to send. |
phone | string | Yes | Number contact format 62xxx. |
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
/api/v1/send/voiceParameters
| Parameter | Type | Required | Description |
|---|---|---|---|
target | string | Yes | Recipient number 62xxx. |
url | string | Yes | Public URL of the audio file (mp3/ogg). |
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
/api/v1/send/videoParameters
| Parameter | Type | Required | Description |
|---|---|---|---|
target | string | Yes | Recipient number 62xxx. |
url | string | Yes | Public URL of the video file (mp4). |
caption | string | No | Caption text for the video. |
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
/api/v1/typingShow or hide the "typing" indicator to the person you are chatting with.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
target | string | Yes | Recipient number 62xxx. |
action | string | Yes | "start" mulai mengetik, "stop" berhenti. |
curl -X POST https://wafa.st/api/v1/typing \
-H "Authorization: Bearer YOUR_API_KEY" \
-d '{
"target": "628123456789",
"action": "start"
}'Mark as read
/api/v1/seenMark a message from the other side as read (blue ticks).
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
target | string | Yes | Chat number 62xxx. |
messageId | string | No | ID message tertentu. Empty = tandai chat. |
curl -X POST https://wafa.st/api/v1/seen \
-H "Authorization: Bearer YOUR_API_KEY" \
-d '{
"target": "628123456789",
"messageId": "[email protected]_3EB0..."
}'Message reaction
/api/v1/reactionReact to a message with an emoji. Send an empty emoji ("") to remove the reaction.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
messageId | string | Yes | ID message that ingin direaksi. |
emoji | string | Yes | Emoji reaksi, mis. "👍". Empty = delete reaksi. |
curl -X POST https://wafa.st/api/v1/reaction \
-H "Authorization: Bearer YOUR_API_KEY" \
-d '{
"messageId": "[email protected]_3EB0...",
"emoji": "👍"
}'Forward message
/api/v1/message/forwardParameters
| Parameter | Type | Required | Description |
|---|---|---|---|
target | string | Yes | Recipient number 62xxx. |
messageId | string | Yes | ID message that diteruskan. |
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
/api/v1/message/editParameters
| Parameter | Type | Required | Description |
|---|---|---|---|
target | string | Yes | Chat number 62xxx. |
messageId | string | Yes | ID of the message to edit (must be an outgoing message). |
text | string | Yes | New text that replaces the message. |
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
/api/v1/message/deleteDelete an outgoing message for everyone.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
target | string | Yes | Chat number 62xxx. |
messageId | string | Yes | ID of the message to delete (must be an outgoing message). |
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
/api/v1/message/statusCheck the delivery status of an outgoing message (sent, delivered, read, failed).
Parameters (query)
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Yes | WhatsApp message id, from the id field in the /send response. |
curl "https://wafa.st/api/v1/message/[email protected]_3EB0..." \
-H "Authorization: Bearer YOUR_API_KEY"{
"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
/api/v1/messagesFetch a list of messages (incoming by default). Useful for polling the inbox without webhooks.
Parameters (query)
| Parameter | Type | Required | Description |
|---|---|---|---|
direction | string | No | in (default) | out | all. |
limit | number | No | Default 30, maks 200. |
device | string | No | ID/name number tertentu. |
since | string | No | ISO timestamp — fetch anything newer. |
group | 0/1 | No | 1 for ikutkan message group. |
curl "https://wafa.st/api/v1/messages?direction=in&limit=20" \
-H "Authorization: Bearer YOUR_API_KEY"Upload Media
/api/v1/mediaUpload 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
| Parameter | Type | Required | Description |
|---|---|---|---|
file | file | Yes | File that diupload (multipart/form-data, maks 16 MB). |
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.
/api/v1/groupscurl https://wafa.st/api/v1/groups \
-H "Authorization: Bearer YOUR_API_KEY"{
"status": true,
"groups": [
{ "id": "[email protected]", "name": "Tim Sales", "participants": 12 }
]
}/api/v1/groupsParameters (create group)
| Parameter | Type | Required | Description |
|---|---|---|---|
name | string | Yes | New group name. |
participants | string[] | Yes | Number member awal (62xxx). list |
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
/api/v1/groups/participantsManage group members: add, remove, promote to admin or demote.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
groupId | string | Yes | Group ID, e.g. [email protected]. |
action | string | Yes | "add", "remove", "promote" or "demote". |
participants | string[] | Yes | The number to act on (e.g. 15551234567). |
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
/api/v1/contacts/checkCheck whether a number is on WhatsApp.
Parameters (query string)
| Parameter | Type | Required | Description |
|---|---|---|---|
phone | string | Yes | Number that dicek, format 62xxx. |
curl "https://wafa.st/api/v1/contacts/check?phone=628123456789" \
-H "Authorization: Bearer YOUR_API_KEY"{
"status": true,
"phone": "628123456789",
"exists": true
}OTP
Send and verify OTP codes over WhatsApp. Codes are generated server-side and expire.
/api/v1/otp/sendParameters (send)
| Parameter | Type | Required | Description |
|---|---|---|---|
target | string | Yes | Recipient number 62xxx. |
length | number | No | Panjang kode (default 6). |
template | string | No | Template message, gunakan {code} sebagai placeholder. |
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}"
}'/api/v1/otp/verifyParameters (verify)
| Parameter | Type | Required | Description |
|---|---|---|---|
target | string | Yes | Recipient number 62xxx. |
code | string | Yes | Kode OTP that diterima user. |
curl -X POST https://wafa.st/api/v1/otp/verify \
-H "Authorization: Bearer YOUR_API_KEY" \
-d '{
"target": "628123456789",
"code": "123456"
}'{ "status": true, "verified": true, "detail": "OTP valid" }Number list
/api/v1/devicesCheck the connection status of all your numbers.
curl https://wafa.st/api/v1/devices \
-H "Authorization: Bearer YOUR_API_KEY"{
"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:
{
"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)
{ "device": "CS Utama", "event": "message.ack", "id": "[email protected]_3EB0...", "status": "delivered" }Verify the signature (Node)
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 });
});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:
| HTTP | Meaning | Typical cause |
|---|---|---|
400 | Bad Request | Parameter required empty / is not valid |
401 | Unauthorized | API key is wrong, revoked, or missing |
404 | Not Found | The requested number, message, or group was not found |
409 | Conflict | number not connected (scan the QR code first) |
422 | Unprocessable | Invalid parameter format (e.g. poll options < 2) |
429 | Too Many Requests | over 60 requests per minute for this key — wait a moment |
502 | Bad Gateway | the 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:
| Fonnte | Wafast | Notes |
|---|---|---|
target | target | Same. Commas for several numbers. |
message | message | Same. |
url | mediaUrl | Media URL (the alias url is also accepted). |
delay | delay | Same, format "5-10". |
Authorization: TOKEN | Authorization: KEY | A raw token is still accepted. |
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.
Ready to try it?
Open playground