Webhook
Tahu begitu uang berpindah.
Webhook mengirim event ke server Anda saat terjadi, jadi Anda tidak perlu polling. Setiap event bertanda tangan, dikirim ulang sampai Anda mengonfirmasinya, dan aman diproses lebih dari sekali.
◷ Sandbox akan dibuka lebih dulu untuk developer di daftar tunggu. Kunci live menyusul setelah TujuPay berlisensi.
Siapkan endpoint
- 1
Tambahkan URL Anda
Di dasbor, buka Developer, lalu Webhook, dan tambahkan URL HTTPS publik di server Anda.
- 2
Pilih event
Pilih event yang Anda butuhkan, atau langganan semuanya selama masa pengembangan.
- 3
Salin signing secret
Setiap endpoint punya secret sendiri, diawali whsec_. Simpan seperti password.
Verifikasi tanda tangan
Setiap request membawa header TujuPay-Signature berisi timestamp dan tanda tangan HMAC-SHA256 dari timestamp dan body request mentah. Hitung tanda tangan yang sama dengan secret Anda lalu bandingkan. Tolak event yang lebih lama dari lima menit untuk mencegah replay.
TujuPay-Signature: t=1791536400,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd
import crypto from "node:crypto";
export function verifyWebhook(rawBody, header, secret) {
const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
const age = Date.now() / 1000 - Number(parts.t);
if (age > 300) throw new Error("Event too old");
const expected = crypto
.createHmac("sha256", secret)
.update(`${parts.t}.${rawBody}`)
.digest("hex");
const ok = crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1));
if (!ok) throw new Error("Bad signature");
return JSON.parse(rawBody);
}<?php
function verify_webhook(string $rawBody, string $header, string $secret): array {
parse_str(str_replace(',', '&', $header), $parts);
if (time() - (int) $parts['t'] > 300) {
throw new Exception('Event too old');
}
$expected = hash_hmac('sha256', $parts['t'] . '.' . $rawBody, $secret);
if (!hash_equals($expected, $parts['v1'])) {
throw new Exception('Bad signature');
}
return json_decode($rawBody, true);
}Percobaan ulang
Jika server Anda tidak mengembalikan 2xx dalam 10 detik, kami mencoba lagi dengan jeda yang makin panjang hingga tiga hari. Anda juga bisa mengirim ulang event apa pun dari dasbor.
- 11 menit
- 25 menit
- 330 menit
- 42 jam
- 56 jam
- 6Lalu setiap 12 jam, hingga 3 hari
Event
| Event | Dikirim saat |
|---|---|
| payment.succeeded | Pelanggan sudah membayar dan dananya terkonfirmasi |
| payment.failed | Bank menolak atau pelanggan meninggalkan checkout |
| payment.expired | Pembayaran tidak diselesaikan dalam 30 menit |
| refund.succeeded | Refund sudah diterima pelanggan |
| refund.failed | Refund tidak bisa diselesaikan |
| payout.scheduled | Pencairan hari ini sudah dihitung dan akan segera dikirim |
| payout.paid | Pencairan sudah masuk ke bank Anda |
| payout.held | Pencairan ditahan. Event-nya menjelaskan alasannya |
Praktik terbaik
- Langsung kembalikan 200 dan kerjakan proses yang lambat, seperti mengirim email, di background job.
- Simpan setiap ID event dan lewati yang sudah pernah diproses. Percobaan ulang bisa mengirim event yang sama dua kali.
- Selalu verifikasi tanda tangan. Jangan pernah percaya nominal atau status di URL redirect.
- Ambil pembayaran dari API jika Anda butuh status paling baru; event bisa datang tidak berurutan.