Panduan
Webhook
Webhook adalah cara sistemmu tahu ada pesan masuk atau status pengiriman berubah — tanpa polling. Kami POST ke URL-mu, kamu balas 200 secepatnya.
Mendaftarkan endpoint
POST https://wardia.id/v1/webhooks
Authorization: Bearer ardw_live_xxx
{ "url": "https://tokomu.example.com/wa-webhook",
"events": ["message.received", "message.status", "device.status"] }
→ 201 { "id": 7, "url": "...", "events": [...], "secret": "48-karakter-acak" }
secrethanya muncul di respons ini.GET /v1/webhookstidak pernah mengembalikannya lagi. Simpan sekarang — tanpa secret kamu tidak bisa memverifikasi bahwa event benar dari kami.
Event yang dikirim
| Event | Isi |
|---|---|
message.received | Pesan masuk dari pelanggan: device_uuid, from, type, body, wa_message_id, timestamp |
message.status | Perubahan status pesan keluar: sent / delivered / read / failed |
device.status | provisioned / qr_pending / connected / disconnected / banned |
QR pairing tidak pernah dikirim lewat webhook — ambil lewat GET /v1/devices/{uuid}/qr.
Verifikasi tanda tangan
Tiap request kami membawa header X-Ardia-Signature berisi HMAC-SHA256 atas raw body memakai secret-mu. Verifikasi sebelum memproses apa pun:
// Laravel
$raw = $request->getContent();
$expected = 'sha256=' . hash_hmac('sha256', $raw, config('services.wardia.webhook_secret'));
if (! hash_equals($expected, (string) $request->header('X-Ardia-Signature'))) {
abort(401);
}
Hitung HMAC dari body mentah, bukan dari hasil json_decode lalu di-encode ulang — urutan key bisa berubah dan tanda tangan jadi tidak cocok.
Node/Express:
const expected = 'sha256=' + crypto
.createHmac('sha256', process.env.WARDIA_WEBHOOK_SECRET)
.update(req.rawBody)
.digest('hex');
if (!crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(req.get('X-Ardia-Signature') || ''))) {
return res.sendStatus(401);
}
Contoh handler lengkap (Laravel)
Route::post('/wa-webhook', function (Request $request) {
$raw = $request->getContent();
$expected = 'sha256=' . hash_hmac('sha256', $raw, config('services.wardia.webhook_secret'));
abort_unless(hash_equals($expected, (string) $request->header('X-Ardia-Signature')), 401);
$payload = $request->json()->all();
if (($payload['event'] ?? null) === 'message.received') {
// Dedup: WAJIB — event yang sama bisa datang lebih dari sekali.
$isNew = WaEvent::firstOrCreate(['wa_message_id' => $payload['wa_message_id']])->wasRecentlyCreated;
if ($isNew) {
HandleIncomingWa::dispatch($payload); // proses di queue, bukan di sini
}
}
return response()->noContent(); // 204/200 secepatnya
});
Keandalan: retry, dedup, dan auto-disable
- Retry backoff
30 detik → 2 menit → 10 menit → 1 jam → 6 jamkalau endpoint-mu tidak membalas2xx. - At-least-once, bukan exactly-once — payload yang sama bisa tiba dua kali (mis. kamu balas 200 tapi koneksi putus sebelum sampai ke kami). Dedup di sisimu pakai
wa_message_id. Di sisi kami pesan masuk sudah didedup di level device+wa_message_id+arah. - Balas cepat, proses di queue. Handler yang lambat memperbesar peluang timeout, yang berarti kamu akan menerima event itu lagi.
- Auto-disable — setelah 10 kegagalan total, webhook di-nonaktifkan dan harus didaftarkan ulang. Pantau endpoint-mu.
Kalau tujuanmu cuma "chat terjawab otomatis"
Kamu tidak perlu webhook sama sekali. Auto-reply dan CS AI berjalan di sisi kami — lihat Balas otomatis & CS AI. Webhook dipakai kalau kamu ingin sistemmu sendiri yang bereaksi terhadap pesan masuk.