跳到正文

← 全部工具

给 iOS 和 Mac 开发者的免费工具

APNs 推送通知测试工具

填好载荷,选择推送类型,拖入 .p8 密钥并填写 Key ID 和 Team ID,就能得到一条 curl --http2 命令,在“终端”里把推送发到 api.sandbox.push.apple.com 或 api.push.apple.com。所有字段都会先按苹果的规则检查:4096 字节上限(VoIP 为 5120)、每种推送类型需要的 topic 后缀和优先级、后台推送的 content-available,以及实时活动的各个键。签名在你的浏览器里完成:密钥不上传、不保存。

密钥只留在本页

  • •.p8 密钥由你自己的浏览器读取并签名,不会上传;使用期间本页不发出任何网络请求。
  • •不保存任何内容:没有 Cookie,也不写入本地存储。刷新页面后,密钥、设备令牌和载荷都会清除。
  • •浏览器本身无法向 APNs 发送请求,所以本页给你一条在“终端”里运行的 curl 命令。其中的提供方令牌最长可用一小时,别分享给他人。

请求头名称、推送类型、topic 后缀、优先级、载荷键和大小限制均摘自苹果 APNs 文档,核对日期 2026-10-30。

从示例开始

1. 发往哪里

APNs 服务器

设备令牌只能用于与应用签名方式相符的服务器。把开发环境的令牌发到生产服务器会得到 BadDeviceToken。

didRegisterForRemoteNotificationsWithDeviceToken 给出的字节(或实时活动的推送令牌),写成十六进制。空格和 < > 会被去掉。

显示提醒、播放声音或设置角标。

你的 Bundle ID,加上该推送类型需要的后缀。切换推送类型时,后缀会随之替换。

0 表示只尝试一次、不存储;未来某个时间表示在此之前反复尝试;留空则按苹果的默认存储策略。

值相同的通知会在设备上相互替换。最多 64 字节。 0 / 64 字节

一个小写 UUID,APNs 会在响应中原样返回。留空则由 APNs 生成。

2. 用什么方式连接 APNs

把 AuthKey_XXXXXXXXXX.p8 文件拖到这里

也可以打开文件,或在下面粘贴文件内容。Key ID 会按文件名替你填好。

10 个字符,在“证书、标识符和描述文件 > 密钥”中显示在密钥旁边。

10 个字符,在开发者账户的“会员资格详细信息”中查看。

苹果规定:提供方令牌最长有效一小时。更新频率不要高于每 20 分钟一次,也不要低于每 60 分钟一次;在同一连接上更换过于频繁会得到 TooManyProviderTokenUpdates。

3. 载荷

提醒
提醒文字
角标和声音
sound
分组与投递
实时活动
自定义键

苹果会忽略放在 aps 里的自定义键,所以它们放在顶层,例如 {"gameID": "12345678"}。

4. 检查并发送

载荷大小

65 / 4096 字节

按压缩后 JSON 的 UTF-8 字节数计算,也就是 curl 实际发送的内容。一个汉字占 3 字节,大多数表情占 4 字节。

需要修正

  • •请填写 apns-topic(你的 Bundle ID,部分推送类型还需加后缀)。
  • •请填写设备令牌。在此之前,命令会从 $DEVICE_TOKEN 读取。
  • •请填写 Key ID。
  • •请填写 Team ID。

在“终端”里运行的 curl 命令

curl -i --http2 \
  --header 'apns-push-type: alert' \
  --header 'apns-priority: 10' \
  --header "authorization: bearer $AUTHENTICATION_TOKEN" \
  --data '{"aps":{"alert":{"title":"Hello","body":"This is a test push."}}}' \
  "https://api.sandbox.push.apple.com:443/3/device/$DEVICE_TOKEN"

尚未签名:命令会从 $AUTHENTICATION_TOKEN 读取令牌。

macOS 自带的 curl 支持 HTTP/2。收到 200 和 apns-id 响应头表示 APNs 已接受推送,但不保证一定送达设备。

载荷(格式化)

{
  "aps": {
    "alert": {
      "title": "Hello",
      "body": "This is a test push."
    }
  }
}

载荷(压缩后,实际发送)

{"aps":{"alert":{"title":"Hello","body":"This is a test push."}}}

APNs 返回错误时

响应正文是 JSON,例如 {"reason": "BadDeviceToken"}。苹果列出的原因如下:

400 BadCollapseId
apns-collapse-id 超过 64 字节。
400 BadDeviceToken
设备令牌无效,或属于另一个服务器(沙盒与生产混用)。
400 BadExpirationDate
apns-expiration 的值无效。
400 BadMessageId
apns-id 不是有效的 UUID。
400 BadPriority
apns-priority 的值无效。
400 BadTopic
apns-topic 无效。
400 DeviceTokenNotForTopic
设备令牌属于另一个应用或 topic。
400 DuplicateHeaders
有请求头重复出现。
400 IdleTimeout
连接空闲时间过长。
400 InvalidPushType
apns-push-type 的值无效。
400 MissingDeviceToken
路径中没有设备令牌。
400 MissingTopic
缺少 apns-topic,而证书对应多个 topic。
400 PayloadEmpty
请求正文为空。
400 TopicDisallowed
不允许向这个 topic 推送。
403 BadCertificate
证书无效。
403 BadCertificateEnvironment
证书属于另一个服务器(沙盒与生产混用)。
403 ExpiredProviderToken
提供方令牌过期(iat 超过一小时),请重新签名。
403 Forbidden
不允许该操作。
403 InvalidProviderToken
提供方令牌无效或签名无法验证:Key ID、Team ID 或密钥不对。
403 MissingProviderToken
既没有使用证书,也缺少 authorization 请求头。
403 UnrelatedKeyIdInToken
令牌所用密钥与该连接首次推送所用的密钥无关,请新建连接。
403 BadEnvironmentKeyIdInToken
该密钥只限用于另一个环境(沙盒与生产)。
404 BadPath
:path 无效。
405 MethodNotAllowed
只允许 POST。
410 ExpiredToken
设备令牌已过期。
410 Unregistered
设备令牌对这个 topic 已不再有效,通常是应用被删除了。请停止向它发送。
413 PayloadTooLarge
载荷超过 4096 字节(VoIP 为 5120)。
429 TooManyProviderTokenUpdates
提供方令牌更换太频繁。每 20 分钟最多更换一次。
429 TooManyRequests
连续向同一设备令牌发送过多推送,请稍后重试。
500 InternalServerError
APNs 内部错误,请稍后重试。
503 ServiceUnavailable
APNs 暂不可用,请稍后重试。
503 Shutdown
这台 APNs 服务器正在关闭,请重新连接。

