Dokumentasi · REST API v1
Dokumentasi API WhatsApp Gateway (Unofficial)
REST API Universal WhatsApp Gateway untuk mengirim pesan WhatsApp dari aplikasi Anda dan menerima chat masuk lewat webhook. Nomor WhatsApp biasa disambungkan dengan scan QR; tidak perlu WhatsApp Business API resmi. Belum tahu bedanya? Baca apa itu unofficial WhatsApp gateway.
Mulai dalam 3 langkah
- Daftar di wa.hoose.id/daftar (ada masa coba gratis).
- Sambungkan nomor: menu Perangkat → tambah perangkat → scan kode QR dari WhatsApp di HP (Perangkat tertaut → Tautkan perangkat).
- Buat aplikasi: menu Integrasi API → Aplikasi → buat aplikasi dan pilih nomor pengirimnya. Salin API key (hanya ditampilkan sekali) dan ID aplikasi.
Lalu coba kirim pesan pertama:
export WA_API_KEY="uwg_..." # API key dari menu Integrasi API
export WA_APP_ID="..." # ID aplikasi
curl -X POST https://wa.hoose.id/api/v1/messages/send -H "x-api-key: $WA_API_KEY" -H "x-application-id: $WA_APP_ID" \
-H "content-type: application/json" \
-d '{"recipient":{"phone":"081234567890"},"message":{"type":"text","text":"Halo dari API!"}}'Base URL & autentikasi
Base URL: https://wa.hoose.id/api/v1. Request dan respons memakai JSON (kecuali unggah berkas), ukuran body maksimal 1 MB.
Setiap request dari aplikasi wajib membawa dua header:
x-api-key: uwg_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
x-application-id: <ID aplikasi>Data yang bisa dibaca sebuah key hanya milik aplikasinya sendiri. Izin bawaan key: messages:send,
messages:read, contacts:manage, webhooks:manage. Key yang bocor bisa diganti dari menu
Integrasi API (rotate): key lama langsung tidak berlaku.
Kirim pesan teks
POST /messages/send memasukkan pesan ke antrean dan langsung menjawab 202. Pesan dikirim dalam
beberapa detik, bergiliran per nomor pengirim.
curl -X POST https://wa.hoose.id/api/v1/messages/send -H "x-api-key: $WA_API_KEY" -H "x-application-id: $WA_APP_ID" \
-H "content-type: application/json" \
-d '{
"recipient": { "phone": "081234567890", "name": "Budi" },
"message": { "type": "text", "text": "Halo Budi, pesanan #1021 sudah dikirim." },
"externalId": "ORDER-1021",
"metadata": { "orderId": 1021 }
}'{ "success": true, "data": { "messageId": "cmu...", "status": "queued", "externalId": "ORDER-1021", "idempotent": false } }| Field | Keterangan |
|---|---|
recipient.phone | Nomor tujuan. Boleh 08…, 62…, atau +62…; spasi dan strip diabaikan. |
recipient.groupId | ID grup (…@g.us), lihat Kirim ke grup. Isi tepat satu: phone atau groupId. |
recipient.name | Opsional, untuk menyimpan/memperbarui nama kontak. |
message.type | text, atau media untuk berkas yang sudah diunggah. |
message.text | 1–4096 karakter. |
deviceId | Opsional: nomor pengirim lain milik akun Anda. Bawaan: nomor yang dipilih untuk aplikasi. |
scheduledAt | Opsional, waktu kirim ISO 8601 (maksimal 1 tahun ke depan). |
externalId, source | Opsional (≤128 karakter), untuk mencocokkan dengan data di sistem Anda. |
metadata | Opsional, objek JSON bebas ≤4 KB. Ikut dikirim balik di webhook. |
Bila nomor tujuan tidak terdaftar di WhatsApp, pesan berakhir failed dengan errorMessage diawali
NUMBER_NOT_ON_WHATSAPP.
Kirim ke grup
Grup dialamatkan dengan ID grup, bukan nomor. ID grup muncul sebagai chatId (berakhiran @g.us)
pada webhook message.received saat ada pesan dari grup tersebut. Grup yang hanya boleh diisi admin ditolak
403 GROUP_ADMIN_ONLY bila nomor pengirim bukan admin.
curl -X POST https://wa.hoose.id/api/v1/messages/send -H "x-api-key: $WA_API_KEY" -H "x-application-id: $WA_APP_ID" \
-H "content-type: application/json" \
-d '{"recipient":{"groupId":"120363111122223333@g.us"},"message":{"type":"text","text":"Rapat jam 3 ya."}}'Kirim foto & berkas
Jenis berkas mengikuti tipe MIME: image/* terkirim sebagai foto, video/* sebagai video,
audio/* sebagai audio, selain itu sebagai dokumen. Bawaan maksimal 64 MB.
Sekali jalan: POST /messages/send-file
Body multipart/form-data dengan field file, phone atau groupId, dan opsional
caption, name, deviceId, scheduledAt, externalId.
curl -X POST https://wa.hoose.id/api/v1/messages/send-file -H "x-api-key: $WA_API_KEY" -H "x-application-id: $WA_APP_ID" \
-F phone=081234567890 -F caption="Invoice Oktober" -F file=@invoice.pdfUnggah sekali, kirim berkali-kali
Unggah lewat POST /media (field file) → dapat id, lalu kirim ke banyak nomor dengan
message.type: "media". Cocok untuk brosur atau katalog.
curl -X POST https://wa.hoose.id/api/v1/media -H "x-api-key: $WA_API_KEY" -H "x-application-id: $WA_APP_ID" -F file=@brosur.jpg
# → { "success": true, "data": { "id": "cmv...", "kind": "image", ... } }
curl -X POST https://wa.hoose.id/api/v1/messages/send -H "x-api-key: $WA_API_KEY" -H "x-application-id: $WA_APP_ID" \
-H "content-type: application/json" \
-d '{"recipient":{"phone":"081234567890"},"message":{"type":"media","mediaFileId":"cmv...","caption":"Promo minggu ini"}}'Pesan terjadwal & anti-dobel
Isi scheduledAt untuk mengirim nanti; pesan tetap diterima walau nomor sedang terputus dan dikirim saat
waktunya tiba. Tanpa scheduledAt, pesan dikirim secepatnya mengikuti jeda pengiriman akun Anda.
Supaya pesan tidak terkirim dua kali saat request diulang (misalnya karena timeout), kirim header
Idempotency-Key. Request ulang dengan key yang sama mengembalikan pesan lama ("idempotent": true)
tanpa membuat pesan baru.
curl -X POST https://wa.hoose.id/api/v1/messages/send -H "x-api-key: $WA_API_KEY" -H "x-application-id: $WA_APP_ID" \
-H "content-type: application/json" -H "Idempotency-Key: order-1021-shipped" \
-d '{"recipient":{"phone":"081234567890"},"message":{"type":"text","text":"Besok jam 9 kurir datang."},"scheduledAt":"2026-11-04T08:00:00+07:00"}'Cek status pesan
Urutan status pesan keluar: queued → sending → sent → delivered →
read, atau failed. Pesan masuk berstatus received/processed.
# Satu pesan
curl https://wa.hoose.id/api/v1/messages/cmu... -H "x-api-key: $WA_API_KEY" -H "x-application-id: $WA_APP_ID"
# Daftar, dengan filter
curl "https://wa.hoose.id/api/v1/messages?direction=outbound&status=failed&from=2026-11-01&pageSize=50" -H "x-api-key: $WA_API_KEY" -H "x-application-id: $WA_APP_ID"
curl "https://wa.hoose.id/api/v1/messages?externalId=ORDER-1021" -H "x-api-key: $WA_API_KEY" -H "x-application-id: $WA_APP_ID"Filter yang tersedia: page, pageSize (≤200), direction (inbound/outbound),
status, phone, externalId, source, search, from, to.
Hapus pesan
Hapus pesan yang dikirim aplikasi Anda dengan DELETE /messages/:id (izin messages:send). Pesan yang
masih queued dibatalkan sebelum terkirim; pesan yang sudah sent/delivered/read
dihapus untuk semua orang di WhatsApp. Respons berisi action: cancelled, deleted, atau
already_deleted (aman dipanggil ulang).
curl -X DELETE https://wa.hoose.id/api/v1/messages/cmu... -H "x-api-key: $WA_API_KEY" -H "x-application-id: $WA_APP_ID"WhatsApp hanya mengizinkan hapus untuk semua orang sekitar 60 jam setelah pesan terkirim (lewat dari itu:
409 MESSAGE_TOO_OLD), dan nomor pengirim harus sedang tersambung. Pesan bisa juga dihapus dari dashboard: menu
Chat (ikon tempat sampah di samping gelembung) atau detail pesan di menu Messages.
Status nomor WhatsApp
curl https://wa.hoose.id/api/v1/whatsapp/status -H "x-api-key: $WA_API_KEY" -H "x-application-id: $WA_APP_ID"
# → { "success": true, "data": { "deviceId": "...", "status": "connected", "phone": "628...", ... } }
curl https://wa.hoose.id/api/v1/whatsapp/devices -H "x-api-key: $WA_API_KEY" -H "x-application-id: $WA_APP_ID" # nomor yang boleh dipakai mengirimKontak & opt-out
Kontak tersimpan per aplikasi. Kontak yang opt-out tidak bisa dikirimi pesan (403 CONTACT_OPTED_OUT).
| Method | Path | Keterangan |
|---|---|---|
| GET | /contacts?search=&optedOut= | Daftar kontak |
| POST | /contacts | Buat kontak: name, phone, metadata |
| PUT | /contacts/:id | Ubah kontak |
| DELETE | /contacts/:id | Hapus kontak |
| POST | /contacts/:id/opt-out | Tandai berhenti menerima pesan |
| POST | /contacts/:id/opt-in | Izinkan lagi |
Webhook: pesan masuk & status
Webhook mengirim kejadian ke URL Anda secara real time. Buat dari menu Integrasi API → Webhook atau lewat API (maksimal 5 per aplikasi):
curl -X POST https://wa.hoose.id/api/v1/webhooks -H "x-api-key: $WA_API_KEY" -H "x-application-id: $WA_APP_ID" \
-H "content-type: application/json" \
-d '{"url":"https://contoh.com/webhook/whatsapp","events":["message.received","message.sent","message.failed"]}'
# → secret ditampilkan sekali; simpan untuk memeriksa tanda tangan| Event | Kapan |
|---|---|
message.received | Ada pesan masuk (chat pribadi atau grup) |
message.queued / message.sent / message.failed | Status pesan yang dikirim aplikasi Anda berubah |
message.deleted | Pesan dihapus untuk semua orang (lewat dashboard, API, atau dari HP) |
gateway.connected / gateway.disconnected | Nomor WhatsApp tersambung atau terputus |
gateway.qr.updated | Kode QR baru tersedia untuk disambungkan ulang |
POST https://contoh.com/webhook/whatsapp
content-type: application/json
x-gateway-event: message.received
x-gateway-signature: sha256=<HMAC-SHA256(secret, body mentah)>
x-gateway-delivery-id: 2f6d9b1e-...
{
"event": "message.received",
"deliveryId": "2f6d9b1e-...",
"timestamp": "2026-11-03T12:00:00.000Z",
"data": {
"messageId": "cmu...", "direction": "inbound", "phone": "628123456789",
"chatId": "628123456789@s.whatsapp.net", "senderName": "Budi", "isGroup": false,
"type": "text", "text": "Halo, pesanan saya sampai mana?", "status": "received"
}
}Balas dengan status 2xx dalam 10 detik. Bila gagal, delivery diulang dengan jeda 1 s, 4 s, 16 s.
Delivery yang sama bisa datang lebih dari sekali, jadi pakai deliveryId untuk mencegah dobel.
Memeriksa tanda tangan
Hitung HMAC-SHA256 dari body mentah (jangan di-parse lalu di-stringify ulang) dengan secret
webhook, lalu bandingkan dengan header x-gateway-signature.
const crypto = require('node:crypto');
const express = require('express');
const app = express();
app.post('/webhook/whatsapp', express.raw({ type: 'application/json' }), (req, res) => {
const expected = 'sha256=' + crypto.createHmac('sha256', process.env.WEBHOOK_SECRET).update(req.body).digest('hex');
const provided = req.header('x-gateway-signature') || '';
const valid = expected.length === provided.length && crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(provided));
if (!valid) return res.sendStatus(401);
const payload = JSON.parse(req.body.toString('utf8'));
if (payload.event === 'message.received') {
console.log(payload.data.phone, payload.data.text);
}
res.sendStatus(200);
});$raw = file_get_contents('php://input');
$expected = 'sha256=' . hash_hmac('sha256', $raw, getenv('WEBHOOK_SECRET'));
if (!hash_equals($expected, $_SERVER['HTTP_X_GATEWAY_SIGNATURE'] ?? '')) { http_response_code(401); exit; }
$event = json_decode($raw, true);
http_response_code(200);Format respons & kode error
{ "success": true, "data": { ... } }
{ "success": true, "data": [ ... ], "meta": { "pagination": { "page": 1, "pageSize": 25, "total": 120, "totalPages": 5 } } }
{ "success": false, "error": { "code": "VALIDATION_ERROR", "message": "Invalid request body", "details": [ ... ] } }| HTTP | Kode | Arti |
|---|---|---|
| 400 | VALIDATION_ERROR | Body atau query tidak valid (detail per field) |
| 400 | INVALID_PHONE_NUMBER | Nomor tidak bisa dibaca |
| 401 | INVALID_API_KEY | Header API key atau ID aplikasi hilang/salah |
| 403 | FORBIDDEN | Aplikasi nonaktif atau izin key kurang |
| 403 | CONTACT_OPTED_OUT | Penerima memilih berhenti menerima pesan |
| 404 | NOT_FOUND | Data tidak ada atau bukan milik aplikasi Anda |
| 409 | MESSAGE_TOO_OLD | Pesan terlalu lama untuk dihapus untuk semua orang (±60 jam) |
| 429 | RATE_LIMIT_EXCEEDED | Terlalu banyak request per menit |
| 429 | DAILY_LIMIT_EXCEEDED | Batas pesan harian aplikasi tercapai |
| 503 | WHATSAPP_NOT_CONNECTED | Nomor pengirim belum tersambung |
Setiap respons membawa header x-request-id; sertakan saat menghubungi kami.
Batas pemakaian
- Per aplikasi: bawaan 20 request kirim per menit dan 1.000 pesan per hari; bisa diubah di pengaturan aplikasi.
Respons
429menyertakandetails.retryAfterSeconds. - Kuota pesan dan jumlah nomor mengikuti paket langganan.
- Pesan dikirim bergiliran per nomor; jeda tambahan antar pesan API bisa diatur di dashboard supaya nomor lebih aman dari blokir.
Contoh kode: Node.js, PHP, Python
const res = await fetch('https://wa.hoose.id/api/v1/messages/send', {
method: 'POST',
headers: {
'x-api-key': process.env.WA_API_KEY,
'x-application-id': process.env.WA_APP_ID,
'content-type': 'application/json',
},
body: JSON.stringify({
recipient: { phone: '081234567890' },
message: { type: 'text', text: 'Halo dari Node.js' },
}),
});
console.log(res.status, await res.json());$ch = curl_init('https://wa.hoose.id/api/v1/messages/send');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'x-api-key: ' . getenv('WA_API_KEY'),
'x-application-id: ' . getenv('WA_APP_ID'),
'content-type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'recipient' => ['phone' => '081234567890'],
'message' => ['type' => 'text', 'text' => 'Halo dari PHP'],
]),
]);
echo curl_exec($ch);import os, requests
res = requests.post(
'https://wa.hoose.id/api/v1/messages/send',
headers={'x-api-key': os.environ['WA_API_KEY'], 'x-application-id': os.environ['WA_APP_ID']},
json={'recipient': {'phone': '081234567890'}, 'message': {'type': 'text', 'text': 'Halo dari Python'}},
)
print(res.status_code, res.json())Tanpa ngoding: n8n
Tanpa ngoding, pakai node HTTP Request di n8n: method POST, URL
https://wa.hoose.id/api/v1/messages/send, header x-api-key dan x-application-id, body JSON seperti contoh di
atas. Untuk menerima chat masuk, pasang node Webhook di n8n lalu daftarkan URL-nya sebagai webhook
message.received. Contoh alur siap pakai ada di menu Panduan setelah masuk.