Universal WhatsApp Gateway

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

  1. Daftar di wa.hoose.id/daftar (ada masa coba gratis).
  2. Sambungkan nomor: menu Perangkat → tambah perangkat → scan kode QR dari WhatsApp di HP (Perangkat tertaut → Tautkan perangkat).
  3. 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:

cURL
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.

Request
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 }
  }'
Respons 202
{ "success": true, "data": { "messageId": "cmu...", "status": "queued", "externalId": "ORDER-1021", "idempotent": false } }
FieldKeterangan
recipient.phoneNomor tujuan. Boleh 08…, 62…, atau +62…; spasi dan strip diabaikan.
recipient.groupIdID grup (…@g.us), lihat Kirim ke grup. Isi tepat satu: phone atau groupId.
recipient.nameOpsional, untuk menyimpan/memperbarui nama kontak.
message.typetext, atau media untuk berkas yang sudah diunggah.
message.text1–4096 karakter.
deviceIdOpsional: nomor pengirim lain milik akun Anda. Bawaan: nomor yang dipilih untuk aplikasi.
scheduledAtOpsional, waktu kirim ISO 8601 (maksimal 1 tahun ke depan).
externalId, sourceOpsional (≤128 karakter), untuk mencocokkan dengan data di sistem Anda.
metadataOpsional, 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.pdf

Unggah 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 mengirim

Kontak & opt-out

Kontak tersimpan per aplikasi. Kontak yang opt-out tidak bisa dikirimi pesan (403 CONTACT_OPTED_OUT).

MethodPathKeterangan
GET/contacts?search=&optedOut=Daftar kontak
POST/contactsBuat kontak: name, phone, metadata
PUT/contacts/:idUbah kontak
DELETE/contacts/:idHapus kontak
POST/contacts/:id/opt-outTandai berhenti menerima pesan
POST/contacts/:id/opt-inIzinkan 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
EventKapan
message.receivedAda pesan masuk (chat pribadi atau grup)
message.queued / message.sent / message.failedStatus pesan yang dikirim aplikasi Anda berubah
message.deletedPesan dihapus untuk semua orang (lewat dashboard, API, atau dari HP)
gateway.connected / gateway.disconnectedNomor WhatsApp tersambung atau terputus
gateway.qr.updatedKode QR baru tersedia untuk disambungkan ulang
Contoh delivery
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.

Node.js (Express)
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);
});
PHP
$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": [ ... ] } }
HTTPKodeArti
400VALIDATION_ERRORBody atau query tidak valid (detail per field)
400INVALID_PHONE_NUMBERNomor tidak bisa dibaca
401INVALID_API_KEYHeader API key atau ID aplikasi hilang/salah
403FORBIDDENAplikasi nonaktif atau izin key kurang
403CONTACT_OPTED_OUTPenerima memilih berhenti menerima pesan
404NOT_FOUNDData tidak ada atau bukan milik aplikasi Anda
409MESSAGE_TOO_OLDPesan terlalu lama untuk dihapus untuk semua orang (±60 jam)
429RATE_LIMIT_EXCEEDEDTerlalu banyak request per menit
429DAILY_LIMIT_EXCEEDEDBatas pesan harian aplikasi tercapai
503WHATSAPP_NOT_CONNECTEDNomor pengirim belum tersambung

Setiap respons membawa header x-request-id; sertakan saat menghubungi kami.

Batas pemakaian

Contoh kode: Node.js, PHP, Python

Node.js 18+
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());
PHP
$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);
Python
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.

Belum punya akun? Daftar dan coba gratis, sambungkan nomor, lalu buat API key dalam beberapa menit.