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" \
-H "Idempotency-Key: TU_OPERACION_UNICA" \
-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 |
| Check the status of a shipment | GET /v1/messages/status |
| 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 send, send-media, and reply requests require 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" \
-H "Idempotency-Key: media-1001" \
-d '{"line_id":"line_xxx","to":"+34600000000","media_url":"https://tu-cdn.com/foto.jpg","media_type":"image","caption":"Hello"}'
Idempotence.
The send, send-media, and reply shipments require the header. Idempotency-Key with a unique identifier for your operation. During an occurrence, repeating the same key with the same body returns the saved state without resending or consuming quota again. A completed key can initiate a new occurrence when its 24-hour retention expires; network retries of the active occurrence always retain their same internal identity.
-H "Idempotency-Key: pedido-1001"
The responses queued, processing, and sent_unconfirmed include status_url to query GET /v1/messages/status. Only sent with message_id confirms the sending; sent_unconfirmed requires reconciliation or manual review, and Bridge never resends it or automatically returns its quota. If you reuse the same active key with a different body, you receive 409 idempotency_conflict.
GET https://www.wazion.com/api/bridge/v1/messages/status?endpoint=messages%2Fsend&idempotency_key=pedido-1001
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: message.received, message.sent, message.status, line.connected, line.disconnected. line.status it is a subscription alias that includes both line events.
The event field of the body and the header X-WAzion-Bridge-Event always use the normalized name. event_id and X-WAzion-Bridge-Event-Id retain the same identity across all retries.
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_key_required | 428 | Missing Idempotency-Key in a durable shipment. |
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"
The initialize response delivers Mcp-Session-Id. The client must return that header: along with the JSON-RPC id and the arguments, it reliably identifies the MCP submissions. If the client does not maintain sessions, it must explicitly provide idempotency_key in send, send-media, and reply.
| 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.