WA

WA Engine

API Reference v2.0

Admin Panel

Authentication

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.

The API key is scoped to your website. All responses only include data for your devices, messages, and webhooks.

Device Management

POST /api/devices

Register a new device and initialize WhatsApp connection.

FieldTypeRequiredDescription
device_idstringYesUnique ID for this device
namestringNoHuman-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.

GET /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.

GET /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" }

GET /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.

DELETE /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

Messaging

POST /api/messages/send

Send a text or media message.

FieldTypeRequiredDescription
device_idstringYesDevice to send from
tostringYesPhone number or JID
messagestring*Text content
mediastring*URL or base64 string
media_typestringNoimage, video, audio, document (default: image)
captionstringNoCaption 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 }

POST /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: [...] }

Webhook Configuration

GET /api/webhook

Get current webhook configuration for your website.

curl -H "X-API-Key: wak_..." https://watzap.sakuajaib.id/api/webhook
PUT /api/webhook

Update webhook URL, secret, and subscribed events for your website.

FieldDescription
webhook_urlURL to receive webhooks
webhook_secretSecret for HMAC signature verification
webhook_eventsComma-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"}'
GET /api/webhook/device/:id

Get the resolved webhook target for a specific device (shows device override or website fallback).

PUT /api/webhook/device/:id

Override webhook settings for a specific device. Takes webhook_url, webhook_secret, webhook_events.

POST /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

Statistics & Live Polling

All stats endpoints are scoped to your website. Poll every 10-30 seconds for live monitoring.

GET /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
  }
}
GET /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
  }
}
GET /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 }
    }
  ]
}
GET /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 }
  }
}
GET /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"
      }
    ]
  }
}

Webhook Integration

When events occur on your devices, WA Engine sends HTTP POST requests to your webhook URL.

Event Types

EventTrigger
qrQR code generated for scanning
statusConnection state changed (e.g. connected, disconnected, passkey_required)
messageIncoming message received
message.sentOutgoing message from phone (human takeover)

Envelope Format

{
  "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"
}

Headers

HeaderDescription
X-WA-EventEvent type: qr, status, message, message.sent
X-WA-Website-IdWebsite ID that owns the device
X-WA-Device-IdDevice ID that triggered the event
X-WA-SignatureHMAC-SHA256 hex signature (if webhook_secret is set)

Verifying Webhook Authenticity

// 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 });
});

Error Codes

HTTPCodeDescription
401AUTH_REQUIREDMissing API key
401INVALID_API_KEYAPI key not found or invalid
403WEBSITE_INACTIVEWebsite is deactivated
403DEVICE_LIMIT_REACHEDMax devices limit reached
403DEVICE_FORBIDDENDevice belongs to another website
404DEVICE_NOT_FOUNDDevice ID not found
429RATE_LIMIT_EXCEEDEDToo many requests
503DEVICE_OFFLINEDevice not connected
500INTERNAL_ERRORUnexpected server error

Code Examples

Node.js — Full Integration

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();
}

PHP (cURL)

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);
}

Python (requests)

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()