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, session nameINSTANCE, andAPI_KEY. - 02
Connect your number
Scan the QR code, then check that the connection state is
open. - 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 my-business with your session name and .example and YOUR_… placeholders with your details. v1 is the documentation version; do not add /v1 or /docs/v1 to API request paths.
export BASE_URL="https://connect.whats-flow.net"
export INSTANCE="my-business"
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 session key in the apikey header on every request. POST requests in this guide use JSON.
| Header | Value | Usage |
|---|---|---|
apikey | YOUR_API_KEY | Required for every request |
Content-Type | application/json | For JSON request bodies |
Make requests from your server. Never expose your key in browser code or a public repository. The {instance} path parameter is your assigned session name, not a phone number.
Connect a WhatsApp number#
Request a QR code for the session provisioned for your account. Scan it in WhatsApp under Linked devices → Link a device.
/instance/connect/{instance}API KEYRequest parameters
instancepath · stringRequiredThe session name supplied on activation, for example my-business.
This request does not require a body.
While waiting to connect, base64 contains the QR image. This abridged example is not a scannable code. Request a new code if it expires. An already connected session may return connection information instead.
curl --request GET "${BASE_URL}/instance/connect/${INSTANCE}" \
--header "apikey: ${API_KEY}"
Sample response (abridged)200
{
"pairingCode": null,
"code": "QR_CODE_CONTENT",
"base64": "data:image/png;base64,QR_IMAGE_DATA",
"count": 1
}
Connection state#
Check that your session is ready before sending messages. Read state inside instance to see its current connection status.
/instance/connectionState/{instance}API KEYRequest parameters
instancepath · stringRequiredThe session name supplied on activation, for example my-business.
This request does not require a body.
open: connected and ready to send. connecting: establishing a connection. close: disconnected; check the phone and reconnect if needed.
curl --request GET "${BASE_URL}/instance/connectionState/${INSTANCE}" \
--header "apikey: ${API_KEY}"
Sample response (abridged)200
{
"instance": {
"instanceName": "my-business",
"state": "open"
}
}
Send a text message#
Send an order confirmation, shipping update, or follow-up message. Use the recipient's international phone number with digits only, without + or spaces.
/message/sendText/{instance}API KEYRequest parameters
instancepath · stringRequiredThe session name supplied on activation, for example my-business.
numberstringRequiredRecipient phone number including the country code, for example 201000000000.
textstringRequiredThe text of the message to send.
linkPreviewbooleanOptionalEnable link previews in the message.
delayintegerOptionalDelay before sending, in milliseconds.
This response is abridged. Store key.id to track the message. An accepted request or PENDING status does not confirm delivery; follow message updates through webhooks.
curl --request POST "${BASE_URL}/message/sendText/${INSTANCE}" \
--header "apikey: ${API_KEY}" \
--header "Content-Type: application/json" \
--data '{
"number": "201000000000",
"text": "Hi Ahmed, your order #1042 is confirmed. Thank you for choosing us!",
"linkPreview": false
}'
// Node.js 18+ · server-side
const baseUrl = process.env.BASE_URL.replace(/\/$/, "");
const instance = encodeURIComponent(process.env.INSTANCE);
const response = await fetch(`${baseUrl}/message/sendText/${instance}`, {
method: "POST",
headers: {
apikey: process.env.API_KEY,
"Content-Type": "application/json"
},
body: JSON.stringify({
number: "201000000000",
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.key.id);
Use the environment variables from the quickstart. Run this example on your server.
use Illuminate\Support\Facades\Http;
// Read credentials from your server configuration.
$baseUrl = rtrim(config('services.whatsflow.url'), '/');
$instance = rawurlencode(config('services.whatsflow.instance'));
$message = Http::withHeaders([
'apikey' => config('services.whatsflow.key'),
])->timeout(30)->post(
"{$baseUrl}/message/sendText/{$instance}",
[
'number' => '201000000000',
'text' => 'Hi Ahmed, your order #1042 is confirmed.',
'linkPreview' => false,
]
)->throw()->json();
$messageId = $message['key']['id'];
Add url, instance, and key under services.whatsflow in config/services.php, loading their values from environment variables.
Sample response (abridged)201
{
"key": {
"remoteJid": "201000000000@s.whatsapp.net",
"fromMe": true,
"id": "EXAMPLE_MESSAGE_ID"
},
"status": "PENDING"
}
Send images and files#
Share a product image, video, or document such as a PDF invoice using a direct URL the service can access.
/message/sendMedia/{instance}API KEYRequest parameters
instancepath · stringRequiredThe session name supplied on activation, for example my-business.
numberstringRequiredRecipient phone number in international format.
mediatypestringRequiredMedia type: image, video, or document.
mediastringRequiredA direct file URL or its Base64-encoded contents.
mimetypestringOptionalThe MIME type matching the file, such as application/pdf.
fileNamestringOptionalThe filename shown to the recipient, especially for documents.
captionstringOptionalA caption or text accompanying the file.
Replace the example file URL with a real one that serves the file directly without a login page. Set mediatype and mimetype to match. File size and sending limits depend on your account settings.
curl --request POST "${BASE_URL}/message/sendMedia/${INSTANCE}" \
--header "apikey: ${API_KEY}" \
--header "Content-Type: application/json" \
--data '{
"number": "201000000000",
"mediatype": "document",
"mimetype": "application/pdf",
"media": "https://your-store.example/invoices/1042.pdf",
"fileName": "invoice-1042.pdf",
"caption": "Invoice for your order #1042"
}'
Sample response (abridged)201
{
"key": {
"id": "EXAMPLE_MEDIA_MESSAGE_ID",
"fromMe": true
},
"status": "PENDING"
}
Receive events · Webhooks#
Receive messages and connection updates on your server automatically. Create an HTTPS endpoint that accepts POST requests with JSON, then register it for your session.
/webhook/set/{instance}API KEYRequest parameters
instancepath · stringRequiredThe session name supplied on activation, for example my-business.
webhook.enabledbooleanRequiredEnable or disable event delivery.
webhook.urlstringRequiredThe HTTPS URL on your server that will receive events.
webhook.eventsarrayOptionalThe events to subscribe to. Specify them as shown in the example.
webhook.byEventsbooleanOptionalSet to false to send all events to the same URL.
webhook.base64booleanOptionalInclude media as Base64 when enabled.
webhook.headersobjectOptionalCustom headers for verifying requests on your server.
Verify X-Webhook-Secret before processing the request. Return 200 promptly and handle longer tasks in the background. Deduplicate events using the message ID, event type, and update status. Do not expose API keys or full event payloads in public logs.
curl --request POST "${BASE_URL}/webhook/set/${INSTANCE}" \
--header "apikey: ${API_KEY}" \
--header "Content-Type: application/json" \
--data '{
"webhook": {
"enabled": true,
"url": "https://your-app.example/webhooks/whatsflow",
"byEvents": false,
"base64": false,
"headers": {
"X-Webhook-Secret": "YOUR_WEBHOOK_SECRET"
},
"events": [
"MESSAGES_UPSERT",
"MESSAGES_UPDATE",
"CONNECTION_UPDATE"
]
}
}'
Sample response (abridged)201
{
"enabled": true,
"url": "https://your-app.example/webhooks/whatsflow",
"events": [
"MESSAGES_UPSERT",
"MESSAGES_UPDATE",
"CONNECTION_UPDATE"
]
}
Key events
| Subscription event | When is it sent? |
|---|---|
MESSAGES_UPSERT | When a message is added. Check data.key.fromMe to distinguish incoming from outgoing messages. |
MESSAGES_UPDATE | When a message is updated, including delivery or read status changes when available. |
CONNECTION_UPDATE | When the session connection state changes. |
What does an incoming message event look like?
An abridged text message example. Incoming event names use a format such as messages.upsert. The data structure depends on the event and message type.
{
"event": "messages.upsert",
"instance": "my-business",
"data": {
"key": {
"remoteJid": "201000000000@s.whatsapp.net",
"fromMe": false,
"id": "EXAMPLE_INCOMING_ID"
},
"pushName": "Ahmed",
"message": {
"conversation": "When will my order arrive?"
},
"messageType": "conversation"
}
}
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 your apikey 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. |
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.