Lewati ke konten utama
← Kembali ke dokumentasi

Spesifikasi webhook

Format payload, verifikasi tanda tangan, semantik retry.

Spesifikasi webhook

Saat order dikredit, TriaPay menandatangani request POST ke URL webhook yang Anda konfigurasikan. Berkas callback plugin DHRU bawaan adalah receiver standar. Gunakan halaman ini bila Anda menulis sendiri.

Endpoint

Atur URL pada halaman Integration. Dengan plugin DHRU bawaan:

https://yourdhru.com/modules/gateways/callback/triapay.php

Harus HTTPS dan resolve ke IP publik. Alamat privat, loopback, dan link-local ditolak.

Header Nilai
Content-Type application/json
X-Payment-Signature hex(HMAC_SHA256(webhook_secret, signed_input))
X-Payment-Sig-Version 2
X-Payment-Timestamp Unix epoch dalam detik saat penandatanganan
X-Payment-Idempotency Idempotency key order
X-Payment-Mode live atau sandbox

Signed input

Input HMAC adalah:

{timestamp}\n{idempotency}\n{raw_body}

\n adalah satu line feed ASCII (0x0A). Integrasi baru harus mengimplementasi v2.

Payload

{
  "event": "payment.credited",
  "invoiceId": 12345,
  "tenantPublicId": "tnt_a3b1c8d4e2f59071",
  "amount": "10.051231",
  "baseAmount": "10.000000",
  "feeDeducted": "0.000000",
  "fees": "0",
  "txHash": "0xabc...",
  "gateway": "triapay",
  "asset": "USDT",
  "chain": "bep20",
  "matchCode": 51231,
  "mode": "live",
  "ts": 1714579200
}
Field Catatan
event Selalu payment.credited.
invoiceId ID invoice DHRU Anda (digemakan dari pembuatan order).
tenantPublicId Pengenal tenant opaque (tnt_*). Stabil per akun.
amount String desimal. Jumlah yang diterima secara persis.
baseAmount String desimal. Jumlah invoice asli sebelum penyerapan over-fee.
feeDeducted String desimal. Selisih antara jumlah yang diharapkan dan yang diterima saat pelanggan membayar surplus biaya jaringan. Biasanya "0".
fees String desimal. Saat ini "0".
txHash Referensi transaksi unik. Hash tx on-chain, transactionId Binance Pay, atau ID deposit off-chain Binance. Gunakan sebagai dedup key saat mengkredit.
gateway Selalu triapay.
asset USDT atau USDC.
chain trc20, bep20, atau binance_pay. BEP20 dan TRC20 juga memunculkan transfer internal Binance (off-chain) di bawah nilai chain yang sama.
matchCode Kode pencocokan numerik.
mode live atau sandbox. Sama dengan nilai header X-Payment-Mode.
ts Sama dengan nilai X-Payment-Timestamp.

Perubahan breaking

  • (2026-05-10) Payload webhook kini menggunakan camelCase untuk semua field. Receiver custom yang membaca invoice_id / tx_hash / match_code (snake_case) WAJIB diperbarui ke invoiceId / txHash / matchCode. Plugin DHRU bawaan (v2.x atau lebih baru) mendukung format baru secara langsung.
  • tenantId (integer mentah) telah dihapus dan diganti tenantPublicId, string opaque tnt_* yang stabil per akun dan aman untuk dicatat.

Verifikasi (berurutan)

  1. Tolak bila |now - X-Payment-Timestamp| > 300 detik.
  2. Hitung hex(HMAC_SHA256(webhook_secret, "{ts}\n{idempotency}\n{raw_body}")) atas bytes mentah body. Jangan re-serialize JSON.
  3. Bandingkan dengan waktu konstan terhadap X-Payment-Signature (PHP hash_equals, Node crypto.timingSafeEqual, Python hmac.compare_digest).
  4. Tolak bila Anda sudah memproses nilai X-Payment-Idempotency ini.

Respons yang diharapkan

Status Arti
2xx Sukses. Order difinalisasi.
4xx / 5xx / timeout jaringan Diperlakukan sebagai kegagalan. Dicoba ulang.

Hanya status HTTP yang penting. Isi body dicatat untuk debugging.

Kebijakan retry

Pengiriman yang gagal diulang dengan backoff. Bila retry tidak berhasil, order dapat direkonsiliasi manual dari Admin → Orders → Recredit.

Bila webhook URL tidak diatur, order menunggu hingga URL yang valid dikonfigurasi.

Contoh PHP

<?php
$secret = 'your_webhook_secret';
$raw    = file_get_contents('php://input');
$sig    = $_SERVER['HTTP_X_PAYMENT_SIGNATURE']  ?? '';
$ts     = $_SERVER['HTTP_X_PAYMENT_TIMESTAMP'] ?? '0';
$idem   = $_SERVER['HTTP_X_PAYMENT_IDEMPOTENCY'] ?? '';

if (abs(time() - intval($ts)) > 300) {
    http_response_code(401);
    exit('stale timestamp');
}

$signed   = $ts . "\n" . $idem . "\n" . $raw;
$expected = hash_hmac('sha256', $signed, $secret);
if (!hash_equals($expected, $sig)) {
    http_response_code(401);
    exit('bad signature');
}

$payload = json_decode($raw, true);
if (!is_array($payload) || ($payload['event'] ?? '') !== 'payment.credited') {
    http_response_code(400);
    exit('bad event');
}

if (already_credited($payload['txHash'])) {
    echo json_encode(['ok' => true, 'duplicate' => true]);
    exit;
}

credit_invoice(
    $payload['invoiceId'],
    $payload['amount'],
    $payload['txHash'],
    $payload['gateway']
);

echo json_encode(['ok' => true]);

Contoh Node.js

import { createHmac, timingSafeEqual } from 'crypto';
import express from 'express';

const SECRET = process.env.TRIAPAY_WEBHOOK_SECRET!;

app.post('/webhooks/triapay', express.raw({ type: 'application/json' }), (req, res) => {
  const sig  = req.header('X-Payment-Signature')   ?? '';
  const ts   = parseInt(req.header('X-Payment-Timestamp') ?? '0', 10);
  const idem = req.header('X-Payment-Idempotency') ?? '';

  if (Math.abs(Date.now() / 1000 - ts) > 300) return res.status(401).end('stale');

  const signed   = Buffer.concat([Buffer.from(`${ts}\n${idem}\n`), req.body]);
  const expected = createHmac('sha256', SECRET).update(signed).digest('hex');
  const a = Buffer.from(expected, 'hex');
  const b = Buffer.from(sig,      'hex');
  if (a.length !== b.length || !timingSafeEqual(a, b)) {
    return res.status(401).end('bad signature');
  }

  const payload = JSON.parse(req.body.toString());
  // ... idempotency check + credit ...
  res.json({ ok: true });
});

Catatan keamanan

  • Perlakukan webhook secret Anda seperti kata sandi. Putar segera bila bocor.
  • Selalu verifikasi terhadap body request mentah, jangan JSON yang sudah diparse.
  • Selalu gunakan perbandingan waktu konstan.
  • URL webhook harus HTTPS dan resolve ke IP publik.

Butuh lebih?

Apa pun di luar halaman ini tersedia di bawah perjanjian mitra. Hubungi kami via WhatsApp di halaman beranda.