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" }

secret hanya muncul di respons ini. GET /v1/webhooks tidak pernah mengembalikannya lagi. Simpan sekarang — tanpa secret kamu tidak bisa memverifikasi bahwa event benar dari kami.

Event yang dikirim

EventIsi
message.receivedPesan masuk dari pelanggan: device_uuid, from, type, body, wa_message_id, timestamp
message.statusPerubahan status pesan keluar: sent / delivered / read / failed
device.statusprovisioned / 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 jam kalau endpoint-mu tidak membalas 2xx.
  • 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.