Guide to Whatsflow API.
Connect WhatsApp to your app and send messages from one place.
Everything you need to get started, with clear examples you can adapt.
From zero to your first message
Start with the credentials provided when your API service is activated.
- 01
Prepare your credentials
Your service address
BASE_URL, assignedSESSIONname, andAPI_KEY. - 02
Connect your number
Start the session, scan the QR code when prompted, and wait until its status is
WORKING. - 03
Send your first message
Try the message example with a test number you own, then add it to your app.
Use the service address above with the session name and key provided during activation. Replace default with your session name and YOUR_… and .example placeholders with your details. v2 is the documentation version; request paths start with /api. Do not add /docs/v2 or /dashboard.
export BASE_URL="https://waha.whats-flow.net"
export SESSION="default"
export API_KEY="YOUR_API_KEY"
Set these variables once in your terminal session before running the cURL examples. In your app, keep them in your server configuration.
One key for your requests
Include your account key in the X-Api-Key header on every request. Use a JSON body when sending messages or updating settings.
| Header | Value | Usage |
|---|---|---|
X-Api-Key | YOUR_API_KEY | Required for every request |
Content-Type | application/json | For JSON request bodies |
Accept | application/json | Request JSON responses, including the encoded QR image. |
Make requests from your server and keep the key out of browsers and public repositories. {session} in the path and session in the request body refer to your assigned session name, not your phone number.
Start a session#
Start the session provisioned by the Whatsflow team for your account. The session must already exist; this request does not create one.
/api/sessions/{session}/startAPI KEYRequest parameters
sessionpath · stringRequiredThe session name provided during activation. Replace default with your session name.
This request does not require a body.
Replace default with the session name provided during activation. Check its status after starting: request a QR code at SCAN_QR_CODE and send messages only at WORKING.
curl --request POST "${BASE_URL}/api/sessions/${SESSION}/start" \
--header "X-Api-Key: ${API_KEY}" \
--header "Accept: application/json"
Sample response (abridged)201
{
"name": "default",
"status": "STARTING"
}
Connect a WhatsApp number#
When the session is at SCAN_QR_CODE, request the QR image and scan it from WhatsApp → Linked devices → Link a device.
/api/{session}/auth/qrAPI KEYRequest parameters
sessionpath · stringRequiredThe session name provided during activation. Replace default with your session name.
This request does not require a body.
Send Accept: application/json to receive a Base64 image; otherwise, the response may be a binary image. Display it using data:image/png;base64, followed by the data value. This example is a placeholder, not a scannable QR code.
curl --request GET "${BASE_URL}/api/${SESSION}/auth/qr" \
--header "X-Api-Key: ${API_KEY}" \
--header "Accept: application/json"
Sample response (abridged)200
{
"mimetype": "image/png",
"data": "BASE64_QR_IMAGE_DATA"
}
Connection state#
Check status before sending. WORKING means connected, STARTING means the connection is starting, and SCAN_QR_CODE means the session is waiting for a scan.
/api/sessions/{session}API KEYRequest parameters
sessionpath · stringRequiredThe session name provided during activation. Replace default with your session name.
This request does not require a body.
STOPPED means the session is stopped; use the start request. FAILED means the connection failed; check the phone and contact support if you need to reconnect. This response is abridged and may also include account information and config.
curl --request GET "${BASE_URL}/api/sessions/${SESSION}" \
--header "X-Api-Key: ${API_KEY}" \
--header "Accept: application/json"
Sample response (abridged)200
{
"name": "default",
"status": "WORKING"
}
Send a text message#
Send a message to a chatId. For a direct conversation, use the international phone number with digits only, followed by @c.us; for example, 201000000000@c.us.
/api/sendTextAPI KEYRequest parameters
sessionstringRequiredThe session name assigned to your account. default is an example.
chatIdstringRequiredThe chat identifier: an international number followed by @c.us for direct conversations.
textstringRequiredThe text of the message to send.
linkPreviewbooleanOptionalEnable link previews in the message.
reply_tostringOptionalThe ID of a previous message to reply to. Use the complete ID returned by the service.
Store the complete id to track or reply to the message. The response is abridged, and a successful request does not confirm delivery. Subscribe to message.ack for delivery and read updates when available.
curl --request POST "${BASE_URL}/api/sendText" \
--header "X-Api-Key: ${API_KEY}" \
--header "Accept: application/json" \
--header "Content-Type: application/json" \
--data @- <<JSON
{
"session": "${SESSION}",
"chatId": "201000000000@c.us",
"text": "Hi Ahmed, your order #1042 is confirmed. Thank you for choosing us!",
"linkPreview": false
}
JSON
// Node.js 18+ · server-side
const baseUrl = process.env.BASE_URL.replace(/\/$/, "");
const response = await fetch(`${baseUrl}/api/sendText`, {
method: "POST",
headers: {
"X-Api-Key": process.env.API_KEY,
"Content-Type": "application/json"
},
body: JSON.stringify({
session: process.env.SESSION,
chatId: "201000000000@c.us",
text: "Hi Ahmed, your order #1042 is confirmed.",
linkPreview: false
})
});
if (!response.ok) {
throw new Error(`Request failed: ${response.status}`);
}
const message = await response.json();
console.log(message.id);
Use the environment variables from the quickstart. Run this example on your server.
use Illuminate\Support\Facades\Http;
$baseUrl = rtrim(config('services.whatsflow_v2.url'), '/');
$message = Http::withHeaders([
'X-Api-Key' => config('services.whatsflow_v2.key'),
])->timeout(30)->post("{$baseUrl}/api/sendText", [
'session' => config('services.whatsflow_v2.session'),
'chatId' => '201000000000@c.us',
'text' => 'Hi Ahmed, your order #1042 is confirmed.',
'linkPreview' => false,
])->throw()->json();
$messageId = $message['id'];
Add url, session, and key under services.whatsflow_v2 in config/services.php, reading their values from environment variables.
Sample response (abridged)201
{
"id": "true_201000000000@c.us_EXAMPLE_MESSAGE_ID",
"fromMe": true,
"to": "201000000000@c.us"
}
Send an image#
Send an image with an optional caption using a direct file URL. Media sending must be enabled for your account.
/api/sendImageAPI KEYRequest parameters
sessionstringRequiredThe session name assigned to your account. default is an example.
chatIdstringRequiredThe chat identifier: an international number followed by @c.us for direct conversations.
file.urlstringRequiredA direct URL the service can download. Replace the .example address.
file.mimetypestringRequiredThe MIME type matching the file, such as image/jpeg or application/pdf.
captionstringOptionalA caption or text accompanying the file.
Media sending depends on the features enabled for your account; contact the team if it is unavailable. Use a direct file URL that does not require a login, not an HTML page. Size limits depend on the service configuration.
curl --request POST "${BASE_URL}/api/sendImage" \
--header "X-Api-Key: ${API_KEY}" \
--header "Accept: application/json" \
--header "Content-Type: application/json" \
--data @- <<JSON
{
"session": "${SESSION}",
"chatId": "201000000000@c.us",
"file": {
"mimetype": "image/jpeg",
"url": "https://your-store.example/products/item.jpg"
},
"caption": "The product you requested"
}
JSON
Sample response (abridged)201
{
"id": "true_201000000000@c.us_EXAMPLE_IMAGE_ID",
"fromMe": true,
"hasMedia": true
}
Send a document#
Share a PDF invoice or document from a direct URL. Set the file type and name inside file. Media sending must be enabled for your account.
/api/sendFileAPI KEYRequest parameters
sessionstringRequiredThe session name assigned to your account. default is an example.
chatIdstringRequiredThe chat identifier: an international number followed by @c.us for direct conversations.
file.urlstringRequiredA direct URL the service can download. Replace the .example address.
file.mimetypestringRequiredThe MIME type matching the file, such as image/jpeg or application/pdf.
file.filenamestringOptionalThe filename shown to the recipient, such as invoice-1042.pdf.
captionstringOptionalA caption or text accompanying the file.
This example uses file.url. To send the content directly, replace url with a Base64 file.data field, keeping mimetype and filename. Contact the team to confirm media availability and file limits.
curl --request POST "${BASE_URL}/api/sendFile" \
--header "X-Api-Key: ${API_KEY}" \
--header "Accept: application/json" \
--header "Content-Type: application/json" \
--data @- <<JSON
{
"session": "${SESSION}",
"chatId": "201000000000@c.us",
"file": {
"mimetype": "application/pdf",
"filename": "invoice-1042.pdf",
"url": "https://your-store.example/invoices/1042.pdf"
},
"caption": "Invoice for your order #1042"
}
JSON
Sample response (abridged)201
{
"id": "true_201000000000@c.us_EXAMPLE_FILE_ID",
"fromMe": true,
"hasMedia": true
}
Receive events · Webhooks#
Set config.webhooks for the session to receive messages and connection updates on your server. First retrieve the current settings using GET /api/sessions/{session}, and preserve the other config values when updating.
/api/sessions/{session}API KEYRequest parameters
sessionpath · stringRequiredThe session name provided during activation. Replace default with your session name.
config.webhooksarrayRequiredThe list of webhook destinations. This example uses one destination.
config.webhooks[].urlstringRequiredAn HTTPS URL on your server that accepts POST requests with JSON.
config.webhooks[].eventsarrayRequiredThe events to subscribe to, such as message, message.ack, and session.status.
config.webhooks[].customHeadersarrayOptionalHeaders to verify incoming requests. Each entry contains name and value.
PUT replaces config and restarts a running session. This example only shows webhooks; merge your other settings before running it. On your receiver, verify X-Webhook-Secret, return 200 promptly, and use the event id to avoid processing duplicates.
curl --request PUT "${BASE_URL}/api/sessions/${SESSION}" \
--header "X-Api-Key: ${API_KEY}" \
--header "Accept: application/json" \
--header "Content-Type: application/json" \
--data @- <<JSON
{
"config": {
"webhooks": [
{
"url": "https://your-app.example/webhooks/whatsflow",
"events": [
"message",
"message.ack",
"session.status"
],
"customHeaders": [
{
"name": "X-Webhook-Secret",
"value": "YOUR_WEBHOOK_SECRET"
}
]
}
]
}
}
JSON
Sample response (abridged)200
{
"name": "default",
"status": "STARTING"
}
Key events
| Subscription event | When is it sent? |
|---|---|
message | When a new message arrives. For text messages, read payload.from and payload.body. |
message.ack | When a message status changes; for example, DEVICE for delivery or READ for a read receipt in payload.ackName, when available. |
session.status | When the connection changes. Read the status from payload.status. |
What does an incoming message event look like?
An abridged message event. The payload contains message data and its structure varies by event. The outer event id is separate from the message ID in payload.id.
{
"id": "evt_EXAMPLE_EVENT_ID",
"event": "message",
"session": "default",
"payload": {
"id": "false_201000000000@c.us_EXAMPLE_INCOMING_ID",
"from": "201000000000@c.us",
"fromMe": false,
"body": "When will my order arrive?",
"hasMedia": false
}
}
Understand the response. Know your next step.
Check both the HTTP status and the error message. Error details may vary with the request and service configuration.
| Code | Meaning | What should you check? |
|---|---|---|
200 / 201 | Successful request | Read the response. A successful send request does not confirm delivery. |
400 | Invalid request | Check required fields, phone number formatting, and the media type. |
401 | Authentication failed | Check the value of the X-Api-Key header. |
403 | Access denied | Check that the key has access to the requested session. |
404 | Resource not found | Verify the base URL, request path, and session name. |
422 | Session not ready or request cannot be processed | Check the error details and session status. Sending messages requires WORKING. |
501 | Feature unavailable | Contact the team to confirm that the requested feature is enabled for your account. |
429 | Rate limit exceeded, if enabled | Reduce your request rate and respect Retry-After if returned. |
5xx | Service error | Check the connection and retain request details for support, excluding secret keys. |
Manage your sending
Queue outgoing messages and respect your account limits. If a response is lost, check the result before retrying to avoid sending the same message twice.
Keep conversations welcome
Send messages to people who have agreed to hear from you, and respect opt-out requests. Use a test number before messaging customers.
Ready for your next step?
Contact us for API credentials or help with your integration.