Mulai cepat
Base URL integrasi: https://wablas.mada.net.id
Buat token di menu Token API. Satu token tenant (wbl_…) dipakai untuk semua perangkat yang terhubung. Untuk blast invoice, jangan kirim device_id — gateway memilih perangkat secara acak dan pindah otomatis jika kuota per jam hampir penuh.
curl -s -H "Authorization: Bearer wbl_TOKEN_ANDA" \
-H "Content-Type: application/json" \
-d '{"phone":"6281234567890","message":"Invoice #1001 siap."}' \
BASE/api/v1/send/text
Autentikasi
Salah satu header berikut (prioritas dari atas):
X-API-Key: wbl_…X-API-Token: wbl_…Authorization: Bearer wbl_…
| Jenis | Format | Perilaku |
|---|---|---|
| Token tenant | wbl_ + hex |
Semua perangkat tenant. device_id opsional (auto-route jika kosong). |
| Token perangkat | wbl_dev_ + hex |
Terkunci ke 1 perangkat. Tidak auto-pindah. |
Scope default token tenant: messages:send, messages:read. CSRF tidak diperlukan untuk token API.
1 token · banyak perangkat (auto-route)
Dirancang untuk pengiriman massal (invoice, notifikasi) tanpa aplikasi klien harus membagi beban.
- Ambil perangkat aktif + terhubung di tenant (role
user: hanya perangkat miliknya). - Acak di antara perangkat yang masih punya headroom kuota.
- Jika pemakaian ≥ 90% kuota per jam / hari atau ≥ 85% per menit, atau sedang cooldown burst, perangkat itu dilewati.
- Jika semua hampir penuh, dipilih yang sisa kuotanya paling besar (pesan tetap diantrikan; jeda+jitter tetap jalan).
Jeda antar pesan, jitter, dan cap per menit/jam/hari berlaku per perangkat dengan nilai dari pengaturan tenant (superadmin). Auto-route tidak menonaktifkan anti-spam — hanya memindahkan beban ke nomor lain.
| Field respons | Arti |
|---|---|
device_id | Perangkat yang dipilih untuk pesan ini |
device_name | Nama perangkat |
route | auto · auto_fallback_near_cap · pinned |
auto_routed | true jika gateway yang memilih |
Untuk mengunci ke satu nomor, kirim device_id. Token wbl_dev_ selalu pinned.
POST /api/v1/send/text
Alias body: message, body, atau caption. Penerima: phone atau recipient (08… diubah ke 62…).
{
"phone": "6281234567890",
"message": "Halo, invoice #1001 sebesar Rp 250.000.",
"external_id": "inv-1001"
}
Respons 201:
{
"success": true,
"data": {
"message_id": "uuid",
"status": "queued",
"device_id": "uuid",
"device_name": "Kasir 2",
"route": "auto",
"auto_routed": true,
"recipient": "6281234567890",
"queued": true
}
}
POST /api/v1/send/image
URL gambar publik https://… (bukan IP privat / localhost). Alias URL: media_url atau image.
{
"phone": "6281234567890",
"image": "https://cdn.contoh.id/invoice-1001.jpg",
"caption": "Invoice #1001"
}
POST /api/v1/send/document
Alias URL: media_url atau document. Opsional filename, media_mime_type.
{
"phone": "6281234567890",
"document": "https://cdn.contoh.id/invoice-1001.pdf",
"filename": "invoice-1001.pdf",
"media_mime_type": "application/pdf"
}
GET /api/v1/send/message/{id}
Status: queued → processing → sent → delivered → read (atau failed).
curl -s -H "Authorization: Bearer wbl_TOKEN" \
BASE/api/v1/send/message/MESSAGE_UUID
Webhook global (1 URL · semua perangkat)
Isi URL di dashboard: menu Perangkat atau Token API (form Webhook tenant di atas tabel). Aplikasi terintegrasi juga bisa create/update lewat token. Callback yang sama dipakai untuk pesan masuk/keluar dari perangkat aktif mana pun. device_id + device_phone di payload memberitahu nomor mana yang terlibat.
POST atau PUT /api/v1/webhook
curl -s -X POST -H "Authorization: Bearer wbl_TOKEN" \
-H "Content-Type: application/json" \
-d '{"url":"https://app.anda.id/hooks/wablas"}' \
BASE/api/v1/webhook
Opsional pisah inbound/outbound, atau set secret / rotate_secret. GET /api/v1/webhook melihat konfigurasi; DELETE menghapus. URL wajib HTTPS publik.
Gateway mengirim POST JSON ke URL Anda:
Content-Type: application/json
User-Agent: Wablas-Gateway-Webhook/1.0
X-Wablas-Event: sent | delivered | read | failed | message.inbound
X-Wablas-Device-Id: <uuid>
X-Wablas-Signature: sha256=<hmac>
{
"event": "message.inbound",
"message_id": "uuid",
"device_id": "uuid",
"device_name": "Kasir 2",
"device_phone": "628123456789",
"tenant_id": "uuid",
"external_id": "inv-1001",
"recipient": "6289876543210",
"direction": "inbound",
"type": "text",
"status": "delivered",
"body": "Sudah bayar",
"timestamp": "2026-09-10T00:00:00Z"
}
Event failed mengisi error_message (alasan jelas, bahasa Indonesia) dan error_code (misalnya number_not_on_whatsapp = nomor tidak terdaftar/tidak aktif). Prioritas URL: webhook global tenant, lalu webhook per perangkat (dashboard). Verifikasi HMAC-SHA256 terhadap body mentah dengan secret yang dikembalikan saat create/rotate. File lengkap: dokumentasi.md di server.
Rate limit & anti-spam
Tidak ada kuota HTTP per token. Limit anti-spam diatur per tenant oleh superadmin (halaman Tenant). Redis tetap menghitung per perangkat agar multi-device tetap aman. Default tenant baru:
| Pengaturan | Default |
|---|---|
| Jeda min–maks + jitter | 3–8 detik ± 25% |
| Per menit | 10 |
| Per jam | 200 |
| Per hari | 1000 |
| Burst / cooldown | 5 pesan / 60 dtk |
Ubah lewat dashboard Tenant (PUT /api/v1/tenants/{id} field rl_*). API tetap 201 queued meski perangkat sedang jeda. Endpoint PUT /api/v1/devices/{id}/rate-limit sudah dihapus.
Proteksi tambahan HTTP (per token, longgar): kirim ≈900/menit, cek status ≈1800/menit, ubah webhook ≈120/menit. Jika Redis gangguan, limiter fail-open agar pengiriman tetap jalan. Anti-spam per perangkat tetap berlaku.
Error
{ "success": false, "error": "…" }
| HTTP | Contoh |
|---|---|
| 400 | JSON tidak valid, nomor kosong, URL media tidak diizinkan |
| 401 | Token hilang / salah |
| 403 | Scope kurang, perangkat bukan milik Anda |
| 404 | Pesan atau perangkat tidak ada |
| 409 | Tidak ada perangkat terhubung untuk auto-route |
Quick start
Integration base URL: https://wablas.mada.net.id
Create a token in the API tokens menu. One tenant token (wbl_…) works for every connected device. For invoice blasts, do not send device_id — the gateway picks a device at random and switches automatically when the hourly quota is nearly full.
curl -s -H "Authorization: Bearer wbl_YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"phone":"6281234567890","message":"Invoice #1001 is ready."}' \
BASE/api/v1/send/text
Authentication
Use one of the following headers (priority from top):
X-API-Key: wbl_…X-API-Token: wbl_…Authorization: Bearer wbl_…
| Type | Format | Behavior |
|---|---|---|
| Tenant token | wbl_ + hex |
All tenant devices. device_id is optional (auto-route when omitted). |
| Device token | wbl_dev_ + hex |
Locked to one device. No auto-switch. |
Default tenant token scopes: messages:send, messages:read. CSRF is not required for API tokens.
1 token · many devices (auto-route)
Built for bulk sending (invoices, notifications) so the client app does not have to split load.
- Take active + connected devices in the tenant (role
user: only their own devices). - Pick at random among devices that still have quota headroom.
- If usage is ≥ 90% of the hourly / daily cap or ≥ 85% per minute, or the device is in burst cooldown, it is skipped.
- If every device is nearly full, the one with the most remaining quota is used (the message is still queued; delay + jitter still apply).
Inter-message delay, jitter, and per-minute/hour/day caps still apply per device using the tenant settings (superadmin). Auto-route does not disable anti-spam — it only moves load to another number.
| Response field | Meaning |
|---|---|
device_id | Device chosen for this message |
device_name | Device name |
route | auto · auto_fallback_near_cap · pinned |
auto_routed | true if the gateway chose the device |
To pin a number, send device_id. A wbl_dev_ token is always pinned.
POST /api/v1/send/text
Body aliases: message, body, or caption. Recipient: phone or recipient (08… is converted to 62…).
{
"phone": "6281234567890",
"message": "Hello, invoice #1001 for Rp 250.000.",
"external_id": "inv-1001"
}
201 response:
{
"success": true,
"data": {
"message_id": "uuid",
"status": "queued",
"device_id": "uuid",
"device_name": "Cashier 2",
"route": "auto",
"auto_routed": true,
"recipient": "6281234567890",
"queued": true
}
}
POST /api/v1/send/image
Public image URL https://… (not a private IP / localhost). URL aliases: media_url or image.
{
"phone": "6281234567890",
"image": "https://cdn.example.com/invoice-1001.jpg",
"caption": "Invoice #1001"
}
POST /api/v1/send/document
URL aliases: media_url or document. Optional filename, media_mime_type.
{
"phone": "6281234567890",
"document": "https://cdn.example.com/invoice-1001.pdf",
"filename": "invoice-1001.pdf",
"media_mime_type": "application/pdf"
}
GET /api/v1/send/message/{id}
Status: queued → processing → sent → delivered → read (or failed).
curl -s -H "Authorization: Bearer wbl_TOKEN" \
BASE/api/v1/send/message/MESSAGE_UUID
Global webhook (1 URL · all devices)
Set the URL in the dashboard: Devices or API tokens (Tenant webhook card above the table). Integrations can also create/update once with a token. The same callback is used for inbound/outbound messages from any active device. device_id + device_phone in the payload tell you which number was involved.
POST or PUT /api/v1/webhook
curl -s -X POST -H "Authorization: Bearer wbl_TOKEN" \
-H "Content-Type: application/json" \
-d '{"url":"https://your.app/hooks/wablas"}' \
BASE/api/v1/webhook
Optionally split inbound/outbound, or set secret / rotate_secret. GET /api/v1/webhook reads the config; DELETE removes it. The URL must be public HTTPS.
The gateway sends POST JSON to your URL:
Content-Type: application/json
User-Agent: Wablas-Gateway-Webhook/1.0
X-Wablas-Event: sent | delivered | read | failed | message.inbound
X-Wablas-Device-Id: <uuid>
X-Wablas-Signature: sha256=<hmac>
{
"event": "message.inbound",
"message_id": "uuid",
"device_id": "uuid",
"device_name": "Cashier 2",
"device_phone": "628123456789",
"tenant_id": "uuid",
"external_id": "inv-1001",
"recipient": "6289876543210",
"direction": "inbound",
"type": "text",
"status": "delivered",
"body": "Already paid",
"timestamp": "2026-09-10T00:00:00Z"
}
A failed event fills error_message (clear reason) and error_code (for example number_not_on_whatsapp = number not registered / inactive). URL priority: tenant global webhook, then per-device webhook (dashboard). Verify HMAC-SHA256 of the raw body with the secret returned on create/rotate. Full file: dokumentasi.md on the server.
Rate limit & anti-spam
There is no HTTP quota per token. Anti-spam limits are set per tenant by superadmin (Tenants page). Redis still tracks usage per device so multi-device stays safe. Defaults for a new tenant:
| Setting | Default |
|---|---|
| Min–max delay + jitter | 3–8 seconds ± 25% |
| Per minute | 10 |
| Per hour | 200 |
| Per day | 1000 |
| Burst / cooldown | 5 messages / 60 sec |
Change them on the Tenants dashboard (PUT /api/v1/tenants/{id} rl_* fields). The API still returns 201 queued while a device is delaying. PUT /api/v1/devices/{id}/rate-limit has been removed.
Extra HTTP caps (per token, generous): send ≈900/min, status ≈1800/min, webhook writes ≈120/min. If Redis is down the limiter fails open so sending continues. Per-device anti-spam still applies.
Errors
{ "success": false, "error": "…" }
| HTTP | Example |
|---|---|
| 400 | Invalid JSON, empty number, media URL not allowed |
| 401 | Missing / invalid token |
| 403 | Insufficient scope, device is not yours |
| 404 | Message or device not found |
| 409 | No connected device available for auto-route |