Developers
A simple, reliable REST API.
waaraby.com/api/v1
Authentication
Create keys in the dashboard. The key is shown once and stored hashed. Send an Idempotency-Key header with every send request so retries never duplicate a message.
Authorization: Bearer <api_key>
Idempotency-Key: <unique-per-request>
Endpoints
/api/v1/messages/text
Send a text message to a number or a group.
| Parameter | Type | Required | Settings |
|---|---|---|---|
wa_number_id |
integer | Required | Sending number id (from your account) |
to |
string | Required | Recipient in international format or group JID |
text |
string | Required | Message text |
reply_to |
string | Optional | Message id to quote-reply to |
curl -X POST https://waaraby.com/api/v1/messages/text \
-H 'Authorization: Bearer wa_live_xxx' \
-H 'Idempotency-Key: 550e8400-e29b-41d4' \
-H 'Content-Type: application/json' \
-d '{"wa_number_id":1,"to":"value","text":"Hello from the API","reply_to":"value"}'
$response = Http::withHeaders([
'Authorization' => 'Bearer wa_live_xxx',
'Idempotency-Key' => $idempotencyKey,
])->post('https://waaraby.com/api/v1/messages/text', {
"wa_number_id": 1,
"to": "value",
"text": "Hello from the API",
"reply_to": "value"
});
$data = $response->json('data');
const res = await fetch('https://waaraby.com/api/v1/messages/text', {
method: 'POST',
headers: {
'Authorization': 'Bearer wa_live_xxx',
'Idempotency-Key': crypto.randomUUID(),
'Content-Type': 'application/json',
},
body: JSON.stringify({"wa_number_id":1,"to":"value","text":"Hello from the API","reply_to":"value"}),
});
const { data } = await res.json();
import requests, uuid
res = requests.post(
'https://waaraby.com/api/v1/messages/text',
headers={
'Authorization': 'Bearer wa_live_xxx',
'Idempotency-Key': str(uuid.uuid4()),
},
json={
"wa_number_id": 1,
"to": "value",
"text": "Hello from the API",
"reply_to": "value"
},
)
data = res.json()['data']
/api/v1/messages/media
Send an image, video, document or voice note.
| Parameter | Type | Required | Settings |
|---|---|---|---|
wa_number_id |
integer | Required | Sending number id |
to |
string | Required | Recipient |
type |
string | Required | image | video | file | audio |
file_url |
string | Required | File URL (fetched safely) |
caption |
string | Optional | Caption |
curl -X POST https://waaraby.com/api/v1/messages/media \
-H 'Authorization: Bearer wa_live_xxx' \
-H 'Idempotency-Key: 550e8400-e29b-41d4' \
-H 'Content-Type: application/json' \
-d '{"wa_number_id":1,"to":"value","type":"value","file_url":"value","caption":"value"}'
$response = Http::withHeaders([
'Authorization' => 'Bearer wa_live_xxx',
'Idempotency-Key' => $idempotencyKey,
])->post('https://waaraby.com/api/v1/messages/media', {
"wa_number_id": 1,
"to": "value",
"type": "value",
"file_url": "value",
"caption": "value"
});
$data = $response->json('data');
const res = await fetch('https://waaraby.com/api/v1/messages/media', {
method: 'POST',
headers: {
'Authorization': 'Bearer wa_live_xxx',
'Idempotency-Key': crypto.randomUUID(),
'Content-Type': 'application/json',
},
body: JSON.stringify({"wa_number_id":1,"to":"value","type":"value","file_url":"value","caption":"value"}),
});
const { data } = await res.json();
import requests, uuid
res = requests.post(
'https://waaraby.com/api/v1/messages/media',
headers={
'Authorization': 'Bearer wa_live_xxx',
'Idempotency-Key': str(uuid.uuid4()),
},
json={
"wa_number_id": 1,
"to": "value",
"type": "value",
"file_url": "value",
"caption": "value"
},
)
data = res.json()['data']
/api/v1/messages/poll
Send a poll with options.
| Parameter | Type | Required | Settings |
|---|---|---|---|
wa_number_id |
integer | Required | Sending number id |
to |
string | Required | Recipient |
question |
string | Required | Poll question |
options |
string[] | Required | Options |
curl -X POST https://waaraby.com/api/v1/messages/poll \
-H 'Authorization: Bearer wa_live_xxx' \
-H 'Idempotency-Key: 550e8400-e29b-41d4' \
-H 'Content-Type: application/json' \
-d '{"wa_number_id":1,"to":"value","question":"value","options":["Option 1","Option 2"]}'
$response = Http::withHeaders([
'Authorization' => 'Bearer wa_live_xxx',
'Idempotency-Key' => $idempotencyKey,
])->post('https://waaraby.com/api/v1/messages/poll', {
"wa_number_id": 1,
"to": "value",
"question": "value",
"options": [
"Option 1",
"Option 2"
]
});
$data = $response->json('data');
const res = await fetch('https://waaraby.com/api/v1/messages/poll', {
method: 'POST',
headers: {
'Authorization': 'Bearer wa_live_xxx',
'Idempotency-Key': crypto.randomUUID(),
'Content-Type': 'application/json',
},
body: JSON.stringify({"wa_number_id":1,"to":"value","question":"value","options":["Option 1","Option 2"]}),
});
const { data } = await res.json();
import requests, uuid
res = requests.post(
'https://waaraby.com/api/v1/messages/poll',
headers={
'Authorization': 'Bearer wa_live_xxx',
'Idempotency-Key': str(uuid.uuid4()),
},
json={
"wa_number_id": 1,
"to": "value",
"question": "value",
"options": [
"Option 1",
"Option 2"
]
},
)
data = res.json()['data']
/api/v1/messages/location
Send a location.
| Parameter | Type | Required | Settings |
|---|---|---|---|
wa_number_id |
integer | Required | Sending number id |
to |
string | Required | Recipient |
latitude |
number | Required | Latitude |
longitude |
number | Required | Longitude |
curl -X POST https://waaraby.com/api/v1/messages/location \
-H 'Authorization: Bearer wa_live_xxx' \
-H 'Idempotency-Key: 550e8400-e29b-41d4' \
-H 'Content-Type: application/json' \
-d '{"wa_number_id":1,"to":"value","latitude":30.04,"longitude":30.04}'
$response = Http::withHeaders([
'Authorization' => 'Bearer wa_live_xxx',
'Idempotency-Key' => $idempotencyKey,
])->post('https://waaraby.com/api/v1/messages/location', {
"wa_number_id": 1,
"to": "value",
"latitude": 30.04,
"longitude": 30.04
});
$data = $response->json('data');
const res = await fetch('https://waaraby.com/api/v1/messages/location', {
method: 'POST',
headers: {
'Authorization': 'Bearer wa_live_xxx',
'Idempotency-Key': crypto.randomUUID(),
'Content-Type': 'application/json',
},
body: JSON.stringify({"wa_number_id":1,"to":"value","latitude":30.04,"longitude":30.04}),
});
const { data } = await res.json();
import requests, uuid
res = requests.post(
'https://waaraby.com/api/v1/messages/location',
headers={
'Authorization': 'Bearer wa_live_xxx',
'Idempotency-Key': str(uuid.uuid4()),
},
json={
"wa_number_id": 1,
"to": "value",
"latitude": 30.04,
"longitude": 30.04
},
)
data = res.json()['data']
/api/v1/messages/{id}
Message status: queued, sent, delivered, read or failed.
| Parameter | Type | Required | Settings |
|---|---|---|---|
id |
string | Required | Message id |
curl -X POST https://waaraby.com/api/v1/messages/{id} \
-H 'Authorization: Bearer wa_live_xxx' \
-H 'Idempotency-Key: 550e8400-e29b-41d4' \
-H 'Content-Type: application/json' \
-d '{"id":"value"}'
$response = Http::withHeaders([
'Authorization' => 'Bearer wa_live_xxx',
'Idempotency-Key' => $idempotencyKey,
])->post('https://waaraby.com/api/v1/messages/{id}', {
"id": "value"
});
$data = $response->json('data');
const res = await fetch('https://waaraby.com/api/v1/messages/{id}', {
method: 'POST',
headers: {
'Authorization': 'Bearer wa_live_xxx',
'Idempotency-Key': crypto.randomUUID(),
'Content-Type': 'application/json',
},
body: JSON.stringify({"id":"value"}),
});
const { data } = await res.json();
import requests, uuid
res = requests.post(
'https://waaraby.com/api/v1/messages/{id}',
headers={
'Authorization': 'Bearer wa_live_xxx',
'Idempotency-Key': str(uuid.uuid4()),
},
json={
"id": "value"
},
)
data = res.json()['data']
/api/v1/numbers
List your numbers and their status.
curl -X POST https://waaraby.com/api/v1/numbers \
-H 'Authorization: Bearer wa_live_xxx' \
-H 'Idempotency-Key: 550e8400-e29b-41d4' \
-H 'Content-Type: application/json' \
-d '[]'
$response = Http::withHeaders([
'Authorization' => 'Bearer wa_live_xxx',
'Idempotency-Key' => $idempotencyKey,
])->post('https://waaraby.com/api/v1/numbers', []);
$data = $response->json('data');
const res = await fetch('https://waaraby.com/api/v1/numbers', {
method: 'POST',
headers: {
'Authorization': 'Bearer wa_live_xxx',
'Idempotency-Key': crypto.randomUUID(),
'Content-Type': 'application/json',
},
body: JSON.stringify([]),
});
const { data } = await res.json();
import requests, uuid
res = requests.post(
'https://waaraby.com/api/v1/numbers',
headers={
'Authorization': 'Bearer wa_live_xxx',
'Idempotency-Key': str(uuid.uuid4()),
},
json=[],
)
data = res.json()['data']
/api/v1/contacts
List contacts (paginated).
| Parameter | Type | Required | Settings |
|---|---|---|---|
page |
integer | Optional | Page number |
q |
string | Optional | Search by name or number |
curl -X POST https://waaraby.com/api/v1/contacts \
-H 'Authorization: Bearer wa_live_xxx' \
-H 'Idempotency-Key: 550e8400-e29b-41d4' \
-H 'Content-Type: application/json' \
-d '{"page":1,"q":"value"}'
$response = Http::withHeaders([
'Authorization' => 'Bearer wa_live_xxx',
'Idempotency-Key' => $idempotencyKey,
])->post('https://waaraby.com/api/v1/contacts', {
"page": 1,
"q": "value"
});
$data = $response->json('data');
const res = await fetch('https://waaraby.com/api/v1/contacts', {
method: 'POST',
headers: {
'Authorization': 'Bearer wa_live_xxx',
'Idempotency-Key': crypto.randomUUID(),
'Content-Type': 'application/json',
},
body: JSON.stringify({"page":1,"q":"value"}),
});
const { data } = await res.json();
import requests, uuid
res = requests.post(
'https://waaraby.com/api/v1/contacts',
headers={
'Authorization': 'Bearer wa_live_xxx',
'Idempotency-Key': str(uuid.uuid4()),
},
json={
"page": 1,
"q": "value"
},
)
data = res.json()['data']
/api/v1/contacts
Create a contact.
| Parameter | Type | Required | Settings |
|---|---|---|---|
name |
string | Required | Name |
phone |
string | Required | Phone in international format |
tags |
string[] | Optional | Tags |
curl -X POST https://waaraby.com/api/v1/contacts \
-H 'Authorization: Bearer wa_live_xxx' \
-H 'Idempotency-Key: 550e8400-e29b-41d4' \
-H 'Content-Type: application/json' \
-d '{"name":"value","phone":"value","tags":["Option 1","Option 2"]}'
$response = Http::withHeaders([
'Authorization' => 'Bearer wa_live_xxx',
'Idempotency-Key' => $idempotencyKey,
])->post('https://waaraby.com/api/v1/contacts', {
"name": "value",
"phone": "value",
"tags": [
"Option 1",
"Option 2"
]
});
$data = $response->json('data');
const res = await fetch('https://waaraby.com/api/v1/contacts', {
method: 'POST',
headers: {
'Authorization': 'Bearer wa_live_xxx',
'Idempotency-Key': crypto.randomUUID(),
'Content-Type': 'application/json',
},
body: JSON.stringify({"name":"value","phone":"value","tags":["Option 1","Option 2"]}),
});
const { data } = await res.json();
import requests, uuid
res = requests.post(
'https://waaraby.com/api/v1/contacts',
headers={
'Authorization': 'Bearer wa_live_xxx',
'Idempotency-Key': str(uuid.uuid4()),
},
json={
"name": "value",
"phone": "value",
"tags": [
"Option 1",
"Option 2"
]
},
)
data = res.json()['data']
/api/v1/contacts/opt-out
Opt a contact out (they will receive no further campaigns).
| Parameter | Type | Required | Settings |
|---|---|---|---|
phone |
string | Required | Phone |
curl -X POST https://waaraby.com/api/v1/contacts/opt-out \
-H 'Authorization: Bearer wa_live_xxx' \
-H 'Idempotency-Key: 550e8400-e29b-41d4' \
-H 'Content-Type: application/json' \
-d '{"phone":"value"}'
$response = Http::withHeaders([
'Authorization' => 'Bearer wa_live_xxx',
'Idempotency-Key' => $idempotencyKey,
])->post('https://waaraby.com/api/v1/contacts/opt-out', {
"phone": "value"
});
$data = $response->json('data');
const res = await fetch('https://waaraby.com/api/v1/contacts/opt-out', {
method: 'POST',
headers: {
'Authorization': 'Bearer wa_live_xxx',
'Idempotency-Key': crypto.randomUUID(),
'Content-Type': 'application/json',
},
body: JSON.stringify({"phone":"value"}),
});
const { data } = await res.json();
import requests, uuid
res = requests.post(
'https://waaraby.com/api/v1/contacts/opt-out',
headers={
'Authorization': 'Bearer wa_live_xxx',
'Idempotency-Key': str(uuid.uuid4()),
},
json={
"phone": "value"
},
)
data = res.json()['data']
/api/v1/chats
List chats with their last message.
curl -X POST https://waaraby.com/api/v1/chats \
-H 'Authorization: Bearer wa_live_xxx' \
-H 'Idempotency-Key: 550e8400-e29b-41d4' \
-H 'Content-Type: application/json' \
-d '[]'
$response = Http::withHeaders([
'Authorization' => 'Bearer wa_live_xxx',
'Idempotency-Key' => $idempotencyKey,
])->post('https://waaraby.com/api/v1/chats', []);
$data = $response->json('data');
const res = await fetch('https://waaraby.com/api/v1/chats', {
method: 'POST',
headers: {
'Authorization': 'Bearer wa_live_xxx',
'Idempotency-Key': crypto.randomUUID(),
'Content-Type': 'application/json',
},
body: JSON.stringify([]),
});
const { data } = await res.json();
import requests, uuid
res = requests.post(
'https://waaraby.com/api/v1/chats',
headers={
'Authorization': 'Bearer wa_live_xxx',
'Idempotency-Key': str(uuid.uuid4()),
},
json=[],
)
data = res.json()['data']
/api/v1/chats/{jid}/messages
Messages of a specific chat.
| Parameter | Type | Required | Settings |
|---|---|---|---|
jid |
string | Required | Chat JID |
curl -X POST https://waaraby.com/api/v1/chats/{jid}/messages \
-H 'Authorization: Bearer wa_live_xxx' \
-H 'Idempotency-Key: 550e8400-e29b-41d4' \
-H 'Content-Type: application/json' \
-d '{"jid":"value"}'
$response = Http::withHeaders([
'Authorization' => 'Bearer wa_live_xxx',
'Idempotency-Key' => $idempotencyKey,
])->post('https://waaraby.com/api/v1/chats/{jid}/messages', {
"jid": "value"
});
$data = $response->json('data');
const res = await fetch('https://waaraby.com/api/v1/chats/{jid}/messages', {
method: 'POST',
headers: {
'Authorization': 'Bearer wa_live_xxx',
'Idempotency-Key': crypto.randomUUID(),
'Content-Type': 'application/json',
},
body: JSON.stringify({"jid":"value"}),
});
const { data } = await res.json();
import requests, uuid
res = requests.post(
'https://waaraby.com/api/v1/chats/{jid}/messages',
headers={
'Authorization': 'Bearer wa_live_xxx',
'Idempotency-Key': str(uuid.uuid4()),
},
json={
"jid": "value"
},
)
data = res.json()['data']
/api/v1/campaigns
Create a message campaign for a contact list.
| Parameter | Type | Required | Settings |
|---|---|---|---|
wa_number_id |
integer | Required | Sending number id |
list_id |
integer | Required | Contact list id |
message |
string | Required | Message text (variables supported) |
curl -X POST https://waaraby.com/api/v1/campaigns \
-H 'Authorization: Bearer wa_live_xxx' \
-H 'Idempotency-Key: 550e8400-e29b-41d4' \
-H 'Content-Type: application/json' \
-d '{"wa_number_id":1,"list_id":1,"message":"Hello from the API"}'
$response = Http::withHeaders([
'Authorization' => 'Bearer wa_live_xxx',
'Idempotency-Key' => $idempotencyKey,
])->post('https://waaraby.com/api/v1/campaigns', {
"wa_number_id": 1,
"list_id": 1,
"message": "Hello from the API"
});
$data = $response->json('data');
const res = await fetch('https://waaraby.com/api/v1/campaigns', {
method: 'POST',
headers: {
'Authorization': 'Bearer wa_live_xxx',
'Idempotency-Key': crypto.randomUUID(),
'Content-Type': 'application/json',
},
body: JSON.stringify({"wa_number_id":1,"list_id":1,"message":"Hello from the API"}),
});
const { data } = await res.json();
import requests, uuid
res = requests.post(
'https://waaraby.com/api/v1/campaigns',
headers={
'Authorization': 'Bearer wa_live_xxx',
'Idempotency-Key': str(uuid.uuid4()),
},
json={
"wa_number_id": 1,
"list_id": 1,
"message": "Hello from the API"
},
)
data = res.json()['data']
Webhooks
Receive events in your system the moment they happen: inbound messages, status updates, opt-outs, disconnected numbers and finished campaigns.
| Event | Settings |
|---|---|
message.received |
Inbound message from a customer |
message.status |
Status update for an outbound message |
contact.opted_out |
A contact opted out |
number.disconnected |
A number disconnected |
campaign.finished |
A campaign finished |
Verifying the signature
Every webhook is signed with HMAC-SHA256 of the raw body using your account webhook secret. Verify it with a constant-time comparison.
X-Waaraby-Signature: sha256=<hex>
// PHP
$expected = hash_hmac('sha256', $rawBody, $webhookSecret);
if (! hash_equals($expected, $receivedSignature)) {
abort(401);
}
Error codes
| Code | Settings |
|---|---|
| 400 | Bad request — check your parameters |
| 401 | Invalid or revoked API key |
| 403 | The key lacks permission for this action |
| 404 | Resource not found |
| 409 | Conflict — same Idempotency-Key with different content |
| 422 | Validation failed |
| 429 | Rate limit exceeded — see the Retry-After header |
Rate limits
60 requests per minute per key, 10 sends per minute. Limits are adjustable by admins.