它是怎么工作的

  • •载荷表单覆盖苹果的 aps 键:alert(文字或本地化键)、badge、sound(含紧急提醒字典)、thread-id、category、content-available、mutable-content、interruption-level、relevance-score、target-content-id、filter-criteria,以及实时活动的键(event、timestamp、content-state、stale-date、dismissal-date、attributes-type、attributes)。自定义键与 aps 并列。也可以直接粘贴写好的载荷。
  • •显示的大小是压缩后 JSON 的 UTF-8 字节数,也就是 APNs 计算的方式。一个汉字占 3 字节,大多数表情占 4 字节,所以看起来不长的消息也可能超限。
  • •请求头按苹果的列表生成:apns-push-type、带正确后缀的 apns-topic(.voip、.complication、.push-type.liveactivity、.push-type.widgets、.location-query、.voip-ptt 等)、符合各类型规则的 apns-priority(后台推送必须为 5)、apns-expiration、apns-collapse-id(最多 64 字节)和 apns-id。
  • •使用令牌方式时,本页用 Web Crypto 和你的 .p8 密钥签名 JWT,头部为 {alg: ES256, kid},声明为 {iss: Team ID, iat},与 App Store Connect 令牌页使用同一个签名器。苹果接受的令牌最长一小时,且更换频率不要高于每 20 分钟一次。
  • •使用证书方式时,命令用 --cert 和 --key 指向你填写的文件。浏览器无法自己发送这个请求(APNs 需要服务器发起的 HTTP/2,并且拒绝网页直接访问),所以要在“终端”里运行命令。

它做不到的事

  • •它不会发送推送。curl 命令由你自己运行;本页不发出任何网络请求。
  • •它不保存密钥、令牌或载荷。刷新页面就全部清除。
  • •它无法判断设备令牌是否真实、是否属于你的应用、是否与所选服务器匹配,这些由 APNs 回答(BadDeviceToken、DeviceTokenNotForTopic)。
  • •它只检查 content-state 和 attributes 是否为 JSON,不检查是否与你的 Swift ActivityAttributes 类型一致。
  • •它不涉及实时活动频道的广播推送,也不涉及频道管理接口。

常见问题

BadDeviceToken、TopicDisallowed、InvalidProviderToken 是什么意思?
BadDeviceToken(400):设备令牌无效,最常见的原因是把开发环境的令牌发到了生产服务器,或反过来。TopicDisallowed(400):不允许向这个 apns-topic 推送,例如 Bundle ID 不属于你的团队。InvalidProviderToken(403):JWT 无效或签名无法验证,通常是 Key ID 或 Team ID 填错,或者密钥与 kid 不对应。苹果的完整原因列表在本页 curl 命令下方。
沙盒和生产 APNs 服务器有什么区别?
api.sandbox.push.apple.com 用于从 Xcode 安装、aps-environment 为 development 的应用;api.push.apple.com 用于 TestFlight、Ad Hoc 和 App Store 版本。每个版本拿到的设备令牌只能用于对应的服务器。密钥可以两边通用,也可以只限其中一个,取决于你创建时的设置。
推送通知的载荷最大能有多大?
除 VoIP 外,所有推送类型都是 4096 字节;VoIP 为 5120 字节。限制按实际发送的 JSON 正文的 UTF-8 字节数计算,所以中文和表情比字符数看起来更占空间。超过上限时 APNs 返回 413 PayloadTooLarge。
提供方令牌多久换一次?
至少每小时换一次,因为 iat 超过一小时的令牌会被 APNs 拒绝(ExpiredProviderToken);但不要比每 20 分钟一次更频繁,因为在同一连接上更换太勤会得到 TooManyProviderTokenUpdates。两次更换之间,所有推送共用同一个令牌。
为什么后台推送收不到?
检查苹果要求的三点:apns-push-type 为 background;apns-priority 为 5(10 属于错误,不填也等于 10);aps 字典里只有 "content-available": 1,没有 alert、badge 或 sound。即便如此,系统也把后台推送当作低优先级,可能延后处理;苹果建议每小时不超过两三次。
apns-topic 应该填什么?
alert 和 background 推送填 Bundle ID。其他推送类型要加后缀:VoIP 加 .voip,手表复杂功能加 .complication,实时活动加 .push-type.liveactivity,小组件加 .push-type.widgets,控件加 .push-type.controls,位置推送加 .location-query,“按下即说”加 .voip-ptt,File Provider 加 .pushkit.fileprovider。切换推送类型时,本页会替换后缀。

相关工具