What the API does
Wamafy's public REST API lets your own systems (a CRM, an ecommerce backend, a helpdesk, Zapier/Make) drive WhatsApp programmatically - send messages, sync contacts, receive inbound messages via webhooks, run broadcasts, manage templates, and read analytics.
- Base URL:
https://api.wamafy.com/api/v1/public - Format: JSON in, JSON out. Every response is
{ "success": boolean, "data"?: ..., "error"?: { "message", "code" } }. - Two-way: send with the endpoints below; receive inbound customer messages by registering a webhook (see the last section).
Authentication
Create an API key at Settings → API Access → Create key. The full key (wamafy_live_…) is shown once - store it somewhere safe. Send it on every request:
Authorization: Bearer wamafy_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
A revoked key, or a key for a suspended workspace, returns 401. You can hold up to 25 active keys and revoke any of them at any time.
Scopes (least privilege)
Each key carries a set of scopes. When you create a key, either tick Full access (covers everything, current + future) or choose only the scopes that integration needs. Calling an endpoint your key isn't scoped for returns 403 with code API_SCOPE_MISSING.
| Scope | Grants |
|---|---|
templates:read | List templates + their variables |
templates:write | Create + submit templates for review |
messages:send | Send templates + session replies; window check |
messages:read | Read a contact's message history |
contacts:read | List + fetch contacts |
contacts:write | Create / update / opt-in / opt-out contacts |
media:write | Upload media to reuse in sends |
campaigns:send | Trigger a broadcast to a segment |
analytics:read | Read message + delivery stats |
flows:trigger | Start a chatbot flow for a contact |
Keys created before scopes existed default to full access.
Quickstart - list templates + send one
Only approved templates can be sent to someone who hasn't messaged you yet (that's Meta's rule). List yours:
curl https://api.wamafy.com/api/v1/public/templates \ -H "Authorization: Bearer wamafy_live_xxx"
Then send one - map your data onto each variable key:
curl -X POST https://api.wamafy.com/api/v1/public/messages \
-H "Authorization: Bearer wamafy_live_xxx" \
-H "Content-Type: application/json" \
-d '{ "to": "+919876543210", "templateName": "order_update", "variables": { "1": "Priya", "2": "#1234" } }'
The response returns { messageId, to, from, status }. Use ?number= on the list, or "from" on the send, to pick a specific WhatsApp number.
Sending messages
Templates (cold start) - POST /messages
Body: to, templateName, optional variables, from, language, and headerMediaUrl for media-header templates. This is the reliable way to reach someone at any time.
Session replies (inside the 24h window)
After a customer messages you, a 24-hour window opens where you can reply with free-form content. Check it first:
GET /window?to=919876543210 → { "windowOpen": true, "expiresAt": "..." }
POST /messages/text-{ to, text }POST /messages/interactive- replybuttons(up to 3) or alistmenu; the customer's tap returns on the inbound webhookPOST /messages/media- image/doc/video/audio (≤ 16 MB) from amediaHandleor a publiclink
If there's no open conversation you get 400 NO_OPEN_CONVERSATION - send a template instead. Session sends count against your monthly message limit and report delivery status like template sends.
Contacts (CRM sync)
Phone is the natural key. Read needs contacts:read, writes need contacts:write.
GET /contacts- filter (search,status,consentStatus,tag) + paginateGET /contacts/:phone- fetch onePOST /contacts- upsert by phone: updates if it exists, else creates; the response includes"created": true|falsePATCH /contacts/:phone- partial update (name / email / tags / status / consent)POST /contacts/:phone/opt-outand/opt-in- consent shortcutsGET /contacts/:phone/messages- message history, newest first, cursor-paginated withbefore/nextBefore(messages:read)
curl -X POST https://api.wamafy.com/api/v1/public/contacts \
-H "Authorization: Bearer wamafy_live_xxx" -H "Content-Type: application/json" \
-d '{ "phone": "+919876543210", "name": "Priya", "tags": ["vip"] }'
Media, campaigns, templates, analytics, flows
Media - POST /media (media:write)
Upload a file (multipart/form-data, field file) and get a mediaHandle to reuse in media sends. Or skip it and pass a public link straight to POST /messages/media.
Campaigns - POST /campaigns (campaigns:send)
Broadcast an approved template to a tag segment (or every active contact if tags is omitted). Creates + starts it immediately; opted-out contacts are skipped. Body: templateName, tags, variables, name.
Templates - POST /templates (templates:write)
Create + submit a template to Meta for review (same spec as the dashboard builder). Poll GET /templates?status=all to watch it move from PENDING to APPROVED, then it's sendable.
Analytics - GET /analytics (analytics:read)
Message totals (sent / delivered / read / failed / readRate) + a per-day breakdown over from/to (default last 30 days), in your workspace timezone.
Flows - POST /contacts/:phone/trigger-flow (flows:trigger)
Start a chatbot flow for a contact by flowId, with optional variables that land in the run context. Returns started or skipped.
Webhooks - receiving events
To receive (not just send), register a webhook at Settings → API Access → Webhooks. Pick the events you want; you'll get a signing secret (shown once). Wamafy POSTs the event JSON to your URL.
message.inbound
Fires whenever a customer messages one of your numbers - text, button/list tap (interactiveReplyId), media (mediaId), and the Click-to-WhatsApp ad referral on the first message. Pair it with the session-send endpoints for a full two-way integration.
POST <your url>
X-Wamafy-Event: message.inbound
X-Wamafy-Signature: sha256=<hmac>
{ "event": "message.inbound", "occurredAt": "...",
"data": { "messageId": "wamid...", "from": "919876543210", "type": "text",
"text": "Is this in stock?", "conversationId": "...", "leadId": "..." } }
Verify the signature
X-Wamafy-Signature is sha256= + HMAC-SHA256 of the raw request body, keyed with your webhook's signing secret. Recompute it and compare - reject the request if it doesn't match. There's also a separate status webhook that pushes delivery/read/failed receipts for messages you sent.
Respond 2xx quickly. Endpoints that keep failing auto-disable; re-enable from the dashboard. Use Send test to check reachability.
Errors, limits + the request log
Errors come back as { "success": false, "error": { "message", "code" } } with an HTTP status. Common codes:
| Status | Meaning |
|---|---|
401 | Missing / invalid / revoked key |
403 | Key missing the required scope, or monthly API cap reached |
404 | Template / contact / flow / number not found |
400 | Bad request - e.g. NO_OPEN_CONVERSATION, template not approved |
422 | Validation failed (bad body shape) |
429 | Rate limit - 60 requests/min per key |
- Rate limit: 60 requests/minute per key, plus your plan's monthly message + campaign caps.
- Request log: every call is recorded at Settings → API Access → Request log - endpoint, status, outcome, and the error reason on failures. Your first stop for debugging.