Recur
Recur
Recur 文件中心
開發者指南
Webhooks 概覽設定 Webhook 端點Webhook 傳遞機制Webhook 事件類型
開發者指南Webhooks

Webhook 事件類型

所有 Webhook 事件的完整列表和 Payload 格式

事件觸發時序

初次訂閱(Checkout)

下圖說明首次訂閱結帳流程中各事件的觸發順序:

checkout.created vs subscription.created 的差異

  • checkout.created:顧客進入結帳頁面時觸發,此時尚未填寫任何資訊
  • subscription.created:顧客提交基本資訊後觸發,系統建立 pending 狀態的訂閱記錄

訂單生命週期(付款與退款)

下圖說明訂單從建立到完成,以及退款的完整流程:

退款流程

當需要退款時,流程如下:

訂閱續費(Renewal)

下圖說明訂閱週期扣款流程中各事件的觸發順序:

逾期與重試機制

當續費扣款失敗時,訂閱會進入 past_due 狀態。系統會在寬限期內(預設 3 天)自動重試扣款:

  • 重試成功:觸發 invoice.paid + subscription.renewed,訂閱恢復為 active
  • 所有重試失敗:觸發 subscription.expired,訂閱過期

事件命名規則

所有事件遵循 {resource}.{action} 格式:

  • resource:資源類型(checkout、order、subscription、customer)
  • action:動作(created、activated、cancelled 等)

事件信封格式

所有事件都使用統一的信封格式:

{
  "id": "evt_1234567890abcdef",
  "type": "subscription.activated",
  "timestamp": "2024-01-15T10:30:00.000Z",
  "data": {
    // 事件特定的 payload
  }
}
欄位類型說明
idstring唯一事件 ID
typestring事件類型
timestampstringISO 8601 時間戳
dataobject事件 Payload

Checkout 事件

結帳流程相關事件。

checkout.created

結帳 Session 建立時觸發。

{
  "id": "evt_chk_created_001",
  "type": "checkout.created",
  "timestamp": "2024-01-15T10:00:00.000Z",
  "data": {
    "id": "chk_abc123def456",
    "status": "pending",
    "amount": 299,
    "currency": "TWD",
    "product_id": "prod_pro_monthly",
    "customer_id": null,
    "customer_email": "user@example.com",
    "created_at": "2024-01-15T10:00:00.000Z",
    "completed_at": null
  }
}

checkout.completed

結帳完成時觸發(付款成功或失敗後)。

{
  "id": "evt_chk_completed_001",
  "type": "checkout.completed",
  "timestamp": "2024-01-15T10:05:00.000Z",
  "data": {
    "id": "chk_abc123def456",
    "status": "completed",
    "amount": 299,
    "currency": "TWD",
    "product_id": "prod_pro_monthly",
    "customer_id": "cus_xyz789",
    "customer_email": "user@example.com",
    "created_at": "2024-01-15T10:00:00.000Z",
    "completed_at": "2024-01-15T10:05:00.000Z"
  }
}

Order 事件

訂單付款相關事件。

order.paid

訂單付款成功時觸發。

{
  "id": "evt_order_paid_001",
  "type": "order.paid",
  "timestamp": "2024-01-15T10:05:00.000Z",
  "data": {
    "id": "ord_abc123",
    "order_id": "ord_abc123",
    "amount": 299,
    "currency": "TWD",
    "status": "paid",
    "billing_reason": "subscription_create",
    "payment_method": "card",
    "paid_at": "2024-01-15T10:05:00.000Z",
    "created_at": "2024-01-15T10:04:00.000Z",
    "customer_id": "cus_xyz789",
    "product_id": "prod_pro_monthly",
    "checkout_id": "chk_abc123def456",
    "subscription_id": "sub_def456"
  }
}

billing_reason 可能的值:

值說明
purchase一次性購買
subscription_create訂閱首次付款
subscription_cycle訂閱續訂付款
subscription_update訂閱升降級差額

order.payment_failed

訂單付款失敗時觸發。

{
  "id": "evt_order_failed_001",
  "type": "order.payment_failed",
  "timestamp": "2024-01-15T10:05:00.000Z",
  "data": {
    "id": "ord_abc123",
    "order_id": "ord_abc123",
    "amount": 299,
    "currency": "TWD",
    "status": "failed",
    "billing_reason": "subscription_cycle",
    "payment_method": "card",
    "paid_at": null,
    "created_at": "2024-01-15T10:04:00.000Z",
    "customer_id": "cus_xyz789",
    "product_id": "prod_pro_monthly",
    "checkout_id": null,
    "subscription_id": "sub_def456"
  }
}

Subscription 事件

訂閱生命週期相關事件。

subscription.created

訂閱建立時觸發(付款前)。

{
  "id": "evt_sub_created_001",
  "type": "subscription.created",
  "timestamp": "2024-01-15T10:05:00.000Z",
  "data": {
    "id": "sub_def456",
    "customer_id": "cus_xyz789",
    "plan_id": "plan_pro",
    "price_id": "price_pro_monthly",
    "status": "pending",
    "amount": 299,
    "billing_period": "monthly",
    "next_billing_date": null,
    "trial_ends_at": null,
    "current_period_start": "2024-01-15T00:00:00.000Z",
    "current_period_end": "2024-02-15T00:00:00.000Z",
    "created_at": "2024-01-15T10:05:00.000Z",
    "updated_at": "2024-01-15T10:05:00.000Z"
  }
}

subscription.activated

訂閱啟用時觸發(首次付款成功)。

{
  "id": "evt_sub_activated_001",
  "type": "subscription.activated",
  "timestamp": "2024-01-15T10:05:30.000Z",
  "data": {
    "id": "sub_def456",
    "customer_id": "cus_xyz789",
    "plan_id": "plan_pro",
    "price_id": "price_pro_monthly",
    "status": "active",
    "amount": 299,
    "billing_period": "monthly",
    "next_billing_date": "2024-02-15T00:00:00.000Z",
    "trial_ends_at": null,
    "current_period_start": "2024-01-15T00:00:00.000Z",
    "current_period_end": "2024-02-15T00:00:00.000Z",
    "created_at": "2024-01-15T10:05:00.000Z",
    "updated_at": "2024-01-15T10:05:30.000Z"
  }
}

subscription.renewed

訂閱續訂時觸發(週期性扣款成功)。

{
  "id": "evt_sub_renewed_001",
  "type": "subscription.renewed",
  "timestamp": "2024-02-15T00:00:30.000Z",
  "data": {
    "id": "sub_def456",
    "customer_id": "cus_xyz789",
    "plan_id": "plan_pro",
    "price_id": "price_pro_monthly",
    "status": "active",
    "amount": 299,
    "billing_period": "monthly",
    "next_billing_date": "2024-03-15T00:00:00.000Z",
    "trial_ends_at": null,
    "current_period_start": "2024-02-15T00:00:00.000Z",
    "current_period_end": "2024-03-15T00:00:00.000Z",
    "created_at": "2024-01-15T10:05:00.000Z",
    "updated_at": "2024-02-15T00:00:30.000Z"
  }
}

subscription.cancelled

訂閱取消時觸發。

取消後訂閱仍會持續到 current_period_end。期滿後會觸發 subscription.expired。

{
  "id": "evt_sub_cancelled_001",
  "type": "subscription.cancelled",
  "timestamp": "2024-02-10T15:30:00.000Z",
  "data": {
    "id": "sub_def456",
    "customer_id": "cus_xyz789",
    "plan_id": "plan_pro",
    "price_id": "price_pro_monthly",
    "status": "cancelled",
    "amount": 299,
    "billing_period": "monthly",
    "next_billing_date": null,
    "trial_ends_at": null,
    "current_period_start": "2024-02-15T00:00:00.000Z",
    "current_period_end": "2024-03-15T00:00:00.000Z",
    "created_at": "2024-01-15T10:05:00.000Z",
    "updated_at": "2024-02-10T15:30:00.000Z"
  }
}

subscription.expired

訂閱過期時觸發(取消後期滿或續訂失敗)。

{
  "id": "evt_sub_expired_001",
  "type": "subscription.expired",
  "timestamp": "2024-03-15T00:00:00.000Z",
  "data": {
    "id": "sub_def456",
    "customer_id": "cus_xyz789",
    "plan_id": "plan_pro",
    "price_id": "price_pro_monthly",
    "status": "expired",
    "amount": 299,
    "billing_period": "monthly",
    "next_billing_date": null,
    "trial_ends_at": null,
    "current_period_start": "2024-02-15T00:00:00.000Z",
    "current_period_end": "2024-03-15T00:00:00.000Z",
    "created_at": "2024-01-15T10:05:00.000Z",
    "updated_at": "2024-03-15T00:00:00.000Z"
  }
}

subscription.trial_ending

試用期即將結束時觸發(提前 3 天通知)。

{
  "id": "evt_sub_trial_ending_001",
  "type": "subscription.trial_ending",
  "timestamp": "2024-01-12T00:00:00.000Z",
  "data": {
    "id": "sub_trial123",
    "customer_id": "cus_xyz789",
    "plan_id": "plan_pro",
    "price_id": "price_pro_monthly",
    "status": "trialing",
    "amount": 299,
    "billing_period": "monthly",
    "next_billing_date": "2024-01-15T00:00:00.000Z",
    "trial_ends_at": "2024-01-15T00:00:00.000Z",
    "current_period_start": "2024-01-01T00:00:00.000Z",
    "current_period_end": "2024-01-15T00:00:00.000Z",
    "created_at": "2024-01-01T10:00:00.000Z",
    "updated_at": "2024-01-12T00:00:00.000Z"
  }
}

subscription.upgraded

訂閱升級時觸發。

{
  "id": "evt_sub_upgraded_001",
  "type": "subscription.upgraded",
  "timestamp": "2024-02-01T14:00:00.000Z",
  "data": {
    "id": "sub_def456",
    "customer_id": "cus_xyz789",
    "plan_id": "plan_enterprise",
    "price_id": "price_enterprise_monthly",
    "status": "active",
    "amount": 999,
    "billing_period": "monthly",
    "next_billing_date": "2024-03-01T00:00:00.000Z",
    "trial_ends_at": null,
    "current_period_start": "2024-02-01T00:00:00.000Z",
    "current_period_end": "2024-03-01T00:00:00.000Z",
    "created_at": "2024-01-15T10:05:00.000Z",
    "updated_at": "2024-02-01T14:00:00.000Z"
  }
}

subscription.downgraded

訂閱降級時觸發。

{
  "id": "evt_sub_downgraded_001",
  "type": "subscription.downgraded",
  "timestamp": "2024-02-01T14:00:00.000Z",
  "data": {
    "id": "sub_def456",
    "customer_id": "cus_xyz789",
    "plan_id": "plan_basic",
    "price_id": "price_basic_monthly",
    "status": "active",
    "amount": 99,
    "billing_period": "monthly",
    "next_billing_date": "2024-03-01T00:00:00.000Z",
    "trial_ends_at": null,
    "current_period_start": "2024-02-01T00:00:00.000Z",
    "current_period_end": "2024-03-01T00:00:00.000Z",
    "created_at": "2024-01-15T10:05:00.000Z",
    "updated_at": "2024-02-01T14:00:00.000Z"
  }
}

subscription.past_due

訂閱付款失敗時觸發,訂閱進入逾期狀態。

逾期狀態會持續最多 3 天(寬限期),期間系統會自動重試扣款。 若所有重試都失敗,訂閱將會過期(觸發 subscription.expired)。

{
  "id": "evt_sub_past_due_001",
  "type": "subscription.past_due",
  "timestamp": "2024-02-15T00:05:00.000Z",
  "data": {
    "id": "sub_def456",
    "customer_id": "cus_xyz789",
    "plan_id": "plan_pro",
    "price_id": "price_pro_monthly",
    "status": "past_due",
    "amount": 299,
    "billing_period": "monthly",
    "next_billing_date": "2024-02-15T00:00:00.000Z",
    "trial_ends_at": null,
    "current_period_start": "2024-01-15T00:00:00.000Z",
    "current_period_end": "2024-02-15T00:00:00.000Z",
    "created_at": "2024-01-15T10:05:00.000Z",
    "updated_at": "2024-02-15T00:05:00.000Z"
  }
}

Invoice 事件

帳單(週期扣款)相關事件。每次訂閱續費都會建立一張帳單(Invoice)。

invoice.created

帳單建立時觸發(扣款前)。

{
  "id": "evt_inv_created_001",
  "type": "invoice.created",
  "timestamp": "2024-02-15T00:00:00.000Z",
  "data": {
    "id": "inv_abc123",
    "invoice_number": "INV-20240215-XYZ789",
    "subscription_id": "sub_def456",
    "customer_id": "cus_xyz789",
    "amount": 299,
    "currency": "TWD",
    "status": "pending",
    "billing_reason": "subscription_cycle",
    "period_start": "2024-02-15T00:00:00.000Z",
    "period_end": "2024-03-15T00:00:00.000Z",
    "paid_at": null,
    "created_at": "2024-02-15T00:00:00.000Z"
  }
}

invoice.paid

帳單付款成功時觸發。

{
  "id": "evt_inv_paid_001",
  "type": "invoice.paid",
  "timestamp": "2024-02-15T00:00:30.000Z",
  "data": {
    "id": "inv_abc123",
    "invoice_number": "INV-20240215-XYZ789",
    "subscription_id": "sub_def456",
    "customer_id": "cus_xyz789",
    "amount": 299,
    "currency": "TWD",
    "status": "paid",
    "billing_reason": "subscription_cycle",
    "period_start": "2024-02-15T00:00:00.000Z",
    "period_end": "2024-03-15T00:00:00.000Z",
    "paid_at": "2024-02-15T00:00:30.000Z",
    "created_at": "2024-02-15T00:00:00.000Z"
  }
}

invoice.payment_failed

帳單付款失敗時觸發。

此事件通常伴隨 subscription.past_due 一起觸發。 使用此事件可以發送付款失敗通知給顧客。

{
  "id": "evt_inv_failed_001",
  "type": "invoice.payment_failed",
  "timestamp": "2024-02-15T00:05:00.000Z",
  "data": {
    "id": "inv_abc123",
    "invoice_number": "INV-20240215-XYZ789",
    "subscription_id": "sub_def456",
    "customer_id": "cus_xyz789",
    "amount": 299,
    "currency": "TWD",
    "status": "pending",
    "billing_reason": "subscription_cycle",
    "period_start": "2024-02-15T00:00:00.000Z",
    "period_end": "2024-03-15T00:00:00.000Z",
    "paid_at": null,
    "created_at": "2024-02-15T00:00:00.000Z"
  }
}

Refund 事件

退款相關事件。當用戶申請退款時會觸發這些事件。退款流程請參考上方的退款流程圖。

退款限制

  • 退款必須在付款後 180 天內申請
  • 退款金額不能超過原始付款金額
  • 支援全額退款和部分退款

refund.created

退款申請建立時觸發。此時退款尚未處理,狀態為 pending。

{
  "id": "evt_ref_created_001",
  "type": "refund.created",
  "timestamp": "2024-01-20T14:00:00.000Z",
  "data": {
    "id": "ref_abc123",
    "refund_number": "REF-20240120-XYZ789",
    "charge_id": "chg_def456",
    "charge_number": "CHG-20240115-ABC123",
    "order_id": "ord_xyz789",
    "invoice_id": null,
    "subscription_id": "sub_ghi012",
    "customer_id": "cus_xyz789",
    "amount": 299,
    "currency": "TWD",
    "status": "pending",
    "reason": "customer_request",
    "reason_detail": "用戶要求取消訂閱並退款",
    "original_amount": 299,
    "refunded_amount": 0,
    "created_at": "2024-01-20T14:00:00.000Z",
    "processed_at": null,
    "failed_at": null,
    "failure_code": null,
    "failure_message": null
  }
}

refund.succeeded

退款處理成功時觸發。款項將退回到原付款方式。

{
  "id": "evt_ref_succeeded_001",
  "type": "refund.succeeded",
  "timestamp": "2024-01-20T14:01:00.000Z",
  "data": {
    "id": "ref_abc123",
    "refund_number": "REF-20240120-XYZ789",
    "charge_id": "chg_def456",
    "charge_number": "CHG-20240115-ABC123",
    "order_id": "ord_xyz789",
    "invoice_id": null,
    "subscription_id": "sub_ghi012",
    "customer_id": "cus_xyz789",
    "amount": 299,
    "currency": "TWD",
    "status": "succeeded",
    "reason": "customer_request",
    "reason_detail": "用戶要求取消訂閱並退款",
    "original_amount": 299,
    "refunded_amount": 299,
    "created_at": "2024-01-20T14:00:00.000Z",
    "processed_at": "2024-01-20T14:01:00.000Z",
    "failed_at": null,
    "failure_code": null,
    "failure_message": null
  }
}

refund.failed

退款處理失敗時觸發。可能需要重試或人工介入。

{
  "id": "evt_ref_failed_001",
  "type": "refund.failed",
  "timestamp": "2024-01-20T14:01:00.000Z",
  "data": {
    "id": "ref_abc123",
    "refund_number": "REF-20240120-XYZ789",
    "charge_id": "chg_def456",
    "charge_number": "CHG-20240115-ABC123",
    "order_id": "ord_xyz789",
    "invoice_id": null,
    "subscription_id": "sub_ghi012",
    "customer_id": "cus_xyz789",
    "amount": 299,
    "currency": "TWD",
    "status": "failed",
    "reason": "customer_request",
    "reason_detail": "用戶要求取消訂閱並退款",
    "original_amount": 299,
    "refunded_amount": 0,
    "created_at": "2024-01-20T14:00:00.000Z",
    "processed_at": null,
    "failed_at": "2024-01-20T14:01:00.000Z",
    "failure_code": "REFUND_FAILED",
    "failure_message": "金流商退款處理失敗"
  }
}

reason 可能的值:

Webhook payload 中的 reason 欄位使用 snake_case 格式,對應到系統中的 RefundReason enum。

Enum 值Payload 值說明
REQUESTED_BY_CUSTOMERcustomer_request客戶主動要求退款
DUPLICATE_CHARGEduplicate重複付款
FRAUDULENTfraudulent詐騙或未授權交易
SUBSCRIPTION_CANCELEDsubscription_cancelled訂閱取消退款
PRODUCT_NOT_DELIVEREDproduct_not_delivered商品/服務未提供
PRODUCT_UNSATISFACTORYproduct_unsatisfactory商品/服務不滿意
OTHERother其他原因(需填 reason_detail)

Customer 事件

客戶資料相關事件。

customer.created

新客戶建立時觸發。

{
  "id": "evt_cus_created_001",
  "type": "customer.created",
  "timestamp": "2024-01-15T10:05:00.000Z",
  "data": {
    "id": "cus_xyz789",
    "email": "user@example.com",
    "name": "王小明",
    "status": "active",
    "created_at": "2024-01-15T10:05:00.000Z",
    "updated_at": "2024-01-15T10:05:00.000Z"
  }
}

customer.updated

客戶資料更新時觸發。

{
  "id": "evt_cus_updated_001",
  "type": "customer.updated",
  "timestamp": "2024-01-20T09:00:00.000Z",
  "data": {
    "id": "cus_xyz789",
    "email": "new-email@example.com",
    "name": "王小明",
    "status": "active",
    "created_at": "2024-01-15T10:05:00.000Z",
    "updated_at": "2024-01-20T09:00:00.000Z"
  }
}

Payload 欄位說明

Subscription Payload

欄位類型說明
idstring訂閱 ID
customer_idstring客戶 ID
plan_idstring方案 ID
price_idstring價格 ID
statusstring訂閱狀態
amountnumber訂閱金額(新台幣)
billing_periodstring計費週期(weekly, monthly, yearly)
next_billing_datestring | null下次扣款日期
trial_ends_atstring | null試用結束日期
current_period_startstring當前週期開始
current_period_endstring當前週期結束
created_atstring建立時間
updated_atstring更新時間

status 可能的值:

狀態說明
pending待付款
trialing試用中
active使用中
past_due付款逾期
cancelled已取消(期滿前)
expired已過期

Order Payload

欄位類型說明
idstring訂單 ID
amountnumber訂單金額
currencystring幣別(TWD)
statusstring訂單狀態
billing_reasonstring帳單原因
payment_methodstring付款方式
paid_atstring | null付款時間
customer_idstring客戶 ID
product_idstring | null商品 ID
checkout_idstring | null結帳 ID
subscription_idstring | null訂閱 ID

Refund Payload

欄位類型說明
idstring退款 ID
refund_numberstring退款編號
charge_idstring原付款 Charge ID
charge_numberstring原付款編號
order_idstring | null訂單 ID(首次付款)
invoice_idstring | null帳單 ID(續費付款)
subscription_idstring | null關聯的訂閱 ID
customer_idstring | null客戶 ID
amountnumber退款金額
currencystring幣別(TWD)
statusstring退款狀態
reasonstring退款原因代碼
reason_detailstring | null退款原因詳情
original_amountnumber原付款金額
refunded_amountnumber已退款總額
created_atstring建立時間
processed_atstring | null處理完成時間
failed_atstring | null失敗時間
failure_codestring | null失敗代碼
failure_messagestring | null失敗訊息

status 可能的值:

狀態說明
pending待處理
processing處理中
succeeded退款成功
failed退款失敗

處理範例

依事件類型處理

app.post('/api/webhooks/recur', async (req, res) => {
  const event = req.body;

  switch (event.type) {
    case 'subscription.activated':
      await handleSubscriptionActivated(event.data);
      break;

    case 'subscription.cancelled':
      await handleSubscriptionCancelled(event.data);
      break;

    case 'order.paid':
      await handleOrderPaid(event.data);
      break;

    case 'customer.created':
      await handleCustomerCreated(event.data);
      break;

    // 退款事件處理
    case 'refund.created':
      await handleRefundCreated(event.data);
      break;

    case 'refund.succeeded':
      await handleRefundSucceeded(event.data);
      break;

    case 'refund.failed':
      await handleRefundFailed(event.data);
      break;

    default:
      console.log(`Unhandled event type: ${event.type}`);
  }

  res.status(200).json({ received: true });
});

async function handleSubscriptionActivated(data: SubscriptionPayload) {
  // 啟用用戶權限
  await enableUserAccess(data.customer_id, data.plan_id);

  // 發送歡迎郵件
  await sendWelcomeEmail(data.customer_id);
}

async function handleSubscriptionCancelled(data: SubscriptionPayload) {
  // 記錄取消原因(可選)

  // 發送挽留郵件
  await sendRetentionEmail(data.customer_id);
}

// 退款處理函數
async function handleRefundCreated(data: RefundPayload) {
  console.log(`退款申請建立: ${data.refund_number}`);

  // 記錄退款申請
  await db.refundLog.create({
    data: {
      refundId: data.id,
      refundNumber: data.refund_number,
      amount: data.amount,
      status: 'pending',
    },
  });
}

async function handleRefundSucceeded(data: RefundPayload) {
  console.log(`退款成功: ${data.refund_number}, 金額: ${data.amount}`);

  // 更新訂單狀態
  if (data.order_id) {
    await db.order.update({
      where: { id: data.order_id },
      data: { refundedAmount: data.refunded_amount },
    });
  }

  // 如果是訂閱退款,可能需要停用權限
  if (data.subscription_id) {
    await revokeUserAccess(data.customer_id, data.subscription_id);
  }

  // 發送退款成功通知
  await sendRefundConfirmationEmail(data.customer_id, data);
}

async function handleRefundFailed(data: RefundPayload) {
  console.error(`退款失敗: ${data.refund_number}, 原因: ${data.failure_message}`);

  // 通知管理員處理失敗的退款
  await notifyAdminRefundFailed(data);
}

下一步

  • 設定 Webhook 端點 - 在後台建立端點
  • Webhook 傳遞機制 - 了解簽章驗證和重試策略

Webhook 傳遞機制

了解 Webhook 傳遞、重試策略和簽章驗證

範例

透過實際範例學習如何使用 Recur SDK

On this page

事件觸發時序初次訂閱(Checkout)訂單生命週期(付款與退款)退款流程訂閱續費(Renewal)事件命名規則事件信封格式Checkout 事件checkout.createdcheckout.completedOrder 事件order.paidorder.payment_failedSubscription 事件subscription.createdsubscription.activatedsubscription.renewedsubscription.cancelledsubscription.expiredsubscription.trial_endingsubscription.upgradedsubscription.downgradedsubscription.past_dueInvoice 事件invoice.createdinvoice.paidinvoice.payment_failedRefund 事件refund.createdrefund.succeededrefund.failedCustomer 事件customer.createdcustomer.updatedPayload 欄位說明Subscription PayloadOrder PayloadRefund Payload處理範例依事件類型處理下一步