WhatsApp Business
Send text, media, documents, templates, interactive messages, locations and reactions on official and QR lines, with an opt-out guard.
Send WhatsApp messages, manage contacts, campaigns and conversations, post loyalty points from the till and receive events in real time through signed webhooks. 45 endpoints with scoped keys, an OpenAPI spec and a Postman collection.
curl -X POST https://site.watily.com/api/v1/messages \
-H "Authorization: Bearer wtly_live_xxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"to": "966501234567",
"type": "text",
"message": "Hi! Your order #1042 is ready for pickup"
}'
Send text, media, documents, templates, interactive messages, locations and reactions on official and QR lines, with an opt-out guard.
Import up to 500 contacts per call, segments, and create, start, pause and resume campaigns from your system.
Read conversations and messages, assign them to agents, archive, tag, and track connected lines and analytics.
One call from the till enrolls the customer and adds the stamp or points with no duplicates, plus reversals and wallet notifications.
12 events pushed to your URL: incoming messages, delivery status, campaigns, loyalty, line connect and disconnect.
Each key has specific scopes and its own rate limit, can be revoked instantly from the dashboard, and is stored hashed on our side.
Sign up for Watily and connect a WhatsApp number officially or by QR. Registration is free with a trial.
Go to WhatsApp, Integrations, API keys. Pick only the scopes you need. Keys start with wtly_live_ and are shown in full once.
Use the example above or import the Postman collection. Base URL: https://site.watily.com/api/v1
Authentication — either header works:
Authorization: Bearer wtly_live_xxxxxxxx
# or
X-Api-Key: wtly_live_xxxxxxxx
Common errors
| HTTP | Meaning |
|---|---|
| 401 | invalid_api_key — missing or revoked key |
| 403 | insufficient_scope / subscription_expired / quota_exceeded |
| 402 | wallet_insufficient — official-line wallet cannot cover the send |
| 409 | recipient_opted_out |
| 422 | invalid_phone — 10 to 15 digits |
| 429 | rate limit exceeded |
Limits per key: 120 requests/min for reads and 60/min for writes and sends, which may vary by plan. Phone numbers are normalised automatically, so 0501234567 becomes 966501234567.
Each route lists the scope your key needs. Full field and response details are in the interactive reference.
| Method | Path | Scope | Description |
|---|---|---|---|
| POST | /messages | messages:send | Send a WhatsApp message (text, media, template, interactive, location) |
| POST | /media | messages:send | Upload media for a later message |
| GET | /templates | messages:send | List approved templates |
| Method | Path | Scope | Description |
|---|---|---|---|
| GET | /contacts | contacts:read | List contacts |
| GET | /contacts/{phone} | contacts:read | Get a contact by phone |
| GET | /contacts/{phone}/exists | contacts:read | Does the number have WhatsApp (QR lines) |
| POST | /contacts | contacts:write | Create a contact |
| PATCH | /contacts/{phone} | contacts:write | Update a contact |
| POST | /contacts/import | contacts:write | Bulk import up to 500 per call |
| POST | /contacts/{phone}/unsubscribe | contacts:write | Unsubscribe from marketing |
| POST | /contacts/{phone}/resubscribe | contacts:write | Resubscribe |
| GET | /segments | contacts:read | List segments |
| POST | /segments | contacts:write | Create a segment |
| Method | Path | Scope | Description |
|---|---|---|---|
| POST | /campaigns | campaigns:write | Create a draft campaign |
| GET | /campaigns/{campaign} | campaigns:read | Get a campaign |
| POST | /campaigns/{campaign}/start | campaigns:write | Start |
| POST | /campaigns/{campaign}/pause | campaigns:write | Pause |
| POST | /campaigns/{campaign}/resume | campaigns:write | Resume |
| Method | Path | Scope | Description |
|---|---|---|---|
| GET | /conversations | conversations:read | List inbox conversations |
| GET | /conversations/{id} | conversations:read | Get a conversation |
| GET | /conversations/{id}/messages | conversations:read | List its messages |
| POST | /conversations/{id}/assign | conversations:write | Assign or unassign |
| POST | /conversations/{id}/tags | conversations:write | Replace tags |
| POST | /conversations/{id}/archive | conversations:write | Archive (and unarchive) |
| POST | /conversations/{id}/read | conversations:write | Mark as read |
| GET | /instances | conversations:read | Connected WhatsApp lines |
| GET | /analytics/summary | conversations:read | Usage summary for a range (last 30 days by default) |
| Method | Path | Scope | Description |
|---|---|---|---|
| POST | /loyalty/stamps | loyalty:write | POS flow: enroll + card + stamp in one call |
| POST | /loyalty/stamps/reverse | loyalty:write | Reverse a stamp by idempotency_key |
| POST | /loyalty/points | loyalty:write | POS flow: enroll + award points in one call |
| POST | /loyalty/points/reverse | loyalty:write | Reverse a points award |
| POST | /loyalty/members | loyalty:write | Enroll a member |
| GET | /loyalty/programs | loyalty:read | Active programs |
| GET | /loyalty/members/{phone} | loyalty:read | Get a member |
| GET | /loyalty/members/{phone}/points | loyalty:read | Quick balance and level check |
| GET | /loyalty/members/{phone}/transactions | loyalty:read | Transaction history |
| GET | /loyalty/members/{phone}/wallet-card | loyalty:read | Apple / Google Wallet card links |
| GET | /loyalty/cards/{cardNumber} | loyalty:read | Look up by printed or scanned card number |
| POST | /loyalty/notifications | loyalty:notify | Wallet lock-screen notification |
| GET | /loyalty/notifications | loyalty:read | Notification delivery history |
| Method | Path | Scope | Description |
|---|---|---|---|
| GET | /webhooks | webhooks:manage | List endpoints |
| POST | /webhooks | webhooks:manage | Register an endpoint (secret returned once) |
| PATCH | /webhooks/{id} | webhooks:manage | Update |
| DELETE | /webhooks/{id} | webhooks:manage | Delete |
| POST | /webhooks/{id}/test | webhooks:manage | Send a test delivery |
Note: the Automation Journeys API was removed and its old route answers 410. Use the Growth Engine abandoned-cart automation instead.
| Scope | Allows |
|---|---|
| messages:send | Send messages, upload media, read templates |
| contacts:read | Read contacts and segments |
| contacts:write | Create and edit contacts and segments |
| campaigns:read | Read campaigns |
| campaigns:write | Create and control campaigns |
| conversations:read | Read conversations, lines and analytics |
| conversations:write | Assign, archive and tag conversations |
| loyalty:read | Read loyalty data |
| loyalty:write | Record and reverse stamps and points |
| loyalty:notify | Wallet notifications (separate scope on purpose) |
| webhooks:manage | Manage webhooks |
Example: a POS system needs loyalty:write and loyalty:read only, so if its key leaks it cannot send messages or read conversations.
One request enrolls the customer, issues the card and adds the stamp. Re-sending the same idempotency_key never doubles it.
curl -X POST https://site.watily.com/api/v1/loyalty/stamps \
-H "X-Api-Key: wtly_live_xxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"phone": "966501234567",
"idempotency_key": "pos-order-882",
"amount": 45.5,
"branch": "Main branch"
}'
The URL must be public HTTPS. Keep the secret returned in the response; it is shown once. Omit events to subscribe to all.
curl -X POST https://site.watily.com/api/v1/webhooks \
-H "Authorization: Bearer wtly_live_xxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/watily-hook",
"events": ["message.incoming", "message.status"]
}'
Each delivery carries the headers X-Watily-Event X-Watily-Delivery X-Watily-Signature. The signature is sha256=HMAC_SHA256(raw_body, secret).
<?php
$raw = file_get_contents('php://input');
$sig = $_SERVER['HTTP_X_WATILY_SIGNATURE'] ?? '';
$calc = 'sha256=' . hash_hmac('sha256', $raw, $endpointSecret);
if (!hash_equals($calc, $sig)) {
http_response_code(401);
exit;
}
$event = json_decode($raw, true); // $event['event'], $event['data']
http_response_code(200);const crypto = require('crypto');
app.post('/watily-hook', express.raw({ type: '*/*' }), (req, res) => {
const calc = 'sha256=' + crypto
.createHmac('sha256', ENDPOINT_SECRET)
.update(req.body).digest('hex');
const sig = req.get('X-Watily-Signature') || '';
if (sig.length !== calc.length ||
!crypto.timingSafeEqual(Buffer.from(calc), Buffer.from(sig))) {
return res.sendStatus(401);
}
const evt = JSON.parse(req.body); // evt.event, evt.data
res.sendStatus(200);
});{
"event": "message.incoming",
"timestamp": "2026-08-14T10:00:00Z",
"company_id": 123,
"data": { "phone": "9665xxxxxxx", "message_text": "Hello" }
}Compute the signature on the raw body before any JSON parsing, compare it with a constant-time function and answer 200 quickly.
In the Watily dashboard go to WhatsApp, then Integrations, then API keys. Create a key and tick only the scopes your integration needs. The full key is shown once at creation, so store it safely; you can revoke it at any time.
The base URL is https://site.watily.com/api/v1. Send the key as an Authorization Bearer header or in an X-Api-Key header; both behave the same.
Yes. Each key gets 120 requests per minute for reads and 60 per minute for writes and sends, which can vary by plan. Going over returns HTTP 429; retry shortly after.
Yes, through webhooks: register an HTTPS URL and choose events (incoming message, message status, campaign completed, unsubscribes, loyalty events, line connected or disconnected). Every delivery is signed with an X-Watily-Signature header you verify with HMAC SHA-256.
Yes. A one-call flow for tills and ERPs enrolls the customer and adds the stamp or points, an idempotency_key prevents double-posting, and there are endpoints to reverse, check balances, fetch wallet-card links and push lock-screen notifications.
Yes. The full interactive reference, an OpenAPI 3 spec and a ready Postman collection are available from the reference page and stay in sync with every API change.
The same request works on official lines (Meta Cloud API) and QR lines. Template messages, wallet balance and the 24-hour window rules apply to official lines as Meta defines them, and usage beyond the plan quota is billed at Meta’s direct rate.
Create your account, grab your key, and message us on WhatsApp if you need help wiring up your system.