Webhook
TagihanDigital mengirim notifikasi POST ke URL Anda begitu ada peristiwa pembayaran, supaya sistem Anda tidak perlu menanyai API terus-menerus.
Memasang URL webhook
Masuk ke Dashboard, buka Integrasi API, lalu pilih tab Webhook. Isi URL produksi Anda — harus HTTPS dan bisa diakses publik — lalu tekan Kirim tes untuk memastikan endpoint Anda menerima dan membalas dengan benar sebelum dipakai untuk trafik sungguhan.
Event yang dikirim
| Event | status_code | transaction_status | gross_amount |
|---|---|---|---|
| invoice.paid | 200 | settlement | Total invoice |
| payment.received | 201 | partial | Nominal pembayaran itu |
| payment_link.expired | 202 | expire | Nominal link |
Contoh payload
Berikut bentuk lengkap yang benar-benar dikirim untuk event invoice.paid. Field external_id berisi ID transaksi milik Anda sendiri, persis seperti yang dikirim saat membuat invoice.
{
"event": "invoice.paid",
"delivery_id": "whd_3k2j8f",
"sent_at": "2026-09-16T12:03:11.000Z",
"test": false,
"invoice_id": "clx9a",
"invoice_number": "INV-2026-0012",
"external_id": "ORD-2026-0912",
"status_code": "200",
"transaction_status": "settlement",
"gross_amount": "150000.00",
"amount_paid": "150000.00",
"balance_due": "0.00",
"payment_status": "PAID",
"customer": { "id": "cus_123", "name": "PT Maju", "email": "finance@maju.test" },
"payment": {
"id": "pay_1",
"amount": "150000.00",
"method": "DUITKU_VA",
"reference": "D1871800123",
"paid_at": "2026-09-16T12:03:09.000Z"
},
"signature_key": "a1f4..."
}Verifikasi signature
Setiap kiriman membawa signature_key yang dihitung dari tiga field payload ditambah Server Key organisasi Anda. Hitung ulang nilainya di sisi Anda dan bandingkan sebelum memproses kiriman:
signature_key = sha512(invoice_number + status_code + gross_amount + ServerKey)Hasilnya berupa hex huruf kecil. gross_amount selalu memakai dua desimal (misalnya 150000.00, bukan 150000) — salin nilainya dari payload apa adanya, jangan diformat ulang, karena string yang berbeda menghasilkan signature yang berbeda pula.
Verifikasi signature (PHP):
$body = json_decode(file_get_contents('php://input'), true);
$expected = hash('sha512',
$body['invoice_number'] . $body['status_code'] . $body['gross_amount'] . $serverKey
);
if (!hash_equals($expected, $body['signature_key'])) {
http_response_code(403);
exit;
}
http_response_code(200);Verifikasi signature (Node.js):
import { createHash, timingSafeEqual } from "node:crypto";
app.post("/webhook/tagihandigital", (req, res) => {
const b = req.body;
const expected = createHash("sha512")
.update(b.invoice_number + b.status_code + b.gross_amount + SERVER_KEY)
.digest("hex");
const ok = timingSafeEqual(Buffer.from(expected), Buffer.from(b.signature_key));
if (!ok) return res.sendStatus(403);
// delivery_id konstan di seluruh percobaan ulang: simpan dan tolak duplikat.
res.sendStatus(200);
});Header tiap kiriman
Setiap request webhook membawa header berikut:
X-TD-Event— nama event, misalnyainvoice.paid.X-TD-Delivery-Id— sama dengandelivery_idpada payload.X-TD-Test—trueuntuk kiriman tes dari Dashboard,falseuntuk kiriman sungguhan.User-Agent: TagihanDigital-Webhook/1
Jadwal retry
Percobaan pertama dikirim langsung saat peristiwa terjadi. Kalau gagal — respons bukan 2xx, timeout, atau koneksi putus — dicoba ulang dengan jeda +1 menit, +5 menit, +30 menit, +2 jam, lalu +6 jam. Setelah percobaan keenam tetap gagal, statusnya menjadi FAILED dan pengiriman berhenti — Anda bisa mengirim ulang secara manual dari Dashboard.
Anjuran menangani webhook
Balas 200 secepatnya begitu payload diterima dan signature-nya valid, lalu proses isinya (update status, kirim notifikasi, dan seterusnya) di latar belakang — jangan menahan respons sampai proses panjang selesai, supaya tidak dianggap timeout dan dikirim ulang tanpa perlu. Pakai delivery_id sebagai kunci idempotensi: nilainya sama di seluruh percobaan ulang untuk peristiwa yang sama, jadi Anda bisa menolak payload dengan delivery_id yang sudah pernah diproses.