Skip to content

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.

Base URL
https://wxr.in/api/v1

Authentication

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.

Template
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"] }
  }'
Response
{ "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).

One order
{
  "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 id stays 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 ping event, and the delivery log to see every request and response.

Events

message.receivedA customer sent a WhatsApp message.
message.statusA message you sent was sent, delivered, read or failed.
contact.createdA contact was added: a first message from a new number, the app, an import via API, or Zapier.
contact.updatedA contact's name, email, tags or opt-in status changed. `changes` lists which.
conversation.handoffThe AI agent or a chatbot rule handed a chat to your team. Includes `by` and `reason`.
conversation.resolvedA teammate resolved a conversation.
campaign.completedA campaign finished sending. Includes delivery, read and reply counts.
order.createdA customer tapped Buy now, sent a cart from your WhatsApp catalog, or a teammate created an order.
order.paidAn order was paid (Razorpay, Stripe, test checkout, or marked paid by your team).
order.updatedAn 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.

Node.js
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 ?? ""));
}
Python
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).