Lewati ke konten utama
API Reference

WhatsApp Unofficial Gateway API

REST API untuk mengirim & menerima pesan WhatsApp dari sistem Anda sendiri. Semua endpoint relatif terhadap base URL berikut.

base url
https://wau.jasaonline.net

Butuh API key? Buat di portal setelah berlangganan.

Empat langkah untuk mulai
1Buat API key di portalX-API-Key: jo_live_…
2Buat devicePOST /v1/devices
3Tautkan nomor lewat QRPOST /v1/devices/{id}/pair
4Kirim pesan pertamaPOST /v1/messages
Key disimpan sebagai hash argon2id

Autentikasi

Setiap permintaan menyertakan header X-API-Key berisi API key penuh Anda (format jo_live_<…>). Bukan bearer token.

Aplikasi AndaX-API-KeyGatewayvalidasi200 / 401
curl
curl https://wau.jasaonline.net/v1/ping \
-H "X-API-Key: jo_live_xxxxxxxx_xxxxxxxx…"
401 unauthorizedKey tidak valid atau dicabut
403 tenant_suspendedTenant ditangguhkan

Key disimpan sebagai hash (argon2id) — simpan baik-baik saat dibuat, tidak dapat ditampilkan ulang oleh gateway (portal menyimpan salinan terenkripsi untuk Anda).

Apakah bagian ini membantu?

Rate limit & idempotensi

60 req/min

Batas default per key. Terlampaui → 429 rate_limit_exceeded disertai header Retry-After: 60.

Idempotency-KeyHeader unik per operasi. Gateway mengabaikan permintaan duplikat dengan key yang sama, mencegah pengiriman ganda saat retry.

Sertakan header unik per operasi untuk mencegah duplikasi saat retry. Respons yang diputar ulang membawa Idempotent-Replay: true.

Apakah bagian ini membantu?

Resep penggunaan

Empat alur umum yang bisa langsung ditiru. Setiap cuplikan bisa disalin dan dijalankan setelah Anda mengganti X-API-Key dan nilai contoh lainnya.

1. Kirim pesan pertama

Buat device, mulai pairing, pindai QR, lalu kirim pesan teks.

bash
# 1. Buat device
DEVICE=$(curl -s -X POST https://wau.jasaonline.net/v1/devices \
-H "X-API-Key: jo_live_…" \
-H "Content-Type: application/json" \
-d '{"label":"CS Utama"}' | jq -r '.id')
# 2. Buka sesi pairing
curl -s -X POST https://wau.jasaonline.net/v1/devices/$DEVICE/pair -H "X-API-Key: jo_live_…"
# 3. Tampilkan QR (buka URL ini di peramban, pindai dalam ~20 detik)
open https://wau.jasaonline.net/v1/devices/$DEVICE/pair/qr.png
# 4. Tunggu status ACTIVE, lalu kirim pesan
curl -s -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"}'

2. Terima webhook

Daftarkan URL, lalu verifikasi tanda tangan di endpoint Anda sebelum membalas 200 OK.

bash
# Daftarkan endpoint penerima
curl -s -X POST https://wau.jasaonline.net/v1/webhooks \
-H "X-API-Key: jo_live_…" \
-H "Content-Type: application/json" \
-d '{"url":"https://server-anda.com/webhook","events":["message.received","message.status"]}'

Di server Anda (contoh Express — lihat bagian Webhook untuk bahasa lain):

node
app.use("/webhook", express.json({
verify: (req, _res, buf) => { req.rawBody = buf; }
}));
app.post("/webhook", (req, res) => {
if (!verify(req, process.env.JO_WEBHOOK_SECRET)) return res.sendStatus(401);
res.sendStatus(200); // balas dulu
enqueue(req.body); // kerjakan belakangan
});

3. Retry idempoten

Kirim dengan Idempotency-Key unik. Kalau koneksi putus atau dapat 5xx, ulangi permintaan dengan key yang sama — gateway akan mengembalikan respons asli, bukan menjalankan ulang.

bash
IDEMPOTENCY=$(uuidgen)
for i in 1 2 3; do
STATUS=$(curl -s -o /tmp/wa_resp.json -w "%{http_code}" -X POST https://wau.jasaonline.net/v1/messages \
-H "X-API-Key: jo_live_…" \
-H "Idempotency-Key: $IDEMPOTENCY" \
-H "Content-Type: application/json" \
-d '{"to":"6281234567890","type":"text","text":"Pesan penting"}')
[ "$STATUS" -lt 500 ] && break
sleep $((2 ** i))
done
cat /tmp/wa_resp.json

