This guide walks you through connecting a WhatsApp number and sending and receiving messages with the Chatzuri WhatsApp API. You need two things, both from your dashboard: your team API key and an agent ID. Every request is authenticated with the API key and lives under /api/v1/agents/{agentId}/whatsapp.
Connect your number
Start a session, then poll the status endpoint. While pairing, the response includes a qr (a PNG data URL) — render it and scan it from WhatsApp on your phone (Linked devices → Link a device).
# Start
curl -X POST https://chatzuri.com/api/v1/agents/AGENT_ID/whatsapp/session \
-H "Authorization: Bearer $CHATZURI_API_KEY"
# Poll until connected (render .qr while connected is false)
curl https://chatzuri.com/api/v1/agents/AGENT_ID/whatsapp/session \
-H "Authorization: Bearer $CHATZURI_API_KEY"
# → { "connected": true, "linked": true, "state": "connected",
# "needsScan": false, "phone": "2547...", "qr": null }Later on, the same endpoint tells you whether anything is actually wrong. linked means the number is still paired with WhatsApp; live means we hold a connection right now. A deploy or a brief network problem drops the second and leaves the first alone, and you get state: "reconnecting" with needsScan: false — nothing to do, it comes back on its own. Branch on needsScan, not on "connected is false", or you will send people chasing QR codes they don't need.
Send your first message
Once connected, send a text message. to is an E.164 number or a full JID.
curl -X POST https://chatzuri.com/api/v1/agents/AGENT_ID/whatsapp/messages \
-H "Authorization: Bearer $CHATZURI_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "to": "254700000000", "text": "Your code is 123456" }'
# → { "status": "sent", "messageId": "BAE5F...", "to": "254700000000", ... }Send media
Media is sent by public URL — no upload step. Fill in the field that matches what you want to send; text becomes the caption.
# Image with a caption
-d '{ "to": "254700000000", "imageUrl": "https://example.com/receipt.png", "text": "Your receipt" }'
# A PDF document
-d '{ "to": "254700000000", "documentUrl": "https://example.com/invoice.pdf", "documentFilename": "invoice.pdf" }'Receive messages
Point a webhook at your app. Setting a URL switches the number into pass-through: incoming messages are delivered to you, and the response gives you a signing secret.
curl -X PUT https://chatzuri.com/api/v1/agents/AGENT_ID/whatsapp/webhook \
-H "Authorization: Bearer $CHATZURI_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "url": "https://your-app.com/whatsapp/inbound" }'
# → { "mode": "passthrough", "url": "...", "secret": "whsec_...", ... }Each incoming message is POSTed to your URL:
{
"event": "message.received",
"from": "254700000000",
"message": { "id": "ABC", "type": "text", "text": "hi", "fromMe": false },
"timestamp": "2026-07-19T10:30:00.000Z"
}Verify it's genuinely from Chatzuri with the secret:
import crypto from "crypto";
// Use the RAW body. JSON.stringify(req.body) will NOT reproduce the signed
// bytes — key order and whitespace are part of them.
function verify(rawBody, header, secret) {
const expected =
"sha256=" + crypto.createHmac("sha256", secret).update(rawBody).digest("hex");
const a = Buffer.from(expected);
const b = Buffer.from(header ?? ""); // x-chatzuri-signature
// timingSafeEqual throws on a length mismatch — check lengths first.
return a.length === b.length && crypto.timingSafeEqual(a, b);
}We also send x-chatzuri-signature-v1 in the form t=<unix>,v1=<hex>, where the hash covers "<t>.<rawBody>". Prefer it: the timestamp is part of the hash, so reject anything outside a few minutes and a captured delivery can't be replayed at you forever.
Download inbound media
When someone sends a photo or file, the webhook includes a media.url. Fetch it with your API key to download the decrypted file.
curl -L -H "Authorization: Bearer $CHATZURI_API_KEY" \ "https://chatzuri.com/api/v1/agents/AGENT_ID/whatsapp/media/ABC" \ -o received.jpg
Check a number before sending
Confirm a number is on WhatsApp so you never waste a message.
curl -H "Authorization: Bearer $CHATZURI_API_KEY" \
"https://chatzuri.com/api/v1/agents/AGENT_ID/whatsapp/contacts/254700000000"
# → { "phone": "254700000000", "exists": true, "jid": "254700000000@s.whatsapp.net" }Tips for production
- Always use E.164 numbers (for example
254700000000). - Keep your webhook secret private and verify every delivery.
- Respond
2xxto webhooks quickly; do heavy work asynchronously. - Download inbound media promptly — links are short-lived.
- Give each agent its own number to scale across teams and use cases.
- Treat
503 CHANNEL_RECONNECTINGas retryable and409 CHANNEL_NOT_CONFIGUREDas an alert. The first means the number is still linked and the connection is coming back; the second means someone has to scan a QR, and retrying will never fix it. - Turn on groups, read receipts or delivery receipts via
PATCH /whatsapp/settings— all three ship off.
X-RateLimit-Limit, so you can pace yourself rather than discover the ceiling as a 429.