2026年App Store服务器通知V2教程:从App Store Connect配置到退款、续订与消费请求的逐步接入
Apple 已经关掉 V1 通道:版本 1 通知与 App Store Receipts 接口均已废弃,官方说明现在是「在你的服务器上改为实现版本 2 端点」。对任何跑着订阅的应用,这不是一次外观层面的迁移——通知流就是服务器得知续订成功、发生退款、正在重试扣款、或 Apple 让你举证某笔退款请求的唯一通道。本教程给出 2026 年的完整接入路径:在 App Store Connect 打开版本 2、验证 JWS、把全部 notificationType 与 subtype 映射到权益逻辑,以及用 App Store Server API 取通知本身无法给出的「当前状态」。
一句话版本:App Store Connect 把一个 JWS 签名的 JSON 投到你的 HTTPS 地址;你验证证书链、解出 signedPayload、按 notificationType 加 subtype 处理、用 200-206 快速应答;需要「现状」时一律调 App Store Server API,而不是依赖事件发生时的快照。
第 1 步 — 先定两个端点,再动 App Store Connect
生产环境需要一个 HTTPS 地址,沙箱环境可选第二个(Apple 明确允许两个环境共用同一个地址)。服务器必须支持 TLS 1.2 或更高。地址里若带端口,只能是 443 或 1024 及以上——https://example.com:1234/notifications 是合法形状,低特权端口不行。如果入口挂在 IP 白名单后面,把 Apple 的 17.0.0.0/8 网段加进去,沙箱与生产同此一条。以上均见 Enabling App Store Server Notifications。
第 2 步 — 按环境分别开启版本 2
在 App Store Connect 打开你的 App,进入 App 信息 > App Store 服务器通知。生产与沙箱各自独立配置,且每个环境可单独选择版本 2 或已废弃的版本 1——这正是为了让你能在生产先跑新格式、而(例如提审期间)旧格式仍然必须可用,或反过来。Apple 给出的迁移顺序是:先在沙箱环境验证版本 2,再切生产。开始排错前先知道一件事:TEST 通知永远以版本 2 格式下发,与你配置的地址所期望的版本无关。
第 3 步 — 用对状态码,然后异步干活
处理成功就回 200-206;没处理成功就回 50x 或 40x,这样 App Store 会重试投递。工程上的正解是:读包、验签、入队、回 200,真正的业务处理交给 worker。不要在连接里同步调自己的数据库、邮件服务或 App Store Server API。重试机制是拿来挡基础设施故障的,不是拿来兜住一个慢 handler 的——你每触发一次重试,就多一条自己去重的事件。
第 4 步 — 解开载荷
请求体是 responseBodyV2 对象,真正有用的只有 signedPayload 一个字段,它是 JWS。先解析出 header、payload、signature 三段,再对 payload 做 Base64URL 解码,得到 responseBodyV2DecodedPayload。这一层有 notificationType、subtype、version、signedDate、notificationUUID。四个数据对象里只会出现一个:data(内购事件,内含 signedTransactionInfo,订阅类还会带 signedRenewalInfo)、summary(只在 RENEWAL_EXTENSION 配 SUMMARY 子类型时出现)、appData,以及只在 EXTERNAL_PURCHASE_TOKEN 通知上出现的 externalPurchaseToken。
第 5 步 — 三段签名都要验,不能只验最外层
signedPayload、signedTransactionInfo、signedRenewalInfo 各自带独立 JWS 签名,必须逐段验证,算法取 header 里的 alg 参数。落到实现上就是校验 x5c 证书链到 Apple 公布的根证书,根证书在 Apple 证书颁发机构页面下载(该服务当前用的是 AppleRootCA-G3.cer)。不要自己手搓:Apple 提供 App Store Server Library(Python / Java / Node.js / Swift),验签与解码都已封装。只做 Base64 解码、跳过验签的 handler 等于门户大开——任何知道你通知地址的人都能给自己发一份订阅。
第 6 步 — 把 notificationType 与 subtype 映射到权益逻辑
版本 2 的枚举共 23 个通知类型,其中部分带 19 个限定子类型;两份清单见 notificationType 与 subtype。真正决定一个付费用户还能不能用的,是下面这些:
SUBSCRIBED— 子类型INITIAL_BUY、RESUBSCRIBE、UPGRADE、DOWNGRADE。开通权益并记录具体产品标识,不要只记一个「已订阅」。DID_RENEW— 子类型BILLING_RECOVERY、BILLING_RETRY。扣款出问题后补上的续订同样落在这里,按正常续期处理。DID_FAIL_TO_RENEW— 用户在宽限期内时带GRACE_PERIOD子类型。此时通常仍应保留权益,宽限期存在的意义就是如此。GRACE_PERIOD_EXPIRED— 宽限期走完。降级动作应该发生在这里,而不是扣款失败那一刻。EXPIRED— 子类型VOLUNTARY、BILLING_RETRY、PRODUCT_NOT_FOR_SALE。最后一个在你把某产品下架、但用户手上还持有时会用到。DID_CHANGE_RENEWAL_STATUS(AUTO_RENEW_ENABLED/AUTO_RENEW_DISABLED)与DID_CHANGE_RENEWAL_PREF(UPGRADE/DOWNGRADE)— 关掉自动续订的用户在本期末之前仍然有权益,所以绝不能在AUTO_RENEW_DISABLED时收权。REFUND、REFUND_DECLINED、REFUND_REVERSED、REVOKE、RESCIND_CONSENT— 收权侧。退款被撤销意味着钱回到了你这边,权益也必须还给用户。CONSUMPTION_REQUEST— Apple 在索要购买使用情况的证据,见第 7 步。OFFER_REDEEMED、PRICE_CHANGE、PRICE_INCREASE(ACCEPTED/PENDING)、RENEWAL_EXTENDED、RENEWAL_EXTENSION(SUMMARY/FAILURE)— 商务与日期变更,通常触发的是用户通知,而不是权益变更。METADATA_UPDATE、MIGRATION、ONE_TIME_CHARGE、TEST、EXTERNAL_PURCHASE_TOKEN(Apple 生成了 token 但没收到报送时带UNREPORTED子类型)。
完整清单与最新增补见通知服务变更日志——Apple 会持续加字段和取值,在假定自己的 handler 已覆盖全部之前,先回读一遍。
第 7 步 — CONSUMPTION_REQUEST 要在 12 小时内应答
客户对任意产品类型(消耗型、非消耗型、非续期订阅、自动续期订阅)发起退款请求时,Apple 会下发 CONSUMPTION_REQUEST。若客户已同意,你可以调 Send Consumption Information 把 Apple 要求的用量数据报上去,官方给出的响应时限是收到通知起 12 小时;若客户未同意,就什么都不做。两个容易搞错的点:这里需要的同意,与你在追踪/广告上收集的同意是两回事;向 Apple 共享用户数据前取得有效同意的责任在你(开发者)身上。
第 8 步 — App Store Server API 用来取「状态」,不是取「事件」
通知描述的是某一刻。写权益之前先把现状问清楚:
- Get Transaction Info:单笔交易详情,含撤销原因与撤销日期。
- Get All Subscription Statuses:某个客户订阅状态的权威视图。
- Get Refund History:按原始交易号取退款记录。
- Extend a Subscription Renewal Date:为服务中断做补偿。两个属性决定了它是一扇单向门:延长生效之后无法回撤;家庭共享成员的订阅会被自动一并延长,但不支持直接为某个家庭成员发起请求。并且延长的那段时间不计入 Apple 计算佣金率时所用的一年付费服务期。
第 9 步 — 交付前先证明这条管道真的通
调 Request a Test Notification,App Store 会向你的端点投一条 TEST 通知;该调用返回一个 token,在 6 个月内用 Get Test Notification Status 查你的服务器当时究竟回了什么。另外,如果凌晨三点你的端点正好挂了,Get Notification History 能取回 App Store 尝试投递过的版本 2 通知:生产环境保留 180 天、沙箱保留 30 天,每页 20 条,带 continuation token,且返回通知的完整内容。但要记住这份历史的性质:每条记录反映的是当时下发那一刻的状态,不是当前状态。
第 10 步 — 让这条管道长期正确的运维规则
- 写库之前先读
environment。 沙箱流量走沙箱地址;一个共用 handler 把沙箱事件写进生产权益,测试就变成了数据事故。 - 用
notificationUUID去重。 重试和你自己的重放都会让同一事件到达多次;把标识存下来,重复的丢掉。 - 不要把它当收据。 只监听
SUBSCRIBED和DID_RENEW、忽略REFUND、REVOKE、REFUND_REVERSED的实现,会永久泄漏付费权益。要么处理完整生命周期,要么交给 Apple 的库并把校验交给 Server API。 - 自己留一份事件日志。 当客户对权益有争议时,通知历史加你自己的日志才是事后复盘的依据。
- 把端点当成生产系统的一部分。 它需要监控、需要远低于连接预算的请求超时、需要一个队列——而不是一条同步调进计费系统的调用链。
来源
- Apple Developer — App Store Server Notifications:developer.apple.com/documentation/appstoreservernotifications
- Apple Developer — App Store Server Notifications V2:app-store-server-notifications-v2
- Apple Developer — Enabling App Store Server Notifications:enabling-app-store-server-notifications
- Apple Developer — notificationType 与 subtype 枚举:notificationtype / subtype
- Apple Developer — responseBodyV2DecodedPayload:responsebodyv2decodedpayload
- Apple Developer — App Store Server API:developer.apple.com/documentation/appstoreserverapi
- Apple — App Store Server Library(Python):github.com/apple/app-store-server-library-python
- Apple — 证书颁发机构:www.apple.com/certificateauthority
规模化运营开发者账号?运营侧交给我们。