即將上線

Webhook

款項一有動靜,立即知道。

Webhook 會在事件發生時即時推送到您的伺服器,您無需輪詢。每個事件都經過簽章,在您確認接收前會持續重試,且可安全地重複處理。

◷ 沙盒將優先開放給候補名單中的開發者。正式金鑰將在 TujuPay 取得牌照後提供。

設定端點

  1. 1

    新增網址

    在商家後台開啟「開發者」,再進入「Webhook」,新增您伺服器上的公開 HTTPS 網址。

  2. 2

    選擇事件

    挑選您需要的事件,或在開發期間訂閱全部事件。

  3. 3

    複製簽章密鑰

    每個端點都有各自的密鑰,以 whsec_ 開頭。請像密碼一樣妥善保管。

驗證簽章

每個請求都帶有 TujuPay-Signature 標頭,內含時間戳記,以及對時間戳記和原始請求內容計算的 HMAC-SHA256 簽章。請用您的密鑰計算相同的簽章並進行比對。拒絕超過五分鐘的事件,以防範重放攻擊。

標頭格式
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);
}

重試機制

如果您的伺服器未在 10 秒內回傳 2xx,我們會以逐漸拉長的間隔重試,最長持續三天。您也可以在商家後台重新發送任何事件。

  1. 11 分鐘
  2. 25 分鐘
  3. 330 分鐘
  4. 42 小時
  5. 56 小時
  6. 6之後每 12 小時一次,最長 3 天

事件

事件觸發時機
payment.succeeded顧客已付款,款項已確認
payment.failed銀行拒絕,或顧客放棄結帳
payment.expired付款未在 30 分鐘內完成
refund.succeeded退款已退回給顧客
refund.failed退款無法完成
payout.scheduled今日結算已計算完成,即將發出
payout.paid結算款項已到帳
payout.held結算已暫停,事件內容會說明原因

最佳做法

  • 立即回傳 200,將寄送電子郵件等耗時工作交給背景任務處理。
  • 儲存每個事件 ID,略過已處理過的事件。重試可能導致同一事件送達兩次。
  • 務必驗證簽章。切勿信任重新導向網址中的金額或狀態。
  • 如需最新狀態,請透過 API 查詢付款;事件可能不按順序送達。