WhatsApp Unofficial Gateway API
REST API untuk mengirim & menerima pesan WhatsApp dari sistem Anda sendiri. Semua endpoint relatif terhadap base URL berikut.
https://wau.jasaonline.netButuh API key? Buat di portal setelah berlangganan.
Autentikasi
Setiap permintaan menyertakan header X-API-Key berisi API key penuh Anda (format jo_live_<…>). Bukan bearer token.
curl https://wau.jasaonline.net/v1/ping \
-H "X-API-Key: jo_live_xxxxxxxx_xxxxxxxx…"Key tidak valid atau dicabut → 401 unauthorized. Tenant ditangguhkan → 403 tenant_suspended. Key disimpan sebagai hash (argon2id) — simpan baik-baik saat dibuat, tidak dapat ditampilkan ulang oleh gateway (portal menyimpan salinan terenkripsi untuk Anda).
Rate limit & idempotensi
Batas default 60 permintaan/menit per key. Jika terlampaui → 429 rate_limit_exceeded disertai header Retry-After: 60.
Untuk mencegah duplikasi saat retry, sertakan header Idempotency-Key yang unik per operasi. Respons yang diputar ulang membawa Idempotent-Replay: true.
Perangkat & pairing
Sebuah "device" = satu nomor WhatsApp yang tertaut. Siklus statusnya:
NEW → PAIRING → ACTIVE → DISCONNECTED | LOGGED_OUT | BANNEDPOST /v1/devicesGET /v1/devicesGET /v1/devices/{id}DELETE /v1/devices/{id}POST /v1/devices/{id}/pairGET /v1/devices/{id}/pair/qr.pngGET /v1/devices/{id}/pair/streamBuat device, mulai pairing, lalu pindai QR (PNG di /pair/qr.png, atau stream SSE di /pair/stream). QR berotasi ~20 detik.
Setelah QR dipindai, status tetap PAIRING sekitar 25 detik(proses upload prekey) sebelum berubah ke ACTIVE. Jangan timeout lebih cepat. Tidak ada endpoint status terpisah — baca status via GET /v1/devices/{id}.
Pakai portal kami jika Anda tidak ingin membangun UI pairing sendiri.
Kirim pesan
POST /v1/messagescurl -X POST https://wau.jasaonline.net/v1/messages \
-H "X-API-Key: jo_live_…" \
-H "Content-Type: application/json" \
-d '{
"to": "6281234567890",
"type": "text",
"text": "Halo dari gateway"
}'Nomor to boleh berupa digit polos (otomatis ditambah @s.whatsapp.net) atau JID grup …@g.us. Sukses → 202 Accepted:
{
"message_id": "…",
"wa_message_id": "…",
"status": "queued",
"device_id": "…"
}Nomor pengirim & round-robin
Anda tidak mengirim daftar nomor pengirim — tidak ada field from. Pemilihan device diatur oleh satu field opsional device_id:
- Kosongkan
device_id→ gateway memilih otomatis. Untuk chat pribadi, percakapan baru disebar (round-robin) ke device aktif yang paling lama tak dipakai, lalu percakapan itu menempel ke device tersebut agar penerima selalu melihat nomor yang sama. Untuk grup, dipilih device yang menjadi anggota grup itu. - Isi
device_id: "<uuid>"→ paksa satu device tertentu (harus berstatusACTIVE). Ambil id dariGET /v1/devices.
Jadi round-robin lintas semua nomor aktif terjadi dengan sendirinya saat device_id dikosongkan; device_idhanya dipakai bila Anda ingin memilih satu pengirim spesifik. Respons & webhook selalu menyertakan device_id yang benar-benar dipakai.
202 bukan berarti terkirim. Gateway hanya memvalidasi bahwa to dan type terisi. Tipe tak dikenal, atau media tanpa media_id, tetap menerima 202 — kegagalannya muncul asinkron lewat webhook message.status (status failed + status_detail).
Tipe yang didukung: text, image, video, audio, document, sticker, location, contact, reaction, edit, delete. Media memakai media_id (unggah dulu, lihat bagian Media) + caption. Reaction & delete memakai target_id. Lintas-tipe: reply_to, mentions, ttl, dan callback_url per-pesan.
Catatan: tipe buttons saat ini di-render sebagai teks bernomor (WhatsApp membatasi tombol untuk akun tidak resmi), bukan tombol interaktif — jangan mengandalkannya sebagai tombol.
Media
POST /v1/mediaGET /v1/media/{id}Unggah via multipart/form-data (field file) atau JSON {"url": "…"}. Respons berisi media_id yang dipakai saat mengirim pesan media.
curl -X POST https://wau.jasaonline.net/v1/media \
-H "X-API-Key: jo_live_…" \
-F "file=@foto.jpg"
# → { "media_id": "…", "mime_type": "image/jpeg", "size_bytes": 12345 }Webhook
POST /v1/webhooksGET /v1/webhooksPATCH /v1/webhooks/{id}DELETE /v1/webhooks/{id}POST /v1/webhooks/{id}/rotate-secretGET /v1/webhooks/{id}/deliveriesDaftarkan URL untuk menerima event. secret hanya dikembalikan sekali (saat create / rotate-secret). Event yang tersedia: message.received & message.status.
Verifikasi tanda tangan (WAJIB)
Setiap kiriman membawa header X-JO-Signature, X-JO-Timestamp, dan X-JO-Event-Id. Tanda tangan = HMAC-SHA256 atas timestamp + "." + rawBody:
import crypto from "node:crypto";
function verify(req, secret) {
const ts = req.headers["x-jo-timestamp"];
const sig = req.headers["x-jo-signature"]; // "sha256=<hex>"
const expected =
"sha256=" +
crypto.createHmac("sha256", secret)
.update(ts + "." + req.rawBody) // rawBody = bytes mentah, bukan JSON re-serialize
.digest("hex");
return crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected));
}Balas 2xx untuk menandai sukses. Jika gagal, gateway mengulang dengan jadwal 0s · 30s · 2m · 10m · 1h · 6h · 24h, lalu berhenti (dead). Lima kegagalan beruntun membuka circuit breaker ~60 detik.
Callback per-pesan (callback_url pada POST /v1/messages) ditandatangani dengan secret callback tenant, berbeda dari secret endpoint di atas. Secret itu hanya bisa dilihat & dirotasi dari portal.
Payload webhook
message.received
{
"event": "message.received",
"data": {
"message_id": "…",
"wa_message_id": "…",
"device_id": "…",
"chat_jid": "6281…@s.whatsapp.net",
"sender_jid": "6281…@s.whatsapp.net",
"is_group": false,
"type": "text",
"body": { "text": "isi pesan" },
"media": { "media_id": "…", "url": "/v1/media/…" }
}
}media.url adalah path relatif — gabungkan dengan base URL untuk mengunduhnya (https://wau.jasaonline.net/v1/media/…). Bentuk body berbeda per tipe (mis. image → caption/mime; location → latitude/longitude).
message.status
{
"event": "message.status",
"data": {
"message_id": "…",
"wa_message_id": "…",
"to": "6281…@s.whatsapp.net",
"status": "delivered", // sent | delivered | read | played | failed | expired
"status_detail": "…" // ada saat failed/expired
}
}Group, chat, kontak
Tersedia set lengkap untuk grup (/v1/groups/*: join, info, participants, invite-link, leave), chat (/v1/chats/*: mute, archive, read, disappearing, typing, history), dan kontak (/v1/contacts/*: check, block, info, avatar, presence, business). Semua memakai auth & konvensi yang sama.
GET /v1/contacts/check?phones=628xx,629xx mengecek apakah nomor terdaftar di WhatsApp — berguna sebelum mengirim massal.
Kode error
| Status | Arti |
|---|---|
| 401 unauthorized | API key hilang / tidak valid / dicabut |
| 403 tenant_suspended | Tenant ditangguhkan |
| 429 rate_limit_exceeded | Batas laju terlampaui (lihat Retry-After) |
| 400 bad_request | Body / parameter tidak valid |
| 404 device_not_found | Device tidak ada / bukan milik Anda |
| 409 device_not_active | Device belum ACTIVE |
| 503 no_eligible_device | Tidak ada device aktif di bawah batas harian |
Siap mulai? Buat API key atau lihat harga.
