事件类型
无论来自哪个渠道,每个事件的顶层字段都相同。
{ "id": "evt_3f0c...", "type": "subscription.renewed", "created_at": "2026-11-01T12:00:03.000Z", "app_user_id": "6f1c2a3b-4d5e-4f60-8a71-92b3c4d5e6f7", "platform": "ios", "environment": "production", "product_id": "pro_monthly", "transaction_id": "2000000789", "original_transaction_id": "2000000123", "purchased_at": "2026-11-01T12:00:00.000Z", "expires_at": "2026-12-01T12:00:00.000Z", "price_micros": 9990000, "currency": "USD", "ownership": "purchased", "data": { "store": "app_store", "notification_type": "DID_RENEW", "transaction_reason": "RENEWAL", "status": "active", "will_renew": true }}| 字段 | 含义 |
|---|---|
id |
稳定的事件 id,用来去重 |
type |
下表中的一种类型 |
platform |
ios、android、web 或 test |
environment |
production、sandbox 或 test。见环境 |
price_micros |
价格乘以 1,000,000,币种为原始 currency;9.99 USD 即 9990000 |
ownership |
purchased 或 family_shared |
data |
渠道详情。data.store 为 app_store、google_play、stripe、ccbill 或 test_store |
| 类型 | 触发时机 |
|---|---|
purchase |
记录了一笔一次性商品购买(消耗型、非消耗型、非续期订阅) |
subscription.started |
新订阅,或到期后重新订阅 |
subscription.renewed |
续订成功,包括扣款失败后的恢复(data.recovered) |
subscription.product_changed |
升级(立即生效,产生新交易)或降级(data.change:downgrade 或 downgrade_canceled,data.effective:next_renewal,data.new_product_id) |
subscription.canceled |
关闭了自动续订。权限保留到 expires_at |
subscription.uncanceled |
重新打开了自动续订 |
subscription.billing_issue |
扣款失败。data.in_grace_period 表示权限是否继续 |
subscription.grace_period_expired |
宽限期结束,仍未扣款成功 |
subscription.expired |
订阅结束。data.expiration_reason:VOLUNTARY、BILLING_RETRY、PRICE_INCREASE、PRODUCT_NOT_FOR_SALE,其他渠道还有 DEVELOPER 或 DISPUTE |
subscription.paused |
仅 Android。带 data.auto_resume_time |
refund |
已退款。请收回权限 |
refund.reversed |
Apple 撤销了退款。请恢复权限 |
refund.consumption_requested |
Apple 索要消费信息(data.deadline_at、data.default_refund_preference) |
revoked |
家人共享的权限被撤销 |
chargeback |
Stripe 发起拒付。不会收回权限,由你自行决定 |
chargeback.closed |
拒付结案,data.outcome 为 won 或 lost。结果为 lost 时随后会有一条 data.reason: "chargeback_lost" 的 refund |
ad.reward |
一个激励广告被完整观看。见广告奖励 |
test |
只由测试回调接口发送。校验签名、返回 2xx,其他什么都不做 |
只有 Kasskeep 知道对应用户时才会产生事件。不带 appAccountToken 的交易(比如在 App 外兑换的优惠码)会被静默保存,等有用户提交它时再发出事件。
并非每个渠道都会产生所有类型。
| 类型 | App Store | Google Play | Stripe | 测试商店 |
|---|---|---|---|---|
subscription.paused |
✓ | |||
refund.reversed |
✓ | ✓ | ||
refund.consumption_requested |
✓ | |||
revoked |
✓ | ✓ | ||
chargeback、chargeback.closed |
✓ |
在 Android 上,data 还带有 purchase_token、subscription_state、base_plan_id、offer_id 和 notification_type(RTDN 名称)。在 Stripe 上,视情况带有 notification_type、notification_id、subscription_id、invoice_id、stripe_status 和 checkout_session_id。
- 校验签名。
- 保存事件 id。如果已经处理过,返回 2xx 并结束。
- 不要依赖顺序。依据
created_at和事件里的字段,或者用私钥从GET /v1/customers/:app_user_id读取当前状态。 sandbox和test事件不算收入。- 在 10 秒内返回 2xx。耗时的工作放到响应之后做。