WaAPI — Dokumentasi REST API
WaAPI mengirim pesan WhatsApp otomatis dari website atau aplikasi Anda.
Model sederhana: 1 device = 1 token = 1 nomor WhatsApp.
Semua response JSON memakai field status (boolean) dan
detail (pesan) atau reason (jika gagal).
Base URL
Ringkasan fitur
| Fitur | Status | Akses |
|---|---|---|
| Kirim pesan API | ✅ | POST /send |
| QR connect | ✅ | Portal /app |
| WABA (Cloud API) | ✅ | Portal /app → Hubungkan WABA |
| Inbox & kontak | ✅ | Live Portal /app |
| Auto reply / sapaan | ✅ | Live per tenant |
| Webhook pesan masuk | ✅ | Evolution → /webhook/evolution |
| OTP API | ✅ | POST /otp/send · POST /otp/verify |
| Broadcast | ✅ | POST /broadcast · Portal /app |
| Kirim gambar / file | ⏳ | Fase 2 |
Quick Start
Kirim pesan pertama dalam 3 langkah:
- Login ke portal https://wa-api.id/app → menu Device → hubungkan WhatsApp (scan QR).
- Klik tombol Token pada device → salin token (format
waapi_dev_...). - Panggil API dengan header
Authorization: Bearer <token>:
curl -X POST https://wa-api.id/api/v1/send \
-H "Authorization: Bearer waapi_dev_TOKEN_ANDA" \
-H "Content-Type: application/json" \
-d '{"to":"6281234567890","message":"Halo dari WaAPI!"}'
Response sukses: HTTP 202 dengan field id (UUID pesan). Cek status via GET /messages/{id}.
waapi_dev_. Jika token Anda terlihat seperti eyJpdiI6...,
buka ulang modal Token di portal — sistem akan memperbarui ke format yang benar.
Autentikasi
Setiap request wajib menyertakan token di header. WaAPI mendukung dua jenis token — pilih sesuai kebutuhan integrasi Anda.
Jenis token
| Token | Format resmi | Kapan dipakai | Endpoint |
|---|---|---|---|
| Device token | waapi_dev_ + 40 karakter acakContoh: waapi_dev_a8Kx9mP2... |
Kirim dari satu nomor WA tertentu. Paling umum dipakai. | Semua endpoint. Wajib untuk GET /device dan POST /device/disconnect. |
| Tenant API key | waapi_ + 40 karakter acakContoh: waapi_zR3kL7nQ... |
Kirim dari device default tenant. Cocok untuk backend multi-service. | /send, /otp/*, /broadcast, /messages |
Device token → portal Device → klik Token (sama persis dengan yang ditampilkan di modal).
Tenant API key → portal API Keys (ditampilkan sekali saat dibuat).
Cara kirim token (wajib)
Gunakan header Authorization dengan prefix Bearer.
Ini format standar yang sama dengan instruksi di portal Device → Token.
Authorization: Bearer waapi_dev_TOKEN_ANDA
Content-Type: application/json
Alternatif (jarang dipakai)
| Metode | Contoh |
|---|---|
| Header X-Api-Key | X-Api-Key: waapi_dev_xxxxx |
| Body / form | token=waapi_dev_xxxxx |
Untuk integrasi baru, gunakan selalu Authorization: Bearer. Content-Type: application/json direkomendasikan.
Kirim pesan teks ke satu nomor. Pesan masuk antrian dan dikirim di background.
Untuk device WABA, bisa kirim template Meta dengan template_name.
Parameter
| Field | Wajib | Keterangan |
|---|---|---|
target / to | Ya | Nomor tujuan: 08xxx atau 628xxx |
message | Opsional* | Isi pesan teks (maks. 4096 karakter) |
template_name | Opsional* | Nama template Meta (device WABA) |
template_language | Opsional | Bahasa template, default id |
variables | Opsional | Array variabel template, mis. ["Andi","12345"] |
countryCode | Opsional | Default 62 |
*Wajib isi message atau template_name (salah satu).
Contoh request
{
"target": "6281234567890",
"message": "Pesanan #12345 dikonfirmasi."
}
Response sukses
HTTP 202 Accepted — pesan masuk antrian, belum tentu sudah terkirim.
{
"status": "queued",
"detail": "Message queued",
"id": "550e8400-e29b-41d4-a716-446655440000",
"to": "6281234567890",
"message": "Pesanan #12345 dikonfirmasi.",
"device": "abc1",
"queued_at": "2026-06-13T08:00:00+00:00"
}
Simpan id (UUID) untuk cek status via GET /messages/{uuid}.
Response gagal
HTTP 400 / 422 / 429 — lihat field reason.
Kirim kode OTP ke nomor WhatsApp. Kode di-generate server-side dan dikirim sebagai pesan WA.
Simpan uuid dari response untuk verifikasi.
Alur OTP
POST /otp/send
POST /otp/verify dengan kode dari user
webhook_url saat verified/failed
Parameter
| Field | Wajib | Keterangan |
|---|---|---|
phone / to | Ya | Nomor tujuan: 08xxx atau 628xxx |
countryCode | Opsional | Default 62 |
length | Opsional | Panjang kode angka (4–8, default 6) |
expires_in | Opsional | Masa berlaku detik (60–600, default 300 = 5 menit) |
purpose | Opsional | Label, mis. login, reset-password |
message | Opsional | Template pesan WA. Placeholder: {code}, {minutes}, {purpose} |
webhook_url | Opsional | URL HTTPS yang menerima callback saat OTP verified atau failed |
Contoh request
{
"phone": "6281234567890",
"purpose": "login",
"length": 6,
"expires_in": 300,
"webhook_url": "https://app-anda.com/webhook/otp"
}
Response sukses
HTTP 202 Accepted
{
"detail": "OTP sent",
"uuid": "550e8400-e29b-41d4-a716-446655440000",
"phone": "6281234567890",
"expires_at": "2026-06-13T08:15:00+00:00",
"status": "pending"
}
Verifikasi kode OTP yang dikirim user. Maksimal 3 percobaan per UUID (default).
Parameter
| Field | Wajib | Keterangan |
|---|---|---|
uuid | Ya | UUID dari response /otp/send |
code | Ya | Kode angka yang diinput user (4–8 digit) |
Contoh request
{
"uuid": "550e8400-e29b-41d4-a716-446655440000",
"code": "482910"
}
Response sukses
HTTP 200 OK
{
"detail": "OTP verified",
"uuid": "550e8400-e29b-41d4-a716-446655440000",
"phone": "6281234567890",
"status": "verified",
"verified_at": "2026-06-13T08:12:30+00:00"
}
Response gagal
HTTP 422 Unprocessable Entity
{
"status": false,
"reason": "Kode OTP tidak valid."
}
Webhook callback (webhook_url)
Jika webhook_url diisi saat send, WaAPI akan POST JSON ke URL tersebut
saat OTP verified atau failed (percobaan habis).
{
"event": "otp.verified",
"uuid": "550e8400-e29b-41d4-a716-446655440000",
"phone": "6281234567890",
"purpose": "login",
"status": "verified",
"verified_at": "2026-06-13T08:12:30+00:00",
"timestamp": "2026-06-13T08:12:30+00:00"
}
Event otp.failed dikirim jika percobaan verifikasi melebihi batas.
Kirim pesan yang sama ke banyak nomor sekaligus. Proses berjalan di background (antrian).
Gunakan GET /broadcast/{uuid} untuk cek progress.
Parameter
| Field | Wajib | Keterangan |
|---|---|---|
message | Ya | Isi pesan broadcast (maks. 4096 karakter) |
name | Opsional | Nama kampanye (default: auto-generate) |
phones | Opsional* | Array nomor, contoh: ["628111","628222"] |
contact_ids | Opsional* | Array ID kontak dari inbox (portal → Kontak) |
all_contacts | Opsional* | true = kirim ke semua kontak tenant |
scheduled_at | Opsional | Jadwal kirim ISO 8601, mis. 2026-06-14T09:00:00+07:00 |
*Wajib isi minimal satu: phones, contact_ids, atau all_contacts: true.
Contoh request
{
"name": "Promo Ramadan",
"message": "Halo! Diskon 20% berlaku hari ini saja.",
"phones": ["6281234567890", "6289876543210"]
}
Response sukses
HTTP 202 Accepted
{
"detail": "Broadcast queued",
"uuid": "660e8400-e29b-41d4-a716-446655440001",
"name": "Promo Ramadan",
"status": "running",
"total_recipients": 2,
"scheduled_at": null
}
Cek progress kampanye broadcast. Poll endpoint ini sampai status = completed.
{
"detail": "ok",
"uuid": "660e8400-e29b-41d4-a716-446655440001",
"name": "Promo Ramadan",
"status": "completed",
"total_recipients": 100,
"sent_count": 98,
"failed_count": 2,
"scheduled_at": null,
"started_at": "2026-06-13T08:00:00+00:00",
"completed_at": "2026-06-13T08:05:30+00:00"
}
Cek status koneksi device WhatsApp.
Wajib pakai device token (waapi_dev_...).
{
"status": true,
"detail": "device profile",
"device": {
"code": "abc1",
"name": "Toko Utama",
"phone": "6281234567890",
"status": "connected",
"status_label": "Terhubung",
"connected": true,
"connected_at": "2026-06-10T12:00:00+00:00"
}
}
Putuskan sesi WhatsApp device.
Wajib pakai device token (waapi_dev_...).
Cek status pengiriman pesan. UUID didapat dari response POST /send field id.
queued
→
sent
→
delivered
→
read
atau
failed
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"status": "delivered",
"to": "6281234567890",
"message": "Tagihan WiFi jatuh tempo...",
"sent_at": "2026-06-13T08:00:05+00:00",
"delivered_at": "2026-06-13T08:00:12+00:00"
}
HTTP Status Code
Semua response memakai JSON. Field status: true = sukses, status: false = gagal.
| Code | Endpoint | Arti |
|---|---|---|
200 | GET, verify | Request berhasil (data langsung siap) |
202 | POST send, otp/send, broadcast | Diterima & masuk antrian background |
400 | Semua | Request invalid / device token required |
401 | Semua | Token tidak ada atau salah |
422 | POST | Validasi gagal (nomor invalid, OTP salah, dll.) |
429 | POST | Rate limit / kuota habis / anti-spam |
Webhook
1. OTP callback (dari API Anda)
Set field webhook_url saat POST /otp/send.
WaAPI akan POST ke URL tersebut. Lihat format di section OTP di atas.
2. Pesan masuk WhatsApp (Evolution API)
Agar inbox & auto reply berfungsi, arahkan webhook Evolution API ke:
POST https://wa-api.id/webhook/evolution
Event yang perlu diaktifkan di Evolution:
messages.upsert— pesan masuk (inbox, kontak, auto reply)messages.update— status terkirim / dibacaconnection.update— status koneksi device
Contoh Integrasi
curl -X POST https://wa-api.id/api/v1/send \
-H "Authorization: Bearer waapi_dev_TOKEN_ANDA" \
-H "Content-Type: application/json" \
-d '{"target":"6281234567890","message":"Pesanan #12345 dikonfirmasi."}'
$ch = curl_init('https://wa-api.id/api/v1/send');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer waapi_dev_TOKEN_ANDA',
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'target' => '6281234567890',
'message' => 'Pesanan #12345 dikonfirmasi.',
]),
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
const res = await fetch('https://wa-api.id/api/v1/send', {
method: 'POST',
headers: {
'Authorization': 'Bearer waapi_dev_TOKEN_ANDA',
'Content-Type': 'application/json',
},
body: JSON.stringify({
target: '6281234567890',
message: 'Pesanan #12345 dikonfirmasi.',
}),
});
const data = await res.json();
use Illuminate\Support\Facades\Http;
$response = Http::withHeaders([
'Authorization' => 'Bearer waapi_dev_TOKEN_ANDA',
])->post('https://wa-api.id/api/v1/send', [
'target' => '6281234567890',
'message' => 'Pesanan #12345 dikonfirmasi.',
]);
# 1. Kirim OTP
curl -X POST https://wa-api.id/api/v1/otp/send \
-H "Authorization: Bearer waapi_dev_TOKEN_ANDA" \
-H "Content-Type: application/json" \
-d '{"phone":"6281234567890","purpose":"login","webhook_url":"https://app-anda.com/webhook/otp"}'
# 2. Verifikasi (ganti UUID & code)
curl -X POST https://wa-api.id/api/v1/otp/verify \
-H "Authorization: Bearer waapi_dev_TOKEN_ANDA" \
-H "Content-Type: application/json" \
-d '{"uuid":"550e8400-e29b-41d4-a716-446655440000","code":"482910"}'
// Kirim OTP
$send = json_decode(file_get_contents('https://wa-api.id/api/v1/otp/send', false, stream_context_create([
'http' => [
'method' => 'POST',
'header' => "Authorization: Bearer waapi_dev_TOKEN_ANDA\r\nContent-Type: application/json",
'content' => json_encode(['phone' => '6281234567890', 'purpose' => 'login']),
],
])), true);
$uuid = $send['uuid'];
const headers = {
'Authorization': 'Bearer waapi_dev_TOKEN_ANDA',
'Content-Type': 'application/json',
};
const sendRes = await fetch('https://wa-api.id/api/v1/otp/send', {
method: 'POST', headers,
body: JSON.stringify({ phone: '6281234567890', purpose: 'login' }),
});
const { uuid } = await sendRes.json();
use Illuminate\Support\Facades\Http;
$headers = ['Authorization' => 'Bearer waapi_dev_TOKEN_ANDA'];
$send = Http::withHeaders($headers)->post('https://wa-api.id/api/v1/otp/send', [
'phone' => '6281234567890',
'purpose' => 'login',
])->json();
curl -X POST https://wa-api.id/api/v1/broadcast \
-H "Authorization: Bearer waapi_dev_TOKEN_ANDA" \
-H "Content-Type: application/json" \
-d '{"name":"Promo","message":"Diskon 20% hari ini!","phones":["628111","628222"]}'
curl https://wa-api.id/api/v1/broadcast/660e8400-e29b-41d4-a716-446655440001 \
-H "Authorization: Bearer waapi_dev_TOKEN_ANDA"
$ctx = stream_context_create(['http' => [
'method' => 'POST',
'header' => "Authorization: Bearer waapi_dev_TOKEN_ANDA\r\nContent-Type: application/json",
'content' => json_encode([
'message' => 'Diskon 20% hari ini!',
'phones' => ['6281234567890', '6289876543210'],
]),
]]);
$result = json_decode(file_get_contents('https://wa-api.id/api/v1/broadcast', false, $ctx), true);
const headers = {
'Authorization': 'Bearer waapi_dev_TOKEN_ANDA',
'Content-Type': 'application/json',
};
const create = await fetch('https://wa-api.id/api/v1/broadcast', {
method: 'POST', headers,
body: JSON.stringify({
message: 'Diskon 20% hari ini!',
phones: ['6281234567890', '6289876543210'],
}),
});
const { uuid } = await create.json();
use Illuminate\Support\Facades\Http;
$headers = ['Authorization' => 'Bearer waapi_dev_TOKEN_ANDA'];
$campaign = Http::withHeaders($headers)->post('https://wa-api.id/api/v1/broadcast', [
'name' => 'Promo Ramadan',
'message' => 'Diskon 20% hari ini!',
'phones' => ['6281234567890'],
])->json();
Kode Error (reason)
Field detail kadang berisi pesan manusia (Bahasa Indonesia). Field reason untuk logic di kode Anda.
| reason | HTTP | Arti |
|---|---|---|
token required | 401 | Header Authorization tidak dikirim, atau tanpa prefix Bearer |
token invalid | 401 | Token salah, kedaluwarsa, atau format tidak valid (bukan waapi_dev_... / waapi_...) |
target invalid | 422 | Format nomor tidak valid |
input invalid | 422 | Parameter broadcast/OTP tidak lengkap |
device disconnected | 400 | WhatsApp belum terhubung (scan QR dulu) |
insufficient quota | 429 | Kuota pesan habis |
rate limit exceeded | 429 | Terlalu banyak request per menit |
duplicate message | 429 | Pesan duplikat ke nomor sama (anti-spam) |
recipient cooldown | 429 | Nomor baru saja dikirimi pesan |
recipient limit exceeded | 429 | Terlalu banyak penerima unik per jam |
subscription expired | 429 | Paket / trial habis |
device limit exceeded | 429 | Batas nomor WA paket tercapai |
device rate limit exceeded | 429 | Device mengirim terlalu cepat |
account suspended | 429 | Akun nonaktif atau ditangguhkan |
trial expired | 429 | Masa trial habis |
spam content detected | 429 | Konten terdeteksi spam (link/karakter berulang) |
otp cooldown | 429 | OTP baru saja dikirim ke nomor ini |
otp limit exceeded | 429 | Batas OTP per jam (per nomor / per akun) |
broadcast limit exceeded | 429 | Terlalu banyak penerima atau kampanye broadcast per hari |
autoreply limit exceeded | 429 | Batas auto reply per jam tercapai |
autoreply cooldown | 429 | Auto reply ke kontak ini baru saja dikirim |
Troubleshooting autentikasi
| Gejala | Penyebab umum | Solusi |
|---|---|---|
token required (401) |
Header Authorization kosong atau tanpa kata Bearer |
Pakai: Authorization: Bearer <token_dari_portal> |
token required (401) |
Token hanya di body JSON, tanpa header | Pindahkan token ke header Authorization: Bearer ... |
token invalid (401) |
Token terpotong saat copy-paste atau pakai token lama | Buka portal → Device → Token → salin ulang token lengkap |
token invalid (401) |
Token tampil eyJpdiI6... (format lama/rusak) |
Buka ulang modal Token di portal — token akan diperbarui otomatis ke waapi_dev_... |
token invalid (401) |
Pakai device token untuk tenant API key (atau sebaliknya) | Cek prefix: waapi_dev_ = device, waapi_ = tenant key |
Perlindungan Anti-Spam (otomatis)
WaAPI menerapkan batas kecepatan dan konten untuk mengurangi risiko nomor WhatsApp terkena ban:
- Kirim pesan API — rate limit per menit, cooldown antar nomor, cegah pesan duplikat, batas penerima unik per jam (sesuai paket).
- OTP — cooldown antar kirim, maks. 5 OTP/jam per nomor, maks. 100 OTP/jam per akun (default).
- Broadcast — jeda ~3 detik antar penerima, maks. 500 penerima/kampanye, maks. 10 kampanye/hari (default).
- Auto reply — maks. 60 balasan/jam per device, cooldown 30 menit per kontak (default).
- Konten — panjang pesan dibatasi, terlalu banyak link atau karakter berulang ditolak.
Error OTP (HTTP 422, pesan di reason)
Kode OTP tidak valid.Kode OTP sudah kedaluwarsa.Permintaan OTP gagal.— percobaan verifikasi melebihi batas