Developers
API & webhooks
Send WhatsApp messages and sync contacts from your own systems, and get a signed notification the moment something happens in WXR. JSON over HTTPS. Included from the Growth plan.
https://wxr.in/api/v1Authentication
Create a key in Settings → API keys and send it as a bearer token. Keys start with wxr_live_, belong to one workspace, and can be revoked at any time.
curl https://wxr.in/api/v1/me \
-H "Authorization: Bearer wxr_live_…"GET /me returns the workspace the key belongs to, which makes it a good connection test.
Errors
Errors use standard HTTP status codes and always have the same body:
{ "error": { "message": "Template \"order_update\" (en) isn't approved." } }401 missing or revoked key · 402 subscription inactive · 403 plan doesn't include the feature or a limit was reached · 404 not found · 422 invalid input.
Messages
POST/messages
Queues a WhatsApp message and returns 202 with its id. Send either a template (works any time) or text (only within 24 hours of the customer's last message, as WhatsApp requires). Optional from picks which of your numbers sends it.
curl -X POST https://wxr.in/api/v1/messages \
-H "Authorization: Bearer wxr_live_…" -H "Content-Type: application/json" \
-d '{
"to": "+971501234567",
"template": { "name": "order_update", "language": "en", "params": ["Omar", "#4821", "Friday"] }
}'{ "id": "3f0c…", "status": "queued", "conversationId": "a91e…" }Marketing templates are not sent to contacts who opted out (they replied STOP). Utility templates still are.
GET/messages/{id}
Delivery status: queued, sent, delivered, read or failed (with error), plus timestamps. Prefer the message.status webhook to polling.
GET/messages/recent?direction=inbound&limit=20
The latest messages, newest first, in the same shape as webhook events (max 50).
Contacts
POST/contacts
Creates a contact, or updates the one with the same number (201 created, 200 updated). Name and email change only when you send them; tags are added, never removed; optedIn changes only when present. Adding a tag can start a drip sequence.
{ "phone": "+919876543210", "name": "Ananya Rao", "email": "ananya@example.com", "tags": ["vip"], "optedIn": true }GET/contacts?tag=vip&phone=+919876543210&page=1
Lists contacts, newest first, 100 per page: { "data": [...], "page": 1, "total": 1840 }. Filter by tag or find one by phone.
Products
Your catalog, the same one your team, chatbot and AI agent share in chat. Keep it in sync with your store from Zapier, Make or your own code.
POST/products
Creates a product, or updates the one with the same sku (201 created, 200 updated). For a new product, name and price are required; on updates send only what changed. Currencies: INR, USD, EUR, AED.
{ "sku": "KURTA-INDIGO-M", "name": "Indigo block-print kurta", "price": "1499", "currency": "INR",
"category": "Kurtas", "description": "Hand block-printed cotton.", "url": "https://yourshop.com/kurta",
"inStock": true, "active": true }GET/products?sku=KURTA-INDIGO-M&category=Kurtas
Lists products (up to 500), optionally filtered by SKU or category.
Orders
Orders placed in chat: from Buy now on a product card, a cart sent from your WhatsApp catalog, or built by your team in the inbox. Payments go to your own Razorpay or Stripe account.
GET/orders?status=confirmed&payment=paid&phone=+919876543210&limit=25
Latest orders first (max 100). status: pending, confirmed, shipped, delivered, cancelled. payment: unpaid, paid, cod (cash on delivery).
{
"id": "…", "number": 1042, "status": "confirmed", "paymentStatus": "paid", "source": "chat",
"currency": "INR", "subtotal": "1499.00", "shipping": "0.00", "total": "1499.00", "note": "",
"items": [{ "sku": "KURTA-INDIGO-M", "name": "Indigo block-print kurta", "unitPrice": "1499.00", "quantity": 1, "productId": "…" }],
"customer": { "name": "Ananya Rao", "phone": "+919876543210", "contactId": "…" },
"payment": { "provider": "razorpay", "reference": "pay_N3x…", "paidAt": "2026-01-01T09:05:00+00:00" },
"createdAt": "2026-01-01T09:00:00+00:00"
}GET/orders/{id or number}
One order, by id or by its number (e.g. 1042).
PATCH/orders/{id or number}
Update fulfilment from your shipping or store system: { "status": "shipped" } messages the customer in WhatsApp (if their 24-hour window is open; send "notify": false to skip). Record a payment taken elsewhere with { "paid": true, "reference": "UPI-123" }.
Webhooks
Add endpoints in Settings → Webhooks (or with the subscription API below) and pick the events you want. We send a POST with a JSON body for each event:
POST /your/endpoint
Content-Type: application/json
X-WXR-Event: message.received
X-WXR-Delivery: 7d1c…
X-WXR-Signature: t=1767225600,v1=5f2b…
{
"id": "evt_8c1e5f0a9b2d4c6e1a3f5b7d",
"type": "message.received",
"created": "2026-01-01T09:01:00+00:00",
"workspaceId": "90841d3d-…",
"data": {
"message": { "id": "…", "conversationId": "…", "direction": "inbound", "type": "text",
"text": "Hi, do you deliver to Dubai?", "media": null, "createdAt": "…" },
"contact": { "id": "…", "phone": "+971501234567", "name": "Omar Khan", "tags": ["vip"], "optedIn": true }
}
}- Answer with any 2xx within 10 seconds. Do slow work after you respond.
- Failed deliveries are retried with backoff for about 10 minutes (6 attempts). The event
idstays the same, so de-duplicate on it. - Answer 410 Gone to unsubscribe. An endpoint that fails 50 deliveries in a row is switched off; turn it back on in Settings.
- Use the Send test event button to get a
pingevent, and the delivery log to see every request and response.
Events
| message.received | A customer sent a WhatsApp message. |
| message.status | A message you sent was sent, delivered, read or failed. |
| contact.created | A contact was added: a first message from a new number, the app, an import via API, or Zapier. |
| contact.updated | A contact's name, email, tags or opt-in status changed. `changes` lists which. |
| conversation.handoff | The AI agent or a chatbot rule handed a chat to your team. Includes `by` and `reason`. |
| conversation.resolved | A teammate resolved a conversation. |
| campaign.completed | A campaign finished sending. Includes delivery, read and reply counts. |
| order.created | A customer tapped Buy now, sent a cart from your WhatsApp catalog, or a teammate created an order. |
| order.paid | An order was paid (Razorpay, Stripe, test checkout, or marked paid by your team). |
| order.updated | An order was confirmed (including cash on delivery), shipped, delivered or cancelled. `changes` lists what changed. |
Verifying signatures
X-WXR-Signature holds a timestamp t and v1, the hex HMAC-SHA256 of <t>.<raw body> using your endpoint's signing secret. Compare in constant time and reject timestamps older than five minutes.
import crypto from "node:crypto";
export function verifyWxr(rawBody, header, secret) {
const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
if (Math.abs(Date.now() / 1000 - Number(parts.t)) > 300) return false;
const expected = crypto.createHmac("sha256", secret).update(`${parts.t}.${rawBody}`).digest("hex");
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1 ?? ""));
}import hashlib, hmac, time
def verify_wxr(raw_body: bytes, header: str, secret: str) -> bool:
parts = dict(p.split("=", 1) for p in header.split(","))
if abs(time.time() - int(parts.get("t", 0))) > 300:
return False
expected = hmac.new(secret.encode(), f"{parts['t']}.".encode() + raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, parts.get("v1", ""))Verify against the raw request body, before your framework parses it.
Webhook subscriptions
Integration platforms can manage their own endpoints with the API key (REST hooks):
POST/webhooks
{ "url": "https://hooks.example.com/abc", "events": ["message.received"] }Returns 201 with the endpoint's id and signing secret.
DELETE/webhooks/{id}
Unsubscribes (204).
GET/webhooks
Lists the workspace's endpoints.
GET/webhooks/sample?event=message.received
Up to three example data objects for an event, from your latest records, for building field mappings.
Zapier, Make, n8n, Pabbly
Zapier: the WXR app adds triggers for every event above, plus Send Template Message, Send Text Message, Create or Update Contact, Create or Update Product, Update Order Status, Find Contact and Find Order. Connect it with an API key.
Make, n8n and Pabbly: use their webhook trigger (Make: Webhooks → Custom webhook; n8n: Webhook node; Pabbly: Webhook trigger) and paste its URL into Settings → Webhooks. To send messages or update contacts, use their HTTP module (n8n: HTTP Request; Make: HTTP → Make a request) with Authorization: Bearer wxr_live_… and the endpoints above. In n8n you can check signatures in a Code node with the snippet from the previous section (allow the built-in crypto module).