Pra-peluncuran

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. 1

    Tambahkan URL Anda

    Di dasbor, buka Developer, lalu Webhook, dan tambahkan URL HTTPS publik di server Anda.

  2. 2

    Pilih event

    Pilih event yang Anda butuhkan, atau langganan semuanya selama masa pengembangan.

  3. 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.

Format header
TujuPay-Signature: t=1791536400,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd
Node.js
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
<?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.

  1. 11 menit
  2. 25 menit
  3. 330 menit
  4. 42 jam
  5. 56 jam
  6. 6Lalu setiap 12 jam, hingga 3 hari

Event

EventDikirim saat
payment.succeededPelanggan sudah membayar dan dananya terkonfirmasi
payment.failedBank menolak atau pelanggan meninggalkan checkout
payment.expiredPembayaran tidak diselesaikan dalam 30 menit
refund.succeededRefund sudah diterima pelanggan
refund.failedRefund tidak bisa diselesaikan
payout.scheduledPencairan hari ini sudah dihitung dan akan segera dikirim
payout.paidPencairan sudah masuk ke bank Anda
payout.heldPencairan 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.