Referensi API
Webhook
Terima hasil akhir pengiriman di server Anda: event, header, tanda tangan HMAC, dan pengulangan.
Karena pesan dikirim bertahap, hasil akhirnya baru diketahui beberapa saat setelah request. Dengan webhook, BGWA yang menghubungi server Anda saat pesan sudah terkirim, gagal, atau dibatalkan, sehingga Anda tidak perlu memeriksa status satu per satu.
Mengatur webhook
PUT https://bgwa.web.id/api/v1/webhook
cURL
curl -X PUT https://bgwa.web.id/api/v1/webhook \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"url": "https://situs-anda.com/hooks/bgwa"}'
200 OK (pertama kali)
{
"ok": true,
"url": "https://situs-anda.com/hooks/bgwa",
"secret_set": true,
"secret": "whsec_...",
"note": "Simpan secret ini sekarang; tidak akan ditampilkan lagi. ..."
}
Syarat URL
- Wajib
httpsdengan port443atau8443. - Domain publik. Alamat internal/privat (localhost, 192.168.x.x, dan sejenisnya) ditolak.
- Redirect tidak diikuti, jadi daftarkan URL akhirnya.
- URL yang tidak memenuhi syarat dijawab
422beserta penjelasannya.
Endpoint pengelolaan
| Metode | Path | Fungsi |
|---|---|---|
| GET | /webhook | Lihat URL, status secret, dan 10 pengiriman terakhir. |
| PUT | /webhook | Atur URL (body: {"url": "..."}). |
| DELETE | /webhook | Hapus URL dan secret. |
| POST | /webhook/secret | Buat secret baru. Secret lama langsung tidak berlaku. |
| POST | /webhook/test | Kirim event ping uji. Jeda minimal 10 detik antar uji. |
Event
| Event | Kapan dikirim |
|---|---|
message.sent | Pesan diterima server WhatsApp. Belum berarti dibaca penerima. |
message.failed | Pesan gagal dan tidak dicoba lagi (mis. send_failed, not_on_whatsapp, expired). |
message.cancelled | Pesan dibatalkan (mis. recipient_opted_out, account_inactive). |
ping | Uji koneksi dari POST /webhook/test. |
Bentuk permintaan
BGWA mengirim POST dengan body JSON dan header berikut:
Header
POST /hooks/bgwa HTTP/1.1
Content-Type: application/json
X-BGWA-Event: message.sent
X-BGWA-Delivery: 7d1f0c0e-1a2b-4c3d-8e9f-0a1b2c3d4e5f
X-BGWA-Timestamp: 1793626030
X-BGWA-Signature: sha256=3b7f...
| Header | Keterangan |
|---|---|
X-BGWA-Event | Nama event. |
X-BGWA-Delivery | ID unik pengiriman. Sama untuk semua percobaan ulang, pakai untuk membuang duplikat. |
X-BGWA-Timestamp | Waktu (detik Unix) saat dikirim. Dipakai dalam tanda tangan. |
X-BGWA-Signature | sha256= diikuti HMAC-SHA256 hex. |
Body
{
"event": "message.sent",
"id": 123,
"status": "sent",
"to": "6281234567890",
"wa_message_id": "3EB0A1B2C3D4",
"reason": null,
"error": null,
"attempts": 1,
"queued_at": "2026-11-03T03:00:00+00:00",
"sent_at": "2026-11-03T03:00:26+00:00",
"device": { "id": 4, "name": "toko-saya" },
"delivery_id": "7d1f0c0e-1a2b-4c3d-8e9f-0a1b2c3d4e5f"
}
Verifikasi tanda tangan
Selalu verifikasi tanda tangan sebelum memercayai isi webhook. Tanda tangan adalah
HMAC-SHA256(secret, timestamp + "." + body_mentah) dalam heksadesimal, diawali sha256=.
PHP
$secret = getenv('BGWA_WEBHOOK_SECRET');
$ts = $_SERVER['HTTP_X_BGWA_TIMESTAMP'] ?? '';
$body = file_get_contents('php://input');
$sig = 'sha256=' . hash_hmac('sha256', $ts . '.' . $body, $secret);
if (! hash_equals($sig, $_SERVER['HTTP_X_BGWA_SIGNATURE'] ?? '')) {
http_response_code(401);
exit;
}
if (abs(time() - (int) $ts) > 300) { // tolak bila lebih dari 5 menit
http_response_code(400);
exit;
}
$event = json_decode($body, true);
// proses $event['event'], $event['id'] ... lalu:
http_response_code(200);
Node.js (Express)
import express from 'express';
import crypto from 'node:crypto';
const app = express();
// simpan body mentah untuk verifikasi
app.post('/hooks/bgwa', express.raw({ type: 'application/json' }), (req, res) => {
const ts = req.get('X-BGWA-Timestamp') || '';
const expected = 'sha256=' + crypto
.createHmac('sha256', process.env.BGWA_WEBHOOK_SECRET)
.update(ts + '.' + req.body.toString('utf8'))
.digest('hex');
const got = req.get('X-BGWA-Signature') || '';
const ok = got.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(got), Buffer.from(expected));
if (!ok) return res.sendStatus(401);
if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) return res.sendStatus(400);
const event = JSON.parse(req.body.toString('utf8'));
// proses event.event, event.id ...
res.sendStatus(200);
});
Python (Flask)
import hmac, hashlib, os, time
from flask import Flask, request, abort
app = Flask(__name__)
@app.post("/hooks/bgwa")
def bgwa_hook():
ts = request.headers.get("X-BGWA-Timestamp", "")
body = request.get_data() # byte mentah
expected = "sha256=" + hmac.new(
os.environ["BGWA_WEBHOOK_SECRET"].encode(),
ts.encode() + b"." + body,
hashlib.sha256,
).hexdigest()
if not hmac.compare_digest(expected, request.headers.get("X-BGWA-Signature", "")):
abort(401)
if abs(time.time() - int(ts or 0)) > 300:
abort(400)
event = request.get_json()
# proses event["event"], event["id"] ...
return "", 200
Pengulangan dan idempotensi
- Balas 2xx secepatnya. Proses berat dikerjakan di belakang layar (queue) setelah menjawab.
- Gagal dicoba ulang. Jika jawaban bukan 2xx atau koneksi gagal, BGWA mengulang hingga 7 kali dengan jeda 30 detik, 2 menit, 10 menit, 30 menit, 2 jam, lalu 6 jam.
- Bisa terkirim lebih dari sekali. Simpan
delivery_iddan abaikan yang sudah diproses. - Urutan tidak dijamin. Jangan mengandalkan urutan kedatangan antar pesan.
- Tolak timestamp lama (mis. lebih dari 5 menit) untuk mencegah pemutaran ulang.
Menguji webhook
cURL
# 1. kirim ping uji
curl -X POST https://bgwa.web.id/api/v1/webhook/test \
-H "Authorization: Bearer YOUR_API_KEY"
# 2. beberapa detik kemudian, lihat hasilnya
curl https://bgwa.web.id/api/v1/webhook \
-H "Authorization: Bearer YOUR_API_KEY"
GET /webhook
{
"url": "https://situs-anda.com/hooks/bgwa",
"secret_set": true,
"deliveries": [
{
"delivery_id": "7d1f0c0e-1a2b-4c3d-8e9f-0a1b2c3d4e5f",
"event": "ping",
"status": "delivered",
"attempts": 1,
"last_status": 200,
"last_error": null,
"created_at": "2026-11-03T03:00:00+00:00",
"delivered_at": "2026-11-03T03:00:02+00:00"
}
]
}
Gunakan last_status dan last_error untuk mencari tahu mengapa sebuah pengiriman gagal.