WhatsApp REST API documentation: QR sessions, messages and webhooks
Use these WhatsApp API integration docs to send messages from your backend: connect an existing number by QR code, authenticate with a session-bound Bearer key, call the send-message REST endpoint, and receive signed webhook events. Follow the Node.js, Laravel, Python and .NET examples below.
RelayWA is a QR-linked WhatsApp Web session API, not Meta's official WhatsApp Cloud API. Outbound sends are direct; your application handles scheduling, pacing and send retries.
https://relaywa.com/apiREST API
JSON over HTTPSUse scoped Bearer credentials from your backend.
Realtime
SSE + webhooksWatch session events and receive outbound callbacks.
Multi-session
One API, many numbersRoute each send through a specific WhatsApp session.
WhatsApp REST API quickstart
Connect a QR-linked session, copy a scoped API key and send your first WhatsApp message from your server.
Start the 7-day trial or choose a paid plan, then finish workspace setup.
Create a session, scan the QR from WhatsApp → Linked devices and wait for connected.
Open View API key after connecting. Your session key is created automatically and includes messages.send.
Call /api/send-message with your session key and store the RelayWA message ID for status checks.
curl -X POST 'https://relaywa.com/api/send-message' \
-H 'Authorization: Bearer rw_session_YOUR_KEY' \
-H 'Content-Type: application/json' \
-d '{
"to": "E164_RECIPIENT_NUMBER",
"text": "Your order is ready.",
"clientMessageId": "order-1001"
}'E164_RECIPIENT_NUMBER.Authentication
API requests use Bearer credentials. Keep them server-side and rotate/revoke them when an integration changes ownership.
Authorization: Bearer rw_session_YOUR_KEYOrganization key
Can operate across sessions in its organization when the key has the required scope.
Session-bound key
Starts with rw_session_ and restricts the credential to one WhatsApp session for tighter isolation.
Do not embed RelayWA keys in browser JavaScript or distributable mobile binaries. Send from your own backend.
API keys & scopes
Dashboard users create and revoke keys. API access is then limited by scopes and, for session tokens, by the bound session.
sessions.readsessions.managemessages.readmessages.sendcontacts.readchats.readgroups.readwebhooks.readwebhooks.manageDashboard-only management
Creating/revoking API keys and changing session lifecycle requires authenticated dashboard permissions.
Least privilege
Give integrations only the scopes they need. A reporting service usually does not need messages.send.
Sessions & QR lifecycle
A session represents one linked WhatsApp account and owns its runtime, message routing and connection state.
/whatsapp-sessionssessions.readList workspace sessions/whatsapp-sessions/:sessionIdsessions.readRead one session/whatsapp-sessions/:sessionId/qrcodesessions.readRead QR availability / data/whatsapp-sessions/:sessionId/eventssessions.readServer-sent session events/whatsapp-sessionssessions.manageCreate a session/whatsapp-sessions/:sessionIdsessions.manageUpdate session name or phone hint/whatsapp-sessions/:sessionIdsessions.manageDelete an unlinked or logged-out session/whatsapp-sessions/:sessionId/logssessions.readRead connection event history/whatsapp-sessions/:sessionId/connectsessions.manageStart or resume pairing/whatsapp-sessions/:sessionId/restartsessions.manageRestart runtime without logout/whatsapp-sessions/:sessionId/disconnectsessions.manageLog out linked WhatsAppSession states
need_scan
A QR is available or pairing is required. Read /qr and display it to the authorized user.
reconnecting
The worker is trying to restore the current authenticated session. Avoid forcing logout.
logged_out
The linked WhatsApp account is no longer authenticated and needs a new QR pairing.
Send WhatsApp messages with the REST API
Send text or media using the server-side send-message endpoint. Outbound requests dispatch immediately; a successful send only confirms transport acceptance, not recipient delivery.
/whatsapp-sessions/:sessionId/messagesmessages.readList recent messages/whatsapp-sessions/:sessionId/messages/:messageIdmessages.readRead message state/send-messagemessages.sendSend text/send-imagemessages.sendSend image/send-videomessages.sendSend video/send-audiomessages.sendSend audio / voice note/send-documentmessages.sendSend document/send-replymessages.sendReply to a message/send-reactionmessages.sendSend reaction/send-locationmessages.sendSend location/send-contactmessages.sendSend vCard contact/send-pollmessages.sendSend pollText payload
{
"to": "E164_RECIPIENT_NUMBER",
"text": "Your order is ready.",
"clientMessageId": "order-1001"
}torequiredE.164 international number, digits only, or another supported WhatsApp recipient identifier. Length 7–32.
textrequiredMessage body from 1 to 4096 characters.
clientMessageIdYour idempotency key, up to 120 characters. Reuse prevents accidental duplicate creation.
Media & actions
Media and interaction endpoints use the same recipient and idempotency validation as text messages.
Media payload
{
"to": "E164_RECIPIENT_NUMBER",
"url": "https://cdn.example.com/invoice.pdf",
"mimeType": "application/pdf",
"mediaSizeBytes": 245820,
"fileName": "invoice.pdf",
"caption": "Your invoice",
"clientMessageId": "invoice-1001"
}Images
16 MBImage MIME types.
Video
64 MBVideo MIME types.
Audio
16 MBSet voiceNote: true only for audio.
Documents
100 MBDocument and supported media MIME types.
Downloaded bytes must match mediaSizeBytes and stay within the file limit. The worker checks the supplied content type, sends media from memory, then clears temporary buffers after every attempt. RelayWA does not retain an outbound file copy or delete files from your source hosting. These checks do not include malware scanning.
Action payload requirements
replyRequires quotedMessageId and message text.
reactionRequires targetMessageId and an emoji.
locationRequires valid latitude/longitude; name and address are optional.
contactRequires display name and vCard content.
poll2–12 options with selectableCount between 1 and 12.
Contacts, chats & groups
Directory endpoints expose synchronized WhatsApp context for the selected session.
/whatsapp-sessions/:sessionId/contactscontacts.readRead synchronized contacts/whatsapp-sessions/:sessionId/chatschats.readRead synchronized chats/whatsapp-sessions/:sessionId/groupsgroups.readRead synchronized groupsA session-bound key can only read directory data for its bound session. Organization keys still need the matching read scope.
Immediate sending & application jobs
RelayWA sends each request directly to the connected WhatsApp runtime. There is no outbound Redis queue, randomized gap, or automatic retry.
Immediate result
A successful send response includes the message record after transport acceptance. Recipient delivery and read receipts arrive later through status updates and webhooks.
Your application's jobs
Use Laravel Jobs or your own job system for scheduling, throttling, backoff, and retries. RelayWA rejects scheduledAt, priority, and maxAttempts.
Use clientMessageId for idempotency. If a request times out, check its message ID or repeat the same idempotency key to inspect the existing result before deciding to send again. A timeout can have an unknown delivery outcome.
WhatsApp API webhooks
Receive signed HTTPS POST callbacks for your QR-linked session events. Verify the webhook signature against the raw request body before processing updates.
/webhookswebhooks.readList webhook endpoints/webhooks/:endpointId/deliverieswebhooks.readInspect last 100 deliveries/webhookswebhooks.manageCreate webhook endpoint/webhooks/:endpointIdwebhooks.manageUpdate endpoint/webhooks/:endpointIdwebhooks.manageDelete endpointDelivery body
{
"id": "EVENT_ID",
"type": "message.received",
"sessionId": "SESSION_ID",
"createdAt": "2026-10-07T12:00:00.000Z",
"data": {}
}Signature headers
x-relaywa-event: message.received
x-relaywa-event-id: EVENT_ID
x-relaywa-timestamp: 1791378000
x-relaywa-signature: sha256=...Verify HMAC-SHA256
import crypto from "node:crypto";
const expected = "sha256=" + crypto
.createHmac("sha256", secret)
.update(timestamp + "." + rawBody)
.digest("hex");Verify the exact raw request bytes before JSON parsing. RelayWA signs timestamp + "." + rawBody.
Webhook URLs must use HTTPS. RelayWA blocks localhost and private-network IP ranges.
Pacing belongs in your application
Configure the sending cadence, scheduling, retry policy, and duplicate-business-event handling in your own application.
RelayWA still authenticates requests, enforces scopes and subscription quotas, validates recipients and media, and requires a connected session. It does not add a platform sending delay.
Errors & message status
Use HTTP status codes for request-level failures and message status for asynchronous delivery state.
400 Bad Request
Payload validation or media policy failed.
401 Unauthorized
Missing, invalid, expired or revoked credential.
403 Forbidden
Missing scope, wrong session binding or insufficient dashboard permission.
404 Not Found
Resource does not exist inside the authenticated organization.
409 Conflict
Example: immediate send attempted while the session is not connected.
{
"statusCode": 409,
"message": "WhatsApp session is not connected",
"error": "Conflict"
}Baileys vs Chromium
RelayWA supports two WhatsApp Web transports with different resource and compatibility profiles.
Baileys
Lightweight / scalableWebSocket protocol client with lower RAM usage and better session density.
Chromium
Browser-based compatibilityPersistent WhatsApp Web browser profile through Chromium/Puppeteer with higher CPU/RAM usage.
Changing the platform default affects new sessions. Connected sessions keep their active engine; a requested per-session change is deferred rather than forcing logout or immediate QR pairing.
Troubleshooting
Start with the session state, then inspect worker health and only log out when re-pairing is actually required.
Check the session state and worker lease. If state is need_scan, read the latest QR again. Avoid repeatedly creating new sessions.
Read the message status and failure reason. Check session connectivity, worker ownership, and the private dispatch endpoint. Retry policy belongs in your application.
Inspect diagnostics and network stability. Restart can be tried without logout. Logout should be reserved for intentionally re-pairing the WhatsApp account.
Confirm public HTTPS reachability, verify your endpoint returns 2xx quickly, validate HMAC using the raw body and inspect recent delivery attempts.
Check the key type, scopes and session binding. Dashboard-only management operations are not available to ordinary API keys.
For a step-by-step, read-only diagnosis of QR availability and all nine session states, see our QR session troubleshooting tutorial. The session endpoints above remain the canonical API reference.
Production checklist
Use this before putting a customer integration into production.
Store API keys only in server-side secret/environment storage.
Use a unique clientMessageId for business-critical sends.
Verify webhook signatures and keep the raw request body.
Check failed messages and distinguish transport acceptance from recipient delivery.
Monitor session disconnected/logged_out/reconnecting state.
Implement pacing and bounded retry jobs in your application.
Use HTTPS for webhook/media URLs and avoid private-network targets.
Test failure handling before sending production traffic.
WhatsApp API integration examples for Node.js, Laravel, Python and .NET
These WhatsApp API examples send a message from your application server using POST /api/send-message and a scoped Bearer session key. Use the public API endpoint https://relaywa.com/api; never put credentials into browser JavaScript or distributable apps. RelayWA uses QR-linked WhatsApp Web sessions, not Meta's official WhatsApp Cloud API.
YOUR NEXT INTEGRATION STARTS HERE
A few lines of code.
A new connection.
Bring WhatsApp into your application with a straightforward HTTP request.
Link your number
Scan the QR from WhatsApp’s Linked devices.
Copy your session key
Keep credentials scoped to a single number.
Send your first request
Use your language. Receive replies through webhooks.
const response = await fetch( "https://relaywa.com/api/send-message", { method: "POST", headers: { Authorization: "Bearer YOUR_SESSION_KEY", "Content-Type": "application/json" }, body: JSON.stringify({ to: "12025550123", text: "Your order is on its way." }) });if (!response.ok) throw new Error('HTTP ' + response.status);const result = await response.json();Node.js / JavaScript integration guide
Use built-in fetch on a modern Node.js server to send through a session-bound Bearer key.
Setup: Node.js with fetch support; store RELAYWA_SESSION_KEY in server-side environment variables. Never expose it in browser JavaScript.
const response = await fetch(
"https://relaywa.com/api/send-message",
{
method: "POST",
headers: {
Authorization: "Bearer YOUR_SESSION_KEY",
"Content-Type": "application/json"
},
body: JSON.stringify({
to: "12025550123",
text: "Your order is on its way."
})
}
);
if (!response.ok) throw new Error('HTTP ' + response.status);
const result = await response.json();Receiving events: Handle signed HTTPS webhook callbacks using the exact raw request body; verify HMAC before parsing JSON.
Laravel / PHP integration guide
Send from a Laravel service with Http::withToken, and use Laravel jobs when your business needs scheduled sending or retry backoff.
Setup: Set RELAYWA_SESSION_KEY in server-side secrets and services.relaywa.url to the public RelayWA API base (for example https://relaywa.com/api).
$response = Http::withToken(env('RELAYWA_SESSION_KEY'))
->post(config('services.relaywa.url') .
'/send-message', [
'to' => '12025550123',
'text' => 'Your order is on its way.',
]);
$response->throw();Receiving events: Verify the HMAC against the raw request content, then dispatch processing to your Laravel application queue.
Python integration guide
Call the HTTPS REST endpoint from a Python worker using requests with JSON and a scoped session Bearer key.
Setup: Install requests, use an absolute https://.../api URL, handle raise_for_status and store the credential outside source control.
import requests
response = requests.post(
"https://relaywa.com/api/send-message",
headers={"Authorization": "Bearer YOUR_SESSION_KEY"},
json={
"to": "12025550123",
"text": "Your order is on its way."
}
)
response.raise_for_status()
result = response.json()Receiving events: Verify signed event requests using the raw body in your FastAPI/Flask handler before interpreting event data.
C# / .NET integration guide
Send typed JSON requests with HttpClient from a .NET backend and inspect response status before processing results.
Setup: Configure a reusable HttpClient, a server-side session key and the public HTTPS API base; include System.Net.Http.Json where needed.
using var client = new HttpClient();
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", sessionKey);
var response = await client.PostAsJsonAsync(
$"{apiBase}/send-message",
new { to = "12025550123", text = "Your order is ready." }
);
response.EnsureSuccessStatusCode();Receiving events: Validate webhook HMAC using the raw request bytes in ASP.NET Core before processing inbound events.
n8n HTTP Request integration guide
Use the general n8n HTTP Request node to call RelayWA; this is a manual setup recipe, not an official packaged n8n connector.
Setup: Create an HTTP Request step, use a scoped Bearer credential stored in n8n Credentials, and set an absolute https://.../api/send-message URL.
HTTP Request node
Method: POST
URL: {{$env.RELAYWA_API_BASE}}/send-message
Authentication: Header Auth credential
Header: Authorization
Value: Bearer YOUR_SESSION_KEY
Body content type: JSON
Body: {"to":"12025550123","text":"Hello from RelayWA"}Receiving events: Use an n8n Webhook workflow only with appropriate raw-body HMAC verification; otherwise verify via a small trusted middleware service.
Connect a number and send your first API message.
Start with the trial, create a scoped key and use the examples above.