Key idempotensi diingat selama 10 menit (METHOD+PATH+body). Respons ulang membawa header Idempotent-Replay: true.

4. Kelola grup

Gabung ke grup lewat kode undangan, tambah anggota, dan ubah pengaturan dasar.

bash
# Gabung lewat kode undangan
curl -s -X POST https://wau.jasaonline.net/v1/groups/join \
-H "X-API-Key: jo_live_…" \
-H "Content-Type: application/json" \
-d '{"code":"AbCdEfGhIjK"}'
# Ambil JID grup dari info grup (atau dari pesan masuk)
JID="1203630…@g.us"
# Tambah anggota
curl -s -X POST https://wau.jasaonline.net/v1/groups/$JID/participants \
-H "X-API-Key: jo_live_…" \
-H "Content-Type: application/json" \
-d '{"action":"add","jids":["6281234567890@s.whatsapp.net"]}'
# Ubah nama & topik
curl -s -X PATCH https://wau.jasaonline.net/v1/groups/$JID \
-H "X-API-Key: jo_live_…" \
-H "Content-Type: application/json" \
-d '{"name":"Tim Support","topic":"Internal"}'
Apakah bagian ini membantu?

Referensi endpoint

Seluruh endpoint, lengkap dengan contoh payload dan contoh balasan — dan bisa dijalankan langsung dari halaman ini. Tekan Authorize, tempel API key Anda, lalu buka satu endpoint dan tekan Try it out.

Yang dijalankan di sini adalah permintaan SUNGGUHAN ke akun Anda: pesan benar-benar terkirim, webhook benar-benar didaftarkan, perangkat benar-benar terhapus. Operasi yang mengubah data ditandai peringatan di dalam panelnya.

Memeriksa…

Perangkat

Satu device = satu nomor WhatsApp yang tertaut. NEW → PAIRING → ACTIVE.

Pesan

Kirim pesan. 202 berarti diterima antrean, BUKAN terkirim — hasilnya datang lewat webhook message.status.

Media

Unggah berkas — dan kalau `to` diisi, sekalian kirim. Tanpa `to`, endpoint ini hanya menyimpan dan mengembalikan media_id yang bisa dipakai berkali-kali lewat POST /v1/messages (satu unggah, banyak penerima).

Webhook

Endpoint penerima event, riwayat kiriman, dan kirim ulang.

Grup

Gabung, kelola anggota, dan ubah pengaturan grup.

Chat

Operasi pada satu percakapan.

Kontak

Cek nomor, blokir, dan profil kontak.

Audit & utilitas

Jejak audit tenant dan endpoint pemeriksaan.

Permintaan dilewatkan proxy milik situs ini, bukan dipanggil peramban langsung — CSP situs menutup koneksi ke host lain dan gateway tidak memasang header CORS. Proxy hanya meneruskan (method, path) yang ada di halaman ini, dan API key Anda tidak pernah dicatat.

Apakah bagian ini membantu?

Perangkat & pairing

Sebuah "device" = satu nomor WhatsApp yang tertaut. Siklus statusnya:

NEWPAIRINGACTIVEDISCONNECTED | LOGGED_OUT | BANNED

Buat 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}.

Apakah bagian ini membantu?

Kirim pesan

curl · text
curl -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 JIDJabber ID — pengenal unik untuk kontak atau grup di WhatsApp, biasanya berbentuk 6281…@s.whatsapp.net atau 120363…@g.us. grup …@g.us. Sukses → 202 Accepted:

