WhatsApp gateway

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_…
JenisFormatPerilaku
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.

  1. Ambil perangkat aktif + terhubung di tenant (role user: hanya perangkat miliknya).
  2. Acak di antara perangkat yang masih punya headroom kuota.
  3. Jika pemakaian ≥ 90% kuota per jam / hari atau ≥ 85% per menit, atau sedang cooldown burst, perangkat itu dilewati.
  4. 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 responsArti
device_idPerangkat yang dipilih untuk pesan ini
device_nameNama perangkat
routeauto · auto_fallback_near_cap · pinned
auto_routedtrue 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: queuedprocessingsentdeliveredread (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:

PengaturanDefault
Jeda min–maks + jitter3–8 detik ± 25%
Per menit10
Per jam200
Per hari1000
Burst / cooldown5 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": "…" }
HTTPContoh
400JSON tidak valid, nomor kosong, URL media tidak diizinkan
401Token hilang / salah
403Scope kurang, perangkat bukan milik Anda
404Pesan atau perangkat tidak ada
409Tidak 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_…
TypeFormatBehavior
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.

  1. Take active + connected devices in the tenant (role user: only their own devices).
  2. Pick at random among devices that still have quota headroom.
  3. If usage is ≥ 90% of the hourly / daily cap or ≥ 85% per minute, or the device is in burst cooldown, it is skipped.
  4. 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 fieldMeaning
device_idDevice chosen for this message
device_nameDevice name
routeauto · auto_fallback_near_cap · pinned
auto_routedtrue 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: queuedprocessingsentdeliveredread (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:

SettingDefault
Min–max delay + jitter3–8 seconds ± 25%
Per minute10
Per hour200
Per day1000
Burst / cooldown5 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": "…" }
HTTPExample
400Invalid JSON, empty number, media URL not allowed
401Missing / invalid token
403Insufficient scope, device is not yours
404Message or device not found
409No connected device available for auto-route

Ganti password