Webhooks

Webhook 讓你的系統即時知道「這筆付款真的成功了」——不能只靠顧客瀏覽器上的成功畫面,因為顧客隨時可能關掉分頁。

檢視 Markdown

結帳成功頁面只是顯示給顧客看,不代表你的後端系統已經知道這件事。顧客可能在看到成功畫面之前就關掉分頁、斷網,或直接把網址分享給別人。Webhook 才是你系統應該信任的權威來源(authoritative state)。

1. 註冊一個 Webhook endpoint

curl -sS -X POST "$BASE/api/WebhookEndpoint" \
  -H "Authorization: Bearer $API_KEY" -H "Content-Type: application/json" \
  -d '{"url":"https://your-app.example/webhooks/rebatepay","events":["payment.succeeded","payment.failed","refund.succeeded"]}'
回應裡的 signingSecret(whsec_...)只會顯示這一次。之後用 GET 只會看到 secretPrefix。請立即存起來,遺失的話只能停用舊的、重新建立一個。

2. 你會收到什麼

POST /your-url
Content-Type: application/json
X-RebatePay-Signature: t=<unix>,v1=<hmac_sha256(secret, "{t}.{raw_body}")>
X-RebatePay-Event-Id: evt_...
X-RebatePay-Delivery-Id: wda_...
X-RebatePay-Event-Type: payment.succeeded

3. 驗證簽章

  1. 1從 X-RebatePay-Signature 解析出 t(timestamp)與 v1(簽章)。
  2. 2如果 |現在時間 - t| > 300 秒,拒絕(避免重放攻擊)。
  3. 3用你的 signing secret 對 ${t}.${原始 body} 算 HMAC-SHA256。
  4. 4用常數時間比較(constant-time compare)跟收到的 v1 比對,不要用 ===。
import { createHmac, timingSafeEqual } from "node:crypto";

function verify(secret, signatureHeader, rawBody) {
  const [tPart, vPart] = signatureHeader.split(",");
  const t = Number(tPart.split("=")[1]);
  const v1 = vPart.split("=")[1];
  if (Math.abs(Date.now() / 1000 - t) > 300) return false;
  const expected = createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex");
  return timingSafeEqual(Buffer.from(expected), Buffer.from(v1));
}

4. 冪等處理(同一事件可能收到不只一次)

ReBate Pay 是 at-least-once 投遞——同一個事件可能因為重試而送達不只一次。用 id(或 X-RebatePay-Event-Id)當作去重的 key:處理前先檢查這個 event id 是否已經處理過,處理過就直接回 200、不要重複套用副作用。

5. 失敗與重試

你的 endpoint 沒回 2xx(或逾時)——ReBate Pay 會用退避策略自動重試;多次失敗後這次投遞會被標記為 terminal(dead-letter)。你可以用 GET /api/WebhookEvent 和 GET /api/WebhookDelivery 查投遞紀錄,或呼叫 POST /api/WebhookDelivery/{id}/Retry(需要 webhooks:write scope 與 Idempotency-Key)自己重送一次。沒有 /replay 這種端點。

事件類型

分類事件
商品/顧客product.created、price.created、customer.created
訂閱subscription.created、subscription.canceled、subscription.ended
帳單/付款billing_record.created、billing_record.paid、payment.succeeded、payment.failed、payment.unknown
退款refund.requested、refund.succeeded、refund.failed
Payment Linkpayment_link.created、payment_link.completed
用量計費(Sandbox / Early Access)usage.recorded、usage.reversed、credit.granted、credit.low、credit.exhausted、entitlement.changed、entitlement.exhausted
本機開發時要收到真實的 Webhook 投遞,你的接收端必須是公開可連的 URL(例如用 ngrok 之類的工具建立臨時公開網址);ReBate Pay 無法送到 localhost。