202
{
"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:

device_id kosong

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.

device_id: "<uuid>"

Paksa satu device tertentu (harus berstatus ACTIVE). Ambil id dari GET /v1/devices. 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

textimagevideoaudiodocumentstickerlocationcontactreactioneditdelete

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.

Tipe buttons saat ini di-render sebagai teks bernomor (WhatsApp membatasi tombol untuk akun tidak resmi), bukan tombol interaktif — jangan mengandalkannya sebagai tombol.

Apakah bagian ini membantu?

Media

Unggah via multipart/form-data (field file) atau JSON {"url": "…"}. Sertakan to (dan caption opsional) kalau berkasnya mau langsung dikirim — balasannya membawa media_id sekaligus message_id. Tanpa to, endpoint ini hanya menyimpan.

curl -X POST https://wau.jasaonline.net/v1/media -H "X-API-Key: jo_live_…" -F "file=@foto.jpg" -F "to=6281234567890" -F "caption=Struk pesanan #1023"
# → 202 { "media_id": "…", "message_id": "…", "status": "queued", "device_id": "…" }
Apakah bagian ini membantu?

Webhook

Daftarkan URL untuk menerima event. secret hanya dikembalikan sekali (saat create / rotate-secret). Event yang tersedia: message.received & message.status.

WhatsAppeventGatewayPOST + tanda tanganEndpoint Anda2xx = suksesbukan 2xx = retry sampai dead

Verifikasi tanda tanganSANGAT DISARANKAN

Setiap kiriman selalu membawa header X-JO-Signature, X-JO-Timestamp, dan X-JO-Event-Id— Anda tidak perlu mengaktifkan apa pun. Memverifikasinya adalah pilihan Anda: endpoint yang melewatinya tetap menerima event seperti biasa. Yang ditanggung kalau melewatinya juga jelas: siapa pun yang mengetahui URL endpoint Anda bisa mengarang "pesan masuk" yang terlihat asli, karena tanda tangan inilah satu-satunya yang membedakan kiriman kami dari kiriman orang lain.

Tanda tangan = HMACHash-based Message Authentication Code — tanda tangan kriptografi untuk memastikan payload berasal dari gateway.-SHA256 atas timestamp + "." + rawBody, memakai secret endpoint:

import crypto from "node:crypto";
// Umur kiriman yang masih diterima. Tanpa batas ini, satu kiriman lama yang
// terekam bisa diputar ulang kapan saja — tanda tangannya tetap sah selamanya.
const MAX_SKEW_SECONDS = 300;
function verify(req, secret) {
const ts = req.headers["x-jo-timestamp"];
const sig = req.headers["x-jo-signature"]; // "sha256=<hex>"
if (!ts || !sig) return false;
if (Math.abs(Date.now() / 1000 - Number(ts)) > MAX_SKEW_SECONDS) return false;
const expected =
"sha256=" +
crypto.createHmac("sha256", secret)
.update(ts + "." + req.rawBody) // rawBody = bytes mentah, bukan JSON re-serialize
.digest("hex");
const a = Buffer.from(sig), b = Buffer.from(expected);
// timingSafeEqual MELEMPAR kalau panjang keduanya beda. Header ngawur harus
// berakhir sebagai "tidak valid", bukan sebagai 500 di server Anda.
return a.length === b.length && crypto.timingSafeEqual(a, b);
}

Bagian yang paling sering salah bukan HMAC-nya, melainkan rawBody: tanda tangan dihitung atas byte yang benar-benar dikirim, sedangkan JSON.stringify(req.body) menghasilkan susunan spasi dan urutan kunci yang bisa berbeda. Simpan byte mentahnya sebelum parser menyentuhnya — contohnya Express, tapi urusannya sama di kerangka mana pun (Spring: byte[] di @RequestBody; Laravel: $request->getContent()):

node · express
app.use("/webhook", express.json({
verify: (req, _res, buf) => { req.rawBody = buf; }, // byte asli, sebelum di-parse
}));
app.post("/webhook", (req, res) => {
if (!verify(req, process.env.JO_WEBHOOK_SECRET)) return res.sendStatus(401);
// Balas dulu, kerjakan belakangan: gateway menunggu balasan Anda, dan
// pekerjaan berat di sini berubah jadi timeout lalu retry.
res.sendStatus(200);
enqueue(req.body);
});

X-JO-Event-Id adalah kunci idempotensi milik peristiwanya, bukan milik percobaan pengiriman. Ia tetap sama pada tiap retry maupun saat kiriman dikirim ulang dari portal — simpan id yang sudah pernah diproses dan abaikan duplikatnya, jangan bertumpu pada "kami hanya mengirim sekali".

Balas 2xx untuk menandai sukses. Jika gagal, gateway mengulang dengan jadwal berikut, lalu berhenti (dead).

0s30s2m10m1h6h24hdead

Circuit breaker melindungi endpoint Anda dari banjir retry. Setiap kali gateway menerima respons bukan 2xx, penghitung kegagalan naik. Setelah 5 kegagalan beruntun, breaker membuka dan semua pengiriman ke endpoint itu dijeda selama ~60 detik — event tetap masuk antrean internal, tapi tidak dikirim. Begitu jeda selesai, breaker masuk ke mode half-open: satu kiriman percobaan dilepas; kalau sukses, breaker closed kembali dan aliran normal pulih. Kalau gagal, breaker membuka lagi selama 60 detik berikutnya.

Closed5× gagalOpen~60 detikHalf-opensuksesgagalClosedOpen

Dari sisi Anda, endpoint sebaiknya selalu membalas 2xxsecepat mungkin (idealnya < 5 detik) dan mengerjakan payload di background. Gateway membaca status dead dari riwayat kiriman di GET /v1/webhooks/{id}/deliveries.

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.

Apakah bagian ini membantu?

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
}
}
Apakah bagian ini membantu?

