API Reference v2.0
All API requests require an API key. Send it in one of two ways:
# Header (recommended)
X-API-Key: wak_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
# Bearer token
Authorization: Bearer wak_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Your API key is generated when the admin creates a website. Find it on the Website Detail page.
/api/devices
Register a new device and initialize WhatsApp connection.
| Field | Type | Required | Description |
|---|---|---|---|
| device_id | string | Yes | Unique ID for this device |
| name | string | No | Human-readable name |
curl -X POST https://watzap.sakuajaib.id/api/devices \
-H "X-API-Key: wak_..." \
-H "Content-Type: application/json" \
-d '{"device_id":"phone-1","name":"Customer Support"}'
Response: 201 — Device registered. Poll GET /api/devices/:id/qr for the QR code.
/api/devices
List all devices belonging to your website.
curl -H "X-API-Key: wak_..." https://watzap.sakuajaib.id/api/devices
Response: Array of devices with id, name, status, phone_number, last_active, and live_status.
/api/devices/:id/status
Get live connection status for a device.
curl -H "X-API-Key: wak_..." https://watzap.sakuajaib.id/api/devices/phone-1/status
Response: { connected: true, status: "connected", phone_number: "62812xxx" }
/api/devices/:id/qr
Poll for the QR code. Returns base64 PNG. Poll every 3-5 seconds until connected.
curl -H "X-API-Key: wak_..." https://watzap.sakuajaib.id/api/devices/phone-1/qr
Response: { qr_code: "data:image/png;base64,...", generated_at: "ISO8601" } — or { status: "connected" } if already connected.
/api/devices/:id
Disconnect and remove a device. Session files are deleted — scanning QR again is required to reconnect.
curl -X DELETE -H "X-API-Key: wak_..." https://watzap.sakuajaib.id/api/devices/phone-1
/api/messages/send
Send a text or media message.
| Field | Type | Required | Description |
|---|---|---|---|
| device_id | string | Yes | Device to send from |
| to | string | Yes | Phone number or JID |
| message | string | * | Text content |
| media | string | * | URL or base64 string |
| media_type | string | No | image, video, audio, document (default: image) |
| caption | string | No | Caption for media (alias for message) |
* Either message or media is required.
# Text
curl -X POST https://watzap.sakuajaib.id/api/messages/send \
-H "X-API-Key: wak_..." \
-H "Content-Type: application/json" \
-d '{"device_id":"phone-1","to":"6281234567890","message":"Hello!"}'
# Image URL
curl -X POST https://watzap.sakuajaib.id/api/messages/send \
-H "X-API-Key: wak_..." \
-H "Content-Type: application/json" \
-d '{"device_id":"phone-1","to":"6281234567890","media":"https://example.com/photo.jpg","caption":"Check this out"}'
Response: { message_id: "...", timestamp: 1234567890 }
/api/messages/send-bulk
Send to multiple recipients. 2-second delay between each. Returns per-recipient results.
curl -X POST https://watzap.sakuajaib.id/api/messages/send-bulk \
-H "X-API-Key: wak_..." \
-H "Content-Type: application/json" \
-d '{"device_id":"phone-1","messages":[{"to":"6281234567890","message":"Hi"},{"to":"6280987654321","message":"Hello"}]}'
Response: { total, sent, failed, results: [...] }
/api/webhook
Get current webhook configuration for your website.
curl -H "X-API-Key: wak_..." https://watzap.sakuajaib.id/api/webhook
/api/webhook
Update webhook URL, secret, and subscribed events for your website.
| Field | Description |
|---|---|
| webhook_url | URL to receive webhooks |
| webhook_secret | Secret for HMAC signature verification |
| webhook_events | Comma-separated: qr,status,message,message.sent |
curl -X PUT https://watzap.sakuajaib.id/api/webhook \
-H "X-API-Key: wak_..." \
-H "Content-Type: application/json" \
-d '{"webhook_url":"https://myapp.com/api/wa-webhook","webhook_events":"qr,status,message"}'
/api/webhook/device/:id
Get the resolved webhook target for a specific device (shows device override or website fallback).
/api/webhook/device/:id
Override webhook settings for a specific device. Takes webhook_url, webhook_secret, webhook_events.
/api/webhook/test
Send a test webhook to your configured URL.
curl -X POST -H "X-API-Key: wak_..." https://watzap.sakuajaib.id/api/webhook/test
All stats endpoints are scoped to your website. Poll every 10-30 seconds for live monitoring.
/api/stats/server
Server health: CPU usage, RAM, uptime, Node.js version, platform. All responses are wrapped in a { success, data } envelope.
{
"success": true,
"data": {
"cpu_usage": 0.9,
"memory": { "used_mb": 964, "total_mb": 1968, "usage_percent": 49 },
"uptime_seconds": 217013,
"node_version": "v22.23.0",
"platform": "linux",
"pid": 629065
}
}
/api/stats/overview
Your website overview: device counts, messages today, webhook success rate.
{
"success": true,
"data": {
"website_name": "Saku Ajaib",
"total_devices": 5,
"active_devices": 3,
"messages_today": { "incoming": 150, "outgoing": 89 },
"webhook_success_rate": 98.5
}
}
/api/stats/devices
Per-device stats: status, phone number, last active, messages today. Returns an array under data.
{
"success": true,
"data": [
{
"id": "phone-1",
"name": "Customer Support",
"phone_number": "6281234567890",
"status": "connected",
"last_active": "2026-07-11 09:15:00",
"messages_today": { "incoming": 12, "outgoing": 8 }
}
]
}
/api/stats/messages
Message statistics: by hour (last 24h), by message type, total counts.
{
"success": true,
"data": {
"by_hour": [
{ "hour": "09", "direction": "incoming", "count": 5 },
{ "hour": "09", "direction": "outgoing", "count": 3 }
],
"by_type": [
{ "message_type": "conversation", "count": 40 },
{ "message_type": "imageMessage", "count": 6 }
],
"total": { "all": 46, "incoming": 28, "outgoing": 18 }
}
}
/api/stats/webhooks
Webhook delivery stats: success rate, by event, recent failures with details.
{
"success": true,
"data": {
"last_24h": { "total": 120, "successful": 118, "failed": 2, "success_rate": 98.3 },
"by_event": [
{ "event": "message", "total": 90, "successful": 90 },
{ "event": "status", "total": 30, "successful": 28 }
],
"recent_failures": [
{
"id": 51,
"device_id": "phone-1",
"website_id": "web_abc123",
"event": "status",
"url": "https://myapp.com/api/wa-webhook",
"status_code": 500,
"success": 0,
"error_message": "Request failed with status code 500",
"attempts": 3,
"created_at": "2026-07-11 08:40:00"
}
]
}
}
When events occur on your devices, WA Engine sends HTTP POST requests to your webhook URL.
| Event | Trigger |
|---|---|
| qr | QR code generated for scanning |
| status | Connection state changed (e.g. connected, disconnected, passkey_required) |
| message | Incoming message received |
| message.sent | Outgoing message from phone (human takeover) |
{
"event": "message",
"data": {
"device_id": "phone-1",
"from": "6281234567890@s.whatsapp.net",
"message_id": "BAE...",
"timestamp": 1690000000,
"type": "conversation",
"content": "Hello, can I get help?",
"has_media": false
},
"website_id": "web_abc123",
"device_id": "phone-1",
"timestamp": "2026-07-09T12:00:00.000Z"
}
| Header | Description |
|---|---|
| X-WA-Event | Event type: qr, status, message, message.sent |
| X-WA-Website-Id | Website ID that owns the device |
| X-WA-Device-Id | Device ID that triggered the event |
| X-WA-Signature | HMAC-SHA256 hex signature (if webhook_secret is set) |
// Node.js
const crypto = require('crypto');
app.post('/api/wa-webhook', (req, res) => {
const signature = req.headers['x-wa-signature'];
const rawBody = JSON.stringify(req.body);
const expected = crypto
.createHmac('sha256', 'whsec_your_secret')
.update(rawBody)
.digest('hex');
if (signature !== expected) {
return res.status(401).json({ error: 'Invalid signature' });
}
const { event, data, device_id } = req.body;
switch (event) {
case 'message':
console.log(`Message from \${data.from}: \${data.content}`);
break;
case 'qr':
console.log(`QR for \${device_id}: \${data.qr_code}`);
break;
case 'status':
console.log(`\${device_id} is now \${data.status}`);
break;
}
res.json({ received: true });
});
| HTTP | Code | Description |
|---|---|---|
| 401 | AUTH_REQUIRED | Missing API key |
| 401 | INVALID_API_KEY | API key not found or invalid |
| 403 | WEBSITE_INACTIVE | Website is deactivated |
| 403 | DEVICE_LIMIT_REACHED | Max devices limit reached |
| 403 | DEVICE_FORBIDDEN | Device belongs to another website |
| 404 | DEVICE_NOT_FOUND | Device ID not found |
| 429 | RATE_LIMIT_EXCEEDED | Too many requests |
| 503 | DEVICE_OFFLINE | Device not connected |
| 500 | INTERNAL_ERROR | Unexpected server error |
const API_KEY = 'wak_...';
const BASE = 'https://watzap.sakuajaib.id/api';
const headers = { 'X-API-Key': API_KEY, 'Content-Type': 'application/json' };
// Register a device
async function registerDevice(deviceId, name) {
const res = await fetch(\`\${BASE}/devices\`, {
method: 'POST', headers, body: JSON.stringify({ device_id: deviceId, name })
});
return res.json();
}
// Poll for QR code
async function pollQR(deviceId) {
const res = await fetch(\`\${BASE}/devices/\${deviceId}/qr\`, { headers });
return res.json();
}
// Send a message
async function sendMessage(deviceId, to, text) {
const res = await fetch(\`\${BASE}/messages/send\`, {
method: 'POST', headers,
body: JSON.stringify({ device_id: deviceId, to, message: text })
});
return res.json();
}
// Live polling — server health
async function getServerStats() {
const res = await fetch(\`\${BASE}/stats/server\`, { headers });
return res.json();
}
// Get website overview
async function getOverview() {
const res = await fetch(\`\${BASE}/stats/overview\`, { headers });
return res.json();
}
define('API_KEY', 'wak_...');
define('BASE', 'https://watzap.sakuajaib.id/api');
function sendMessage($deviceId, $to, $message) {
$ch = curl_init(BASE . '/messages/send');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'X-API-Key: ' . API_KEY,
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'device_id' => $deviceId,
'to' => $to,
'message' => $message,
]),
]);
$result = curl_exec($ch);
curl_close($ch);
return json_decode($result, true);
}
function getServerStats() {
$ch = curl_init(BASE . '/stats/server');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['X-API-Key: ' . API_KEY],
]);
$result = curl_exec($ch);
curl_close($ch);
return json_decode($result, true);
}
import requests
API_KEY = 'wak_...'
BASE = 'https://watzap.sakuajaib.id/api'
HEADERS = {'X-API-Key': API_KEY, 'Content-Type': 'application/json'}
def send_message(device_id, to, message):
r = requests.post(f'{BASE}/messages/send', json={
'device_id': device_id,
'to': to,
'message': message,
}, headers=HEADERS)
return r.json()
def get_stats():
r = requests.get(f'{BASE}/stats/overview', headers=HEADERS)
return r.json()
def register_device(device_id, name):
r = requests.post(f'{BASE}/devices', json={
'device_id': device_id,
'name': name,
}, headers=HEADERS)
return r.json()