The WhatsApp API, documented.
REST to send and read, signed webhooks to receive, and an MCP server ready for your agent. Integrate WhatsApp into your product with line_id and API keys.
From zero to your first message in 3 steps.
1. Connect a line
In the console, scan the QR code with the WhatsApp of the number you want to use. You get a line_id.
2. Create an API key
In the console, generate an API key. It starts with wzb_live_ and is shown only once.
3. Call the API
Use your line_id and your API key to send the first message (example below).
curl -X POST https://www.wazion.com/api/bridge/v1/messages/send \
-H "Authorization: Bearer TU_API_KEY" \
-H "Content-Type: application/json" \
-d '{"line_id":"TU_LINE_ID","to":"+34600000000","message":"Hello from WAzion Bridge"}'
The console is where you manage lines, API keys, and webhooks. The documentation describes the same API that you can test there.
API base and authentication.
| REST base | https://www.wazion.com/api/bridge/v1 |
|---|---|
| MCP server | https://www.wazion.com/api/bridge/mcp |
| Header | Authorization: Bearer wzb_live_... |
All requests are authenticated with an API key in the Authorization header (it is also accepted X-Bridge-Key). API keys are created in the console and start with wzb_live_. Each response includes a header X-Request-Id tool for support and debugging. The API supports CORS (Access-Control-Allow-Origin: *).
Authorization: Bearer wzb_live_xxxxxxxxxxxxxxxxxxxxxxxx Content-Type: application/json
How Bridge is organized.
line_id
Identify a WhatsApp line connected by QR. Every send or read request uses a line_id.
API key
Credential wzb_live_ that authenticates your application. You can create, rotate, and revoke several in the console.
Webhook
You receive incoming events (messages, states) signed with HMAC at the URL you configure.
You always work with line_id and API keys. Bridge hides the internal WhatsApp session: you never expose or use the session_key.
REST endpoints.
| Action | Endpoint |
|---|---|
| Account, plan, and limits | GET /v1/me |
| Period consumption | GET /v1/usage |
| List lines | GET /v1/lines |
| Detail of a line | GET /v1/lines/{line_id} |
| Send text | POST /v1/messages/send |
| Send media | POST /v1/messages/send-media |
| Respond by quoting | POST /v1/messages/reply |
| React with emoji | POST /v1/messages/react |
| Mark as read | POST /v1/messages/read |
| Archive chat | POST /v1/chats/archive |
| List chats | GET /v1/chats |
| Read messages | GET /v1/messages |
| List / create webhooks | GET /v1/webhooks · POST /v1/webhooks |
| Test a webhook | POST /v1/webhooks/{id}/test |
POST /v1/messages/send
| Parameter | Type | Description | |
|---|---|---|---|
line_id | string | mandatory | Line connected from which it is sent. |
to | string | mandatory | Destination phone in international format, e.g. +34600000000. |
message | string | mandatory | Message text. |
archive_policy | enum | optional | never · always · preserve_if_previously_archived |
The shipments (send, send-media, reply) accept the Idempotency-Key header. The GET /v1/chats and GET /v1/messages readings limit the result to 500 and 200 respectively.
Send a message in your language.
curl -X POST https://www.wazion.com/api/bridge/v1/messages/send \
-H "Authorization: Bearer wzb_live_xxx" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: order-1001" \
-d '{"line_id":"line_xxx","to":"+34600000000","message":"Pedido confirmado"}'
// Node 18+ (fetch nativo)
const res = await fetch("https://www.wazion.com/api/bridge/v1/messages/send", {
method: "POST",
headers: {
"Authorization": "Bearer wzb_live_xxx",
"Content-Type": "application/json",
"Idempotency-Key": "order-1001"
},
body: JSON.stringify({
line_id: "line_xxx",
to: "+34600000000",
message: "Pedido confirmado"
})
});
console.log(await res.json());
import requests
r = requests.post(
"https://www.wazion.com/api/bridge/v1/messages/send",
headers={
"Authorization": "Bearer wzb_live_xxx",
"Idempotency-Key": "order-1001",
},
json={
"line_id": "line_xxx",
"to": "+34600000000",
"message": "Pedido confirmado",
},
)
print(r.json())
<?php
$ch = curl_init("https://www.wazion.com/api/bridge/v1/messages/send");
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
"Authorization: Bearer wzb_live_xxx",
"Content-Type: application/json",
"Idempotency-Key: order-1001",
],
CURLOPT_POSTFIELDS => json_encode([
"line_id" => "line_xxx",
"to" => "+34600000000",
"message" => "Pedido confirmado",
]),
]);
echo curl_exec($ch);
Send image, video, audio, or document.
Use media_type with one of these values: image, video, audio, document. The media_url field must be a public URL that the server can download.
| Parameter | Type | Description | |
|---|---|---|---|
line_id | string | mandatory | Shipping line. |
to | string | mandatory | Destination phone. |
media_url | string (URL) | mandatory | Public URL of the file. |
media_type | enum | optional | image · video · audio · document (by default image) |
caption | string | optional | Text attached to the file. |
curl -X POST https://www.wazion.com/api/bridge/v1/messages/send-media \
-H "Authorization: Bearer wzb_live_xxx" \
-H "Content-Type: application/json" \
-d '{"line_id":"line_xxx","to":"+34600000000","media_url":"https://tu-cdn.com/foto.jpg","media_type":"image","caption":"Hello"}'
Idempotence.
To avoid sending the same message twice (network retries, restarts), send the header. Idempotency-Key with a unique identifier for your operation. If you repeat the same key with the same body, Bridge returns the saved response without resending. The key is remembered for 24 hours and applies to send, send-media, and reply.
-H "Idempotency-Key: pedido-1001"
If you reuse the same key with a different body, you receive 409 idempotency_conflict.
Receive signed events.
Create a webhook with a public HTTPS URL. Bridge will send a POST to that URL on each event. Each webhook has its own secret. (wzb_whsec_...) that is shown only once when creating it.
Events you can subscribe to (they are delivered as produced by the WhatsApp engine): message.received, message.sent, message.status, line.status.
Each delivery includes these headers:
X-WAzion-Bridge-Event: message.received
X-WAzion-Bridge-Signature: sha256=<hmac>
Content-Type: application/json
{
"event": "message.received",
"line_id": "line_xxx",
"data": { ... }
}
Verify the signature
The signature is an HMAC-SHA256 of the raw body using the webhook secret. Always compare before processing:
// Node.js
import crypto from "node:crypto";
function verify(rawBody, signatureHeader, secret) {
const expected = "sha256=" + crypto
.createHmac("sha256", secret)
.update(rawBody)
.digest("hex");
return crypto.timingSafeEqual(
Buffer.from(signatureHeader),
Buffer.from(expected)
);
}
<?php
// PHP
$raw = file_get_contents("php://input");
$sig = $_SERVER["HTTP_X_WAZION_BRIDGE_SIGNATURE"] ?? "";
$expected = "sha256=" . hash_hmac("sha256", $raw, $secret);
if (!hash_equals($expected, $sig)) { http_response_code(401); exit; }
You can trigger a test event to your webhook from the console or with POST /v1/webhooks/{id}/test.
Error handling.
Authentication, account, and server errors use a standardized envelope:
{
"ok": false,
"error": { "code": "unauthorized", "message": "...", "retryable": false }
}
Validation and business errors use a flat form with the error code:
{ "error": "quota_exceeded", "message": "Monthly quota exceeded", "metric": "sent_messages", "used": 10000, "limit": 10000, "plan": "pro" }
| Code | HTTP | Meaning |
|---|---|---|
unauthorized | 401 | Missing, invalid, or revoked API key. |
subscription_required | 402 | The Bridge subscription is not active. |
validation_error | 400 | Fields are missing or invalid. |
line_not_found | 404 | The line_id does not exist in your account. |
line_not_connected | 409 | The line is not connected to WhatsApp. |
idempotency_conflict | 409 | Same Idempotency-Key with different body. |
quota_exceeded | 429 | You exceeded the quota for the period. |
line_limit_reached | 429 | You have reached the maximum number of lines in the plan. |
server_error | 500 | Internal error; you can try again. |
Plan fees.
- The quotas (sent messages, webhook events, number of lines) are per account and are counted within your billing cycle.
- Upon exceeding a quota, you receive 429 (quota_exceeded or line_limit_reached) with the details of used, limit, and plan.
- GET /v1/chats returns a maximum of 500 chats and GET /v1/messages a maximum of 200 messages per request.
- For the stability of WhatsApp, the deliveries of the same line are spaced a few seconds apart; it distributes mass deliveries over time.
Check your consumption at any time with GET /v1/usage. The specific limits of each plan are in the plans page.
MCP server for your agent.
Bridge exposes an MCP (Model Context Protocol) server so that your agent can send and read WhatsApp without writing REST code. Add it with:
claude mcp add --transport http wazion-bridge \ https://www.wazion.com/api/bridge/mcp \ --header "Authorization: Bearer wzb_live_xxx"
| Tool | Parameters |
|---|---|
bridge_list_lines | — |
bridge_send_message | line_id, to, message, archive_policy? |
bridge_send_media | line_id, to, media_url, media_type?, caption? |
bridge_reply_message | line_id, to, message, quoted_message_id |
bridge_react_message | line_id, jid, message_id, emoji |
bridge_get_chats | line_id, limit?, archived? |
bridge_get_messages | line_id, phone, limit? |
bridge_archive_chat | line_id, phone, archive? |
Ready to integrate.
Connect a line, create your API key, and test each endpoint from the console.