Kode error

401unauthorizedAPI key hilang / tidak valid / dicabut
403insufficient_scopeAPI key tidak punya scope untuk endpoint ini
403tenant_suspendedTenant ditangguhkan
429rate_limit_exceededBatas laju terlampaui (lihat Retry-After)
400bad_requestBody / parameter tidak valid
400to and type requiredField to dan type wajib di POST /v1/messages
400bad toFormat to salah atau grup tidak valid
400bad idID device/webhook tidak valid
400bad jidFormat JID kontak tidak valid
400bad group jidFormat JID grup tidak valid
400bad chat jidFormat JID chat tidak valid
400bad actionAksi grup tidak dikenali
400bad participant jidFormat JID peserta grup salah
400bad bodyBody endpoint webhook/grup tidak valid
400bad beforeParameter before bukan RFC3339
400bad delivery idID delivery webhook tidak valid
400label requiredLabel device wajib di POST /v1/devices
400url requiredURL webhook wajib di POST /v1/webhooks
400phones requiredParameter phones wajib di GET /v1/contacts/check
400code requiredKode undangan grup wajib
400action and jids requiredAksi dan daftar JID grup wajib
400invalid urlURL media tidak valid
400empty bodyBody request kosong
404device_not_foundDevice tidak ada / bukan milik Anda
404media_not_foundMedia tidak ditemukan
404message_not_foundPesan tidak ditemukan
404webhook_not_foundWebhook tidak ditemukan
404delivery_not_foundDelivery webhook tidak ditemukan
404no_active_pairingTidak ada sesi pairing aktif
409device_not_activeDevice belum ACTIVE
409device_not_readyDevice belum siap untuk presence/media
409no_qrQR belum siap saat ini
503no_eligible_deviceTidak ada device aktif di bawah batas harian
503no_active_deviceTidak ada device aktif saat ini
400no_device_in_groupGrup tidak memiliki device yang bisa mengelolanya
502storage_errorGagal menulis/membaca storage media
502upstream_errorGagal komunikasi ke WhatsApp
500pairing_failedGagal membuka sesi pairing
500qr_encodeGagal membuat gambar QR
500streaming_unsupportedSSE streaming tidak didukung untuk kondisi ini
500internalKesalahan internal gateway
Apakah bagian ini membantu?

FAQ & troubleshooting

Pesan status 'queued' lama sekali, kenapa?

202 Accepted hanya berarti gateway menerima permintaan. Status akhir (sent/delivered/read/failed) datang lewat webhook message.status. Kalau tidak ada webhook, cek device masih ACTIVE dan webhook endpoint membalas 2xx.

Device sudah ACTIVE tapi tidak bisa kirim

Kemungkinan device mencapai daily_cap, atau grup tidak punya device yang bisa mengelolanya (no_device_in_group). Cek response 503 no_eligible_device / no_active_device, atau cek batas harian di portal.

Webhook tidak dipanggil sama sekali

Pastikan: (1) URL bisa diakses publik, (2) endpoint membalas HTTP 2xx, (3) webhook is_active true, (4) circuit breaker tidak sedang open. Riwayat kiriman ada di GET /v1/webhooks/{id}/deliveries.

Tanda tangan webhook tidak cocok

Hampir selalu karena rawBody berbeda dari byte yang dikirim gateway. Jangan pakai JSON.stringify(req.body). Simpan byte mentah sebelum parser JSON. Lihat contoh verifikasi di bagian Webhook.

Bisa membatasi API key per IP?

Saat ini tidak. API key berlaku global untuk tenant. Anda bisa membuat beberapa key dan mencabutnya per key dari portal jika diperlukan rotasi.

Bagaimana migrasi dari sandbox ke production?

Sandbox dan production adalah tenant terpisah. Device dan API key sandbox tidak otomatis pindah. Setelah testing, buat device & API key baru di production, lalu ubah base URL client Anda.

Apakah bagian ini membantu?

Changelog

v1.0.02026-09-20
  • Rilis stabil REST API WhatsApp Unofficial Gateway.
  • Endpoint perangkat, pesan, media, webhook, grup, chat, kontak, audit, dan utilitas.
  • Autentikasi X-API-Key dengan hash argon2id.
  • Webhook dengan tanda tangan HMAC-SHA256, retry schedule, dan circuit breaker.
  • Idempotensi via Idempotency-Key header.
Apakah bagian ini membantu?

Siap mulai?

Buat API key di portal, lalu pindai QR untuk menautkan nomor.

Ada saran atau menemukan kesalahan docs? Hubungi support.