Skip to content

← All tools

Free tool for iOS and Mac developers

APNs push notification tester

Fill in the payload, pick the push type, drop your .p8 key with its Key ID and Team ID, and get a curl --http2 command that sends the push to api.sandbox.push.apple.com or api.push.apple.com from Terminal. Every field is checked against Apple's rules first: the 4096-byte limit (5120 for VoIP), the topic suffix and priority each push type needs, content-available for background pushes and the Live Activity keys. Signing happens in your browser: the key is not uploaded or stored.

Your key stays on this page

  • •The .p8 key is read and used for signing by your own browser. It is never uploaded, and this page makes no network requests while you use it.
  • •Nothing is saved: no cookies, no local storage. Reloading the page forgets the key, the device token and the payload.
  • •A browser cannot send to APNs itself, so the page gives you a curl command to run in Terminal. The provider token in it works for up to an hour; keep it to yourself.

Header names, push types, topic suffixes, priorities, payload keys and size limits are copied from Apple's APNs documentation, checked on 2026-10-30.

Start from an example

1. Where it goes

APNs server

A device token only works on the server that matches how the app was signed. Sending a development token to production gives BadDeviceToken.

The bytes from didRegisterForRemoteNotificationsWithDeviceToken (or a Live Activity push token), written as hexadecimal. Spaces and < > are removed for you.

Shows an alert, plays a sound or sets the badge.

Your bundle ID, plus the suffix the push type needs. Changing the push type swaps the suffix for you.

0 means try once and do not store. A future time means keep trying until then. Leave blank for Apple's normal storage.

Notifications with the same value replace each other on the device. At most 64 bytes. 0 of 64 bytes

A lowercase UUID that APNs repeats in its answer. Leave blank and APNs makes one.

2. How you sign in to APNs

Drop your AuthKey_XXXXXXXXXX.p8 file here

Or open the file, or paste its text below. The Key ID is filled in from the file name for you.

10 characters, shown next to the key under Certificates, Identifiers & Profiles > Keys.

10 characters, shown under Membership details in your developer account.

Apple: a provider token is valid for up to one hour. Make a new one no more than once every 20 minutes and no less than once every 60 minutes; changing it more often on one connection gives TooManyProviderTokenUpdates.

3. The payload

Alert
Alert text
Badge and sound
sound
Grouping and delivery
Live Activity
Your own keys

Apple ignores your own keys inside aps, so they go at the top level, for example {"gameID": "12345678"}.

4. Check and send

Payload size

65 of 4096 bytes

Counted as UTF-8 bytes of the minified JSON, the form curl sends. Chinese characters take 3 bytes, most emoji 4.

Fix these

  • •Enter apns-topic (your bundle ID, plus a suffix for some push types).
  • •Enter the device token. Until then the command reads it from $DEVICE_TOKEN.
  • •Enter the Key ID.
  • •Enter your Team ID.

curl command for Terminal

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"

Not signed yet: the command reads the token from $AUTHENTICATION_TOKEN.

macOS's own curl supports HTTP/2. A 200 answer with an apns-id header means APNs accepted the push; delivery to the device is not guaranteed.

Payload (pretty)

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

Payload (minified, as sent)

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

If APNs answers with an error

The answer body is JSON such as {"reason": "BadDeviceToken"}. Apple's list of reasons:

400 BadCollapseId
apns-collapse-id is longer than 64 bytes.
400 BadDeviceToken
The device token is not valid, or it belongs to the other server (sandbox vs production).
400 BadExpirationDate
apns-expiration is not a valid value.
400 BadMessageId
apns-id is not a valid UUID.
400 BadPriority
apns-priority is not a valid value.
400 BadTopic
apns-topic is not valid.
400 DeviceTokenNotForTopic
The device token belongs to a different app or topic.
400 DuplicateHeaders
A header appears more than once.
400 IdleTimeout
The connection was idle too long.
400 InvalidPushType
apns-push-type is not a valid value.
400 MissingDeviceToken
The path has no device token.
400 MissingTopic
apns-topic is missing and the certificate covers more than one topic.
400 PayloadEmpty
The body is empty.
400 TopicDisallowed
You may not push to this topic.
403 BadCertificate
The certificate is not valid.
403 BadCertificateEnvironment
The certificate is for the other server (sandbox vs production).
403 ExpiredProviderToken
The provider token is stale (iat more than an hour old). Sign a new one.
403 Forbidden
The action is not allowed.
403 InvalidProviderToken
The provider token is not valid or its signature cannot be verified: wrong Key ID, Team ID or key.
403 MissingProviderToken
No certificate was used and the authorization header is missing.
403 UnrelatedKeyIdInToken
The token's key is not related to the key used for the first push on this connection. Open a new connection.
403 BadEnvironmentKeyIdInToken
The key is limited to the other environment (sandbox vs production).
404 BadPath
The :path is not valid.
405 MethodNotAllowed
Only POST is allowed.
410 ExpiredToken
The device token has expired.
410 Unregistered
The device token is no longer active for this topic, usually because the app was deleted. Stop sending to it.
413 PayloadTooLarge
The payload is over 4096 bytes (5120 for VoIP).
429 TooManyProviderTokenUpdates
The provider token changed too often. Change it at most once every 20 minutes.
429 TooManyRequests
Too many pushes to the same device token in a row. Wait and retry.
500 InternalServerError
An error inside APNs. Retry later.
503 ServiceUnavailable
APNs is unavailable. Retry later.
503 Shutdown
This APNs server is shutting down. Reconnect.

How it works

  • •The payload builder covers Apple's aps keys: alert (text or localized keys), badge, sound (including the critical-alert dictionary), thread-id, category, content-available, mutable-content, interruption-level, relevance-score, target-content-id, filter-criteria and the Live Activity keys (event, timestamp, content-state, stale-date, dismissal-date, attributes-type, attributes). Your own keys go beside aps. You can also paste a finished payload.
  • •The size shown is the UTF-8 byte count of the minified JSON, which is what APNs measures. A Chinese character is 3 bytes and most emoji are 4, so a message can look short and still go over.
  • •Headers follow Apple's list: apns-push-type, apns-topic with the right suffix (.voip, .complication, .push-type.liveactivity, .push-type.widgets, .location-query, .voip-ptt and the others), apns-priority with the per-type rules (background must be 5), apns-expiration, apns-collapse-id (64 bytes at most) and apns-id.
  • •For token sign-in, the page signs a JWT with header {alg: ES256, kid} and claims {iss: Team ID, iat} using Web Crypto and your .p8 key, the same signer as the App Store Connect token page. Apple accepts a token for up to an hour and wants a new one no more than once every 20 minutes.
  • •For certificate sign-in, the command uses --cert and --key with the files you name. A browser cannot send the request itself (APNs needs HTTP/2 from a server, and blocks web pages), so you run the command in Terminal.

What it does not do

  • •It does not send the push. You run the curl command yourself; the page makes no network requests.
  • •It does not keep keys, tokens or payloads. Reloading the page clears everything.
  • •It cannot tell whether a device token is real, belongs to your app or matches the server you chose. APNs answers that (BadDeviceToken, DeviceTokenNotForTopic).
  • •It checks that content-state and attributes are JSON, not that they match your Swift ActivityAttributes type.
  • •It does not cover broadcast pushes to Live Activity channels or the channel management API.

Questions

What do BadDeviceToken, TopicDisallowed and InvalidProviderToken mean?
BadDeviceToken (400): the device token is not valid, most often because a development token was sent to production or the other way round. TopicDisallowed (400): you are not allowed to push to that apns-topic, for example a bundle ID outside your team. InvalidProviderToken (403): the JWT is not valid or its signature cannot be verified, usually a wrong Key ID or Team ID, or a key that does not match the kid. The full table of Apple's reasons is on this page under the curl command.
What is the difference between the sandbox and production APNs servers?
api.sandbox.push.apple.com is for apps installed from Xcode with the development aps-environment; api.push.apple.com is for TestFlight, Ad Hoc and App Store builds. Each build gets device tokens for its own server only. A key can work for both or be limited to one, depending on how you created it.
How big can a push notification payload be?
4096 bytes for every push type except VoIP, which allows 5120 bytes. The limit counts the bytes of the JSON body as sent, in UTF-8, so non-Latin text and emoji use it up faster than the character count suggests. Over the limit, APNs answers 413 PayloadTooLarge.
How often should I make a new provider token?
Apple wants a fresh token at least once an hour, because APNs rejects a token whose iat is more than an hour old (ExpiredProviderToken), and no more than once every 20 minutes, because changing it too often on one connection gives TooManyProviderTokenUpdates. Reuse one token for all pushes in between.
Why does my background push not arrive?
Check the three things Apple requires: apns-push-type background, apns-priority 5 (10 is an error, and leaving the header out means 10), and an aps dictionary with only "content-available": 1, no alert, badge or sound. Even then the system treats background pushes as low priority and may hold them; Apple suggests no more than two or three an hour.
Which apns-topic do I use?
Your bundle ID for alert and background pushes. Other push types add a suffix: .voip for VoIP, .complication for watch complications, .push-type.liveactivity for Live Activities, .push-type.widgets for widgets, .push-type.controls for controls, .location-query for location pushes, .voip-ptt for Push to Talk and .pushkit.fileprovider for File Provider. The page swaps the suffix when you change the push type.

Related