标题:APNs(Token 认证)迁移后 iOS 生产环境推送异常(BadDeviceToken / 410 Unregistered)故障排除指南
适用范围:
- 客户端:iOS 16.7、iOS 17.1,Swift 5,UserNotifications、BackgroundTasks
- 服务端:Node.js 18,APNs JWT Auth Key(Token-Based Authentication),HTTP/2 直连 api.push.apple.com
- 错误码:BadDeviceToken、410 Unregistered
- 推送负载:包含 mutable-content 与 content-available
- 运行条件:部分设备处于公司 VPN 或低电量模式;用户近两周重装应用
目标:
- 指导定位并解决生产环境中订单通知与静默推送不达的问题
- 输出结构化、可直接发布的多语言排障内容(中文/English)
- 覆盖证书/密钥、环境区分、设备令牌生命周期、权限与网络等关键因素
————————————————————
一、问题概述(Problem)
- 现象:
- 迁移至基于 Token 的 APNs 后,生产环境的部分 iOS 设备收不到订单状态通知。
- 服务端日志显示部分请求返回 BadDeviceToken;清理数据库失效令牌后仍有设备不接收。
- 静默推送(content-available)用于后台同步不触发,用户重装应用后仍不稳定。
- 已知条件:
- 使用生产 APNs(api.push.apple.com),JWT Auth Key;Topic 与 bundle identifier 一致。
- 沙箱环境推送成功,生产失败。
- 部分设备处于公司 VPN 或低电量模式。
- 客户端已调用 registerForRemoteNotifications 并上报新 token。
————————————————————
二、诊断(Diagnosis)
请按从高概率到低概率的顺序执行,以尽快定位根因。
A. 环境与令牌(Environment & Device Token)
-
确认环境端点:
- 生产环境必须连接 api.push.apple.com。
- 沙箱环境必须连接 api.sandbox.push.apple.com。
- 若向生产端点发送沙箱令牌,将返回 BadDeviceToken。
操作:
- 在服务端日志中关联每次发送:endpoint、apns-topic、apns-push-type、token 来源(prod/sandbox/发行渠道)。
- 校验生产队列中是否混入沙箱令牌或开发构建的令牌。
-
令牌来源与生命周期:
- 设备令牌与应用、设备、环境强绑定;重装应用、恢复备份、设备 OS 升级或用户清除数据后,令牌可能变化。
- 向旧令牌发送可能返回 410 Unregistered(APNs 表示该令牌对该 topic 不再有效),应立即删除。
操作:
- 在客户端为每次应用冷启动与每次 didRegisterForRemoteNotifications 回调上报令牌,并携带“发行渠道”(App Store/TestFlight/Development,用于区分 prod/sandbox)。
- 在服务端核查数据库:是否仍保留近两周内重装用户的旧令牌;核对 last_seen 时间戳与应用版本;确认清理策略在 410 后即时生效。
-
令牌编码与格式:
- 客户端应以原始 Data 的 hex(不含空格和尖括号)上传,避免使用 description 导致格式错误。
- 服务端存储的令牌字符串长度与字符集是否一致(64 字节 hex)。
B. APNs 请求头与负载(Headers & Payload)
4) apns-push-type 必须设置:
- 用户可见通知:apns-push-type: alert(建议 apns-priority: 10)
- 静默推送:apns-push-type: background(必须 apns-priority: 5;payload 含 {"content-available":1};不应包含 alert)
操作:
- 抽样检查生产失败请求的 HTTP/2 头:apns-push-type、apns-priority、apns-topic、apns-expiration。
- 如果 content-available 与 mutable-content 同时出现且无 alert,移除 mutable-content 并将 push-type 设为 background。
-
apns-topic 与 bundle identifier:
- apns-topic 必须与应用主 bundle identifier 完全一致(不使用扩展的 bundle id)。
- 使用 JWT Auth Key 时还需确保 Team ID 与 keyId、bundleId 一致(否则会出现 InvalidProviderToken/BadTopic,但您当前报错是 BadDeviceToken/410)。
操作:
- 确认所有生产请求均使用主应用的 bundle id 作为 apns-topic。
-
合并与过期:
- 背景推送存在合并与速率限制;大量频繁 content-available 会被合并或丢弃。
- apns-expiration=0 表示仅当设备在线时立即送达,否则过期;用于后台同步时可设置合理 TTL(例如 300–900 秒)。
操作:
- 审查后台队列是否短时间内对同一设备发送多条静默推送;必要时使用 apns-collapse-id 合并。
C. 客户端权限与状态(Client Settings & State)
7) 权限与开关:
- 用户可见通知需要用户授权;静默推送不需要授权,但需启用“后台 App 刷新”且应用未被用户强制退出。
- 低电量模式会暂停后台刷新并显著降低静默推送唤醒概率。
操作(在设备上):
- UNUserNotificationCenter.getNotificationSettings 校验授权状态(alert/badge/sound 是否允许)。
- 设置→通用→后台 App 刷新:确认已开启;低电量模式关闭。
- 提醒用户不要强制退出应用(上滑清除),否则静默推送无法唤醒。
- App 构建渠道:
- App Store/TestFlight 令牌为生产;Xcode 开发/Ad Hoc 通常为沙箱。
操作:
- 在客户端注册令牌时附加构建渠道标识(例如通过编译标志或使用收据类型判断 TestFlight vs App Store),用于服务端路由到对应端点。
D. 网络与公司 VPN(Network & VPN)
9) 设备到 APNs 的连通性:
- 设备需要能与 APNs 建立出站连接:TCP 5223(首选)或 443(回退)到 Apple IP 段(17.0.0.0/8)。
- 公司 VPN/防火墙若阻断或代理此流量,会导致推送延迟或丢失。
操作:
- 与网络团队确认:设备侧允许直连 APNs(17.0.0.0/8,TCP 5223/443);不对该流量进行 MITM/代理。
- 在受影响设备上切换到蜂窝网络或非公司 Wi‑Fi 测试是否恢复。
————————————————————
三、解决措施(Resolution)
按以下步骤逐项落实:
- 强化令牌路由与清理
- 在客户端上报令牌时携带字段:deviceId(匿名标识)、appVersion、buildChannel(appStore/testFlight/development)、environment(prod/sandbox 推断)。
- 服务端持久化并以 environment+bundleId 路由到正确端点:
- prod → api.push.apple.com
- sandbox → api.sandbox.push.apple.com
- 实施 410 Unregistered 的即时清理:收到 410 后立刻软删除令牌,并通知客户端重新上报。
- 对近两周重装的用户:批量清理旧令牌,仅保留 last_seen 最新的令牌;对每个用户/设备仅保持一条有效令牌。
- 规范 APNs 请求头与负载
- 用户可见通知:
- apns-push-type: alert
- apns-priority: 10
- payload 包含 alert;若使用 Notification Service Extension 可保留 mutable-content: 1
- 静默推送(后台同步):
- apns-push-type: background
- apns-priority: 5(必需)
- payload 仅含 {"content-available":1} 与必要的业务数据;移除 alert 与 mutable-content
- 合理设置 apns-expiration(例如 600 秒),防止设备离线期间过期
- 确保 apns-topic 精确等于主应用 bundle identifier。
- 控制推送频率与合并
- 针对同一设备短期内的多次静默推送,使用 apns-collapse-id 合并,降低被系统合并/丢弃的不可控性。
- 将静默推送作为触发信号,与 BGAppRefreshTask/BGProcessingTask 配合;避免完全依赖高频静默推送完成大体量后台同步。
- 客户端稳健性改造
- 在应用冷启动与前后台切换后调用 registerForRemoteNotifications;在 didRegisterForRemoteNotifications 中立即上报令牌。
- 记录并上报 UNUserNotificationCenter 的权限快照,便于服务端分流(例如用户未授权仅发送静默推送)。
- 为静默推送添加容错:若未触发,应用下次前台/后台刷新时补偿执行同步任务。
- 加入可观测性日志(OSLog):didRegister 成功/失败、收到 remote notification(background)、BGTask 启动/完成。
- 网络与终端指导
- 与公司网络部门确认防火墙策略:允许设备对 APNs 的出站连接(TCP 5223 优先,443 作为回退;目标 IP 段 17.0.0.0/8)。
- 面向用户的支持文档:如使用公司 VPN,若收不到通知,可尝试关闭 VPN 或切换网络;关闭低电量模式以允许后台刷新。
————————————————————
四、验证(Verification)
按以下用例验证修复效果:
- 令牌与环境验证
- 在客户端重装后,观察服务端是否收到新令牌并标记为 prod(App Store/TestFlight)。
- 使用该令牌向 api.push.apple.com 发送测试通知,响应应为 200(成功)。若返回 BadDeviceToken,核查令牌来源/环境标记。
- 静默推送验证
- 构造仅含 {"content-available":1} 的负载:
- Headers:apns-push-type: background、apns-priority: 5、apns-topic: <主 bundle id>
- 设备不在低电量模式,后台 App 刷新开启,应用未被强制退出
- 期望结果:application(_:didReceiveRemoteNotification:fetchCompletionHandler:) 或等效路径触发,并启动 BGTask 执行同步。
- 观察客户端日志与服务端回执(HTTP 200)。若未触发,检查设备侧网络/VPN 与系统状态。
- 用户可见通知验证
- 发送含 alert 的推送:
- Headers:apns-push-type: alert、apns-priority: 10
- payload 包含 alert 与(可选)mutable-content: 1
- 期望:设备收到并显示通知;服务端返回 200。
- 错误回执验证
- 人工向已卸载应用的旧令牌发送一条推送,确认收到 410 Unregistered;验证服务端自动清理逻辑是否生效(令牌被标记失效,后续不再发送)。
————————————————————
五、预防(Prevention)
- 令牌管理:
- 令牌模型包含 deviceId、bundleId、environment(prod/sandbox)、buildChannel、appVersion、lastSeenAt。
- 每次收到 410 立即清理;设置定期清理任务,删除 30 天未见的旧令牌。
- 发送策略:
- 必填 apns-push-type;背景推送固定优先级 5。
- 合理设置 apns-expiration 与 apns-collapse-id,减轻合并与过期风险。
- 可观测性:
- 监控 APNs 响应分布(200/BadDeviceToken/Unregistered/…)、端点维度(prod/sandbox)、设备操作系统版本。
- 建立告警:BadDeviceToken 或 410 突增时触发排查。
- 客户端健壮性:
- 每次启动/更新后强制重新上报令牌;在设置变更(权限、后台刷新)时上报状态。
- 为静默推送建立前台补偿路径与 BGTask 回退。
————————————————————
六、升级路径(Escalation)
若完成上述步骤后仍存在生产设备无法接收:
- 收集材料:
- 失败请求的完整 HTTP/2 头(apns-topic、apns-push-type、apns-priority、apns-id、apns-expiration、endpoint)
- APNs 回执(含错误原因)、对应设备的构建渠道、令牌生成与上报时间、客户端日志(didRegister/背景接收)
- 受影响设备的网络环境(VPN/防火墙策略、端口 5223/443 可用性)
- 提交 Apple Developer Technical Support/TSI:
- 描述问题、附加最小可复现样例(一个令牌在 sandbox 成功、prod 失败的对比)
- 若涉及公司网络,附加防火墙例外策略说明与抓包/连通性测试结果
————————————————————
附:关键事实与要求摘要
- APNs 生产端点:api.push.apple.com;沙箱端点:api.sandbox.push.apple.com(HTTP/2,端口 443;可选 2197)。
- 设备到 APNs 的连接需求:出站 TCP 5223(首选)或 443 到 Apple IP 段 17.0.0.0/8。
- apns-push-type 必填(iOS 13+);alert 用于用户可见通知,background 用于静默推送。
- background 推送必须 apns-priority: 5;payload 含 content-available: 1;不包含 alert。
- 410 Unregistered 表示令牌对该 topic 不再有效(如应用卸载/令牌失效),应停止继续发送并清理。
- BadDeviceToken 常见原因:环境端点与令牌不匹配(在 prod 端点使用 sandbox 令牌)、令牌过期/格式错误、bundleId 不匹配。
————————————————————
English version (for publication)
Title: Troubleshooting Guide — APNs (Token-Based) Migration: iOS Production Push Failures (BadDeviceToken / 410 Unregistered)
Scope:
- Client: iOS 16.7/17.1, Swift 5, UserNotifications, BackgroundTasks
- Server: Node.js 18, APNs JWT Auth Key (Token-Based), HTTP/2 to api.push.apple.com
- Error codes: BadDeviceToken, 410 Unregistered
- Payload: includes mutable-content and content-available
- Conditions: some devices on corporate VPN or Low Power Mode; users reinstalled the app recently
- Problem
- After migrating to token-based APNs, some iOS devices don’t receive order status notifications in production.
- Server logs show BadDeviceToken on some requests; after cleaning invalid tokens, delivery remains unreliable on a subset of devices.
- Silent pushes (content-available) for background sync do not fire; reinstall did not stabilize.
- Diagnosis
A. Environment & Token
- Verify endpoints: production → api.push.apple.com; sandbox → api.sandbox.push.apple.com. Sending a sandbox token to production returns BadDeviceToken.
- Token lifecycle: tokens can change on reinstall/OS updates; sending to old tokens should return 410 Unregistered — remove immediately.
- Token encoding: upload hex of the raw device token (no spaces/brackets); server-side strings should be consistent (64-byte hex).
B. Headers & Payload
- apns-push-type is mandatory (iOS 13+):
- alert for user-visible notifications (apns-priority: 10 recommended)
- background for silent pushes (apns-priority: 5 required; payload includes {"content-available":1}; do not include alert)
- apns-topic must match the main app bundle identifier exactly.
- Control coalescing/expiration: set reasonable apns-expiration and use apns-collapse-id to avoid excessive silent push bursts.
C. Client Permissions & State
- User-visible notifications require user authorization.
- Silent pushes require Background App Refresh enabled and the app not force-quit; Low Power Mode reduces wake-up likelihood.
- Record build channel (App Store/TestFlight/Development) at registration to route tokens to correct endpoints.
D. Network & VPN
- Devices must reach APNs: outbound TCP 5223 (preferred) or 443 to Apple IP range 17.0.0.0/8.
- Corporate VPN/firewalls must not block or proxy APNs traffic.
- Resolution
- Strengthen token routing: store environment/buildChannel with tokens; route prod tokens to api.push.apple.com, sandbox tokens to api.sandbox.push.apple.com.
- Immediate cleanup on 410 Unregistered; keep only the most recent token per device/user.
- Normalize headers:
- alert pushes: apns-push-type=alert, apns-priority=10
- silent pushes: apns-push-type=background, apns-priority=5, payload only content-available
- apns-topic equals the app’s bundle id
- Rate control: collapse silent pushes per device; use BGTasks for actual work.
- Client robustness: re-register and upload token on cold start; log notification settings; add compensating sync on next foreground if silent push didn’t fire.
- Network guidance: ensure devices can reach APNs (5223/443 to 17.0.0.0/8); advise users to test on cellular or non-corporate Wi‑Fi.
- Verification
- Token/env: after reinstall, confirm new token marked prod; sending to api.push.apple.com should return 200. BadDeviceToken indicates env mismatch or formatting issues.
- Silent push: background push with headers (push-type=background, priority=5) should trigger background handler when not force-quit and Low Power Mode off.
- Alert push: push-type=alert, priority=10 should display notification.
- Error handling: sending to an old token should return 410; verify automatic cleanup.
- Prevention
- Token model: deviceId, bundleId, environment, buildChannel, appVersion, lastSeenAt; periodic cleanup of stale tokens; immediate removal on 410.
- Sending policy: always set apns-push-type; priority 5 for background; sensible expiration/collapse-id.
- Observability: monitor APNs responses by endpoint and OS version; alert on spikes in BadDeviceToken/410.
- Client hygiene: re-upload token at startup/update; report permission/background changes; provide fallback for silent push.
- Escalation
- Collect: full HTTP/2 headers, APNs response bodies, device build channel, token timestamps, client logs, network/VPN details.
- File an Apple DTS/TSI with a minimal repro (token succeeds on sandbox, fails on prod) and network evidence.
Key Facts
- APNs endpoints: production api.push.apple.com; sandbox api.sandbox.push.apple.com (HTTP/2 over 443; 2197 optional).
- Device connectivity: outbound TCP 5223 (preferred) or 443 to 17.0.0.0/8.
- apns-push-type mandatory; background requires priority 5 and content-available.
- 410 Unregistered → token invalid for topic; stop sending and delete.
- BadDeviceToken → mismatched environment, invalid/old token, or formatting errors.
——
发布建议:
- 将本指南以中/英双语形式发布;在内部平台附加“运维清单”(端口/IP 开放项)与“客户端集成清单”(令牌上报字段、权限日志)。