跳到主要内容

订阅webhook

DEEPayment 向平台运营为当前商户配置的商户级 HTTPS Webhook URL 发送订阅和账单事件。

Subscription Event 与其他商户 webhook 复用同一套商户 API Access Key、Secret Key、Header 和六行 HMAC-SHA256 签名协议。处理事件前必须先对 HTTP 原始 Body 字节验签。

事件是实时线索。通知延迟、乱序、本地状态冲突或事件未覆盖所需状态时,调用对应查询接口确认当前事实。

Webhook URL 未配置或已停用时,Subscription 和 Invoice 业务仍正常推进,但事件不会投递;待投递任务会保留,在配置重新启用后继续重试。

事件类型​

事件含义
subscription.activeSubscription 进入 ACTIVE。
subscription.past_dueSubscription 存在逾期账单。
subscription.canceledSubscription 已取消。
subscription.unpaidSubscription 进入 UNPAID。
subscription.failedSubscription 建立失败。
invoice.payment_failedInvoice 扣款失败。
invoice.paidInvoice 已支付并关联 Payment Order。
invoice.voidedInvoice 已作废,不再针对它收款。
invoice.uncollectibleInvoice 未确认支付成功,自动收款已经终止。

当前不为保留状态 TRIALING、PENDING_CHECKOUT 和 ACTIVATING 发送事件,这些状态必须查询 Subscription。

HTTP Header​

Content-Type: application/json
X-Access-Key: your_access_key
X-Timestamp: 1784023200
X-Signature: lowercase_hex_hmac_sha256
X-Body-Encryption: AES-256-GCM

X-Timestamp 是本次 HTTP 投递时间,使用 Unix 秒,重试时可以变化。只有商户启用 Body 加密时才携带 X-Body-Encryption,明文投递不携带该 Header。payload 的 occurredAt 是领域事件产生时间,使用 Unix 毫秒,重试时保持不变。

验签规则​

根据 X-Access-Key 找到同一环境的 Secret Key,并使用实际在线路上传输的 HTTP Body 原始字节计算:

bodyHash = lowercase_hex(SHA256(rawBody))

stringToSign = "POST" + "\n"
+ escapedPath + "\n"
+ rawQuery + "\n"
+ accessKey + "\n"
+ timestamp + "\n"
+ bodyHash

signature = lowercase_hex(
HMAC-SHA256(secretKey, stringToSign)
)
  • escapedPath 和 rawQuery 来自已配置的 Webhook URL;rawQuery 不包含 ?,没有 Query 时使用空行。
  • 必须原样保留 rawQuery,不能重新排序、重新编码或丢弃空值。
  • accessKey 和 timestamp 分别使用 X-Access-Key、X-Timestamp Header 的原始值。
  • 校验时间窗口后,使用安全比较验证收到的签名与计算结果。

携带 X-Body-Encryption: AES-256-GCM 时,rawBody 是加密 envelope。必须先对 envelope 原始字节校验 X-Signature,再解密;未知加密 Header 或解密失败必须拒绝,不能回退为明文解析。重试可能使用新的时间戳和加密 nonce,因此 wire body、Body Hash 和签名可以变化;解密后的业务 payload 及其 eventId 保持稳定。

公共 Envelope​

字段类型说明
eventIdstring稳定的事件唯一号和商户幂等键。
merchantIdintegerDEEPayment 商户 ID。
eventTypestring事件类型。
occurredAtinteger领域事件产生时间,Unix 毫秒。

所有 Subscription 和 Invoice 事件都包含已冻结的 paymentMethod,当前仅支持 CARD。

Subscription 状态事件​

{
"eventId": "NTF_example_active",
"merchantId": 10001,
"eventType": "subscription.active",
"occurredAt": 1784023200000,
"statusVersion": 3,
"subscriptionNo": "sub_example_001",
"merchantSubscriptionNo": "membership_user_1001",
"planNo": "plan_example_001",
"customerNo": "cus_example_001",
"paymentMethod": "CARD",
"status": "ACTIVE",
"currentPeriodStart": 1782864000000,
"currentPeriodEnd": 1785542399000,
"nextBillingTime": 1785542400000,
"canceledTime": 0,
"cancelReason": ""
}

五类 Subscription 事件使用同一结构,status 分别为 ACTIVE、PAST_DUE、CANCELED、UNPAID、FAILED。statusVersion 只在同一个 subscriptionNo 内单调递增;商户只接受大于该 Subscription 已处理版本的事件。

Invoice 支付失败事件​

{
"eventId": "NTF_example_failed",
"merchantId": 10001,
"eventType": "invoice.payment_failed",
"occurredAt": 1784023200000,
"invoiceNo": "inv_example_002",
"subscriptionNo": "sub_example_001",
"merchantSubscriptionNo": "membership_user_1001",
"amount": "199.00",
"paidAmount": "0",
"currency": "MXN",
"paymentMethod": "CARD",
"status": "PAYMENT_FAILED",
"dueTime": 1784023100000,
"attemptCount": 1
}

Invoice 作废与停止收款事件​

invoice.voided 和 invoice.uncollectible 与 invoice.payment_failed 使用相同的 Invoice 身份和金额字段,status 分别为 VOID 和 UNCOLLECTIBLE。

{
"eventId": "NTF_example_voided",
"merchantId": 10001,
"eventType": "invoice.voided",
"occurredAt": 1784023200000,
"invoiceNo": "inv_example_003",
"subscriptionNo": "sub_example_001",
"merchantSubscriptionNo": "membership_user_1001",
"amount": "199.00",
"paidAmount": "0",
"currency": "MXN",
"paymentMethod": "CARD",
"status": "VOID",
"dueTime": 1784023100000,
"attemptCount": 0
}
{
"eventId": "NTF_example_uncollectible",
"merchantId": 10001,
"eventType": "invoice.uncollectible",
"occurredAt": 1784023200000,
"invoiceNo": "inv_example_004",
"subscriptionNo": "sub_example_001",
"merchantSubscriptionNo": "membership_user_1001",
"amount": "199.00",
"paidAmount": "0",
"currency": "MXN",
"paymentMethod": "CARD",
"status": "UNCOLLECTIBLE",
"dueTime": 1784023100000,
"attemptCount": 3
}

这些 Invoice 事件不改变 Subscription 合同状态,商户判断权益时应单独查询 Subscription。

Invoice 支付成功事件​

{
"eventId": "NTF_example_paid",
"merchantId": 10001,
"eventType": "invoice.paid",
"occurredAt": 1784023200000,
"invoiceNo": "inv_example_001",
"subscriptionNo": "sub_example_001",
"merchantSubscriptionNo": "membership_user_1001",
"orderNo": "FP_example_001",
"merchantOrderNo": "SUBINV_example_001",
"amount": "199.00",
"paidAmount": "199.00",
"currency": "MXN",
"paymentMethod": "CARD",
"status": "PAID",
"payTime": 1784023199000,
"metadata": {
"planNo": "plan_example_001",
"customerNo": "cus_example_001"
}
}

invoice.paid 只确认 Invoice 支付,不代表已结算。当前 Subscription API 不公开结算状态。

商户处理顺序​

  1. 定位凭据

    缺失或未知的 X-Access-Key 必须拒绝;根据 Access Key 找到 Secret Key,并保留实际 wire body 原始字节。

  2. 校验 wire 签名

    校验 Unix 秒时间戳窗口,根据已配置 URL 重建六行 stringToSign,并在解析 Body 前验证 X-Signature。

  3. 严格解密

    存在 X-Body-Encryption: AES-256-GCM 时,解密已完成验签的 envelope;否则把已验签的 raw body 解析为 JSON。

  4. 幂等落库

    使用 payload 中的 eventId 做唯一键;重复事件不能重复开通权益或入账。处理 Subscription 事件时,还应拒绝不大于该 Subscription 已处理版本的 statusVersion。

  5. 确认当前事实

    不同事件类型不保证严格到达顺序;事件与本地状态冲突时查询对应对象。

  6. 返回 2xx

    只有成功处理后才返回任意 HTTP 2xx,非 2xx 会触发平台重试。

投递语义为至少一次。商户已处理事件但 DEEPayment 未收到有效 2xx 时,同一个 eventId 可能再次投递。

查询 API 中的对象状态是整数,事件 payload 的 status 是字符串,两套契约必须分别解析。