B BGWA Docs
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 https dengan port 443 atau 8443.
  • 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 422 beserta penjelasannya.

Endpoint pengelolaan

MetodePathFungsi
GET/webhookLihat URL, status secret, dan 10 pengiriman terakhir.
PUT/webhookAtur URL (body: {"url": "..."}).
DELETE/webhookHapus URL dan secret.
POST/webhook/secretBuat secret baru. Secret lama langsung tidak berlaku.
POST/webhook/testKirim event ping uji. Jeda minimal 10 detik antar uji.

Event

EventKapan dikirim
message.sentPesan diterima server WhatsApp. Belum berarti dibaca penerima.
message.failedPesan gagal dan tidak dicoba lagi (mis. send_failed, not_on_whatsapp, expired).
message.cancelledPesan dibatalkan (mis. recipient_opted_out, account_inactive).
pingUji 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...
HeaderKeterangan
X-BGWA-EventNama event.
X-BGWA-DeliveryID unik pengiriman. Sama untuk semua percobaan ulang, pakai untuk membuang duplikat.
X-BGWA-TimestampWaktu (detik Unix) saat dikirim. Dipakai dalam tanda tangan.
X-BGWA-Signaturesha256= 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_id dan 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.