Skip to content

APNs error codes, and what to do about each

Every APNs error reason Apple documents, from BadDeviceToken and DeviceTokenNotForTopic to 410 Unregistered and the provider token errors, with fixes.

PushBy Rushi Patel, Senior iOS developerUpdated 28 October 20267 min read

When a push to api.push.apple.com fails, Apple answers with an HTTP status and a JSON body with one reason. The reason tells you what to fix. This page lists every reason Apple documents, grouped by status, with the usual cause, the fix, and whether to retry.

HTTP/2 410
apns-id: 6A7F2C10-...
{ "reason": "Unregistered", "timestamp": 1761638400000 }

A success is 200 with an empty body. An error body has reason, and on a 410 also timestamp: the time, in milliseconds, at which Apple confirmed the token was no longer valid.

The errors you will see most

reason Status Usual cause Fix
BadDeviceToken 400 A sandbox token sent to production, or the other way round; or a token stored with spaces or brackets; or an FCM token sent to APNs Send development builds to api.sandbox.push.apple.com and TestFlight and App Store builds to api.push.apple.com. Store the token as plain hex
DeviceTokenNotForTopic 400 apns-topic is not the bundle id of the app that made the token Use the app’s exact bundle id, plus .voip, .complication or .push-type.liveactivity for those push types
Unregistered 410 The app was deleted, or the token became inactive for the topic Delete the token
ExpiredToken 410 The device token has expired Delete the token; the app registers a new one when it next runs
InvalidProviderToken 403 The JWT’s Key ID (kid) or Team ID (iss) does not match the .p8 key, the key was revoked, or the signature is wrong Check the Key ID and Team ID in the Apple Developer account; sign with ES256
ExpiredProviderToken 403 The JWT is more than an hour old Make a new token every 20 to 60 minutes
TooManyProviderTokenUpdates 429 A new JWT was made for every request, or more often than every 20 minutes Make one token and reuse it
PayloadTooLarge 413 The payload is over 4 KB (5 KB for VoIP) Send less: move data to your server and send an id
TooManyRequests 429 Too many pushes in a row to the same device Wait, then retry

Every APNs error reason

From Apple’s table of response error strings:

Status reason Meaning
400 BadCollapseId The apns-collapse-id is over the maximum size (64 bytes)
400 BadDeviceToken The device token is invalid or does not match the environment
400 BadExpirationDate The apns-expiration value is invalid
400 BadMessageId The apns-id value is invalid
400 BadPriority The apns-priority value is invalid
400 BadTopic The apns-topic value is invalid
400 DeviceTokenNotForTopic The device token does not match the topic
400 DuplicateHeaders A header is repeated
400 IdleTimeout The connection was idle too long
400 InvalidPushType The apns-push-type value is invalid
400 MissingDeviceToken No device token in the request path
400 MissingTopic No apns-topic header where one is required
400 PayloadEmpty The payload is empty
400 TopicDisallowed Pushing to this topic is not allowed
403 BadCertificate The certificate is invalid
403 BadCertificateEnvironment The certificate is for the other environment
403 ExpiredProviderToken The provider token is stale; make a new one
403 Forbidden The action is not allowed
403 InvalidProviderToken The provider token is not valid or its signature cannot be verified
403 MissingProviderToken No certificate on the connection and no provider token in authorization
403 UnrelatedKeyIdInToken The token’s key id is not related to the one used first on this connection; open a new connection
403 BadEnvironmentKeyIdInToken The token’s key id does not match the environment
404 BadPath The :path is invalid; it must be /3/device/<token>
405 MethodNotAllowed The method is not POST
410 ExpiredToken The device token has expired
410 Unregistered The device token is inactive for the topic
413 PayloadTooLarge The payload is too large
429 TooManyProviderTokenUpdates The provider token is updated too often; at most once every 20 minutes
429 TooManyRequests Too many requests in a row to the same device token
500 InternalServerError An internal error at Apple
503 ServiceUnavailable The service is unavailable
503 Shutdown The APNs server is shutting down

Sandbox or production: the cause of most BadDeviceToken

A token belongs to one APNs environment:

Build Environment Send to
Run from Xcode onto a phone Development (sandbox) api.sandbox.push.apple.com
TestFlight Production api.push.apple.com
App Store Production api.push.apple.com

The aps-environment entitlement in the build decides it. A token key (.p8) works for both environments, so the same key is fine; the host is what must match. If you cannot tell which kind of token you have, try production and, on BadDeviceToken, try sandbox before deleting the token.

The provider token (JWT)

With a .p8 key, every request carries authorization: bearer <JWT>, a token you sign yourself:

  • Header: { "alg": "ES256", "kid": "<Key ID>" }
  • Claims: { "iss": "<Team ID>", "iat": <seconds now> }
  • Signed with the .p8 private key, ES256.

Four errors come from getting this wrong. InvalidProviderToken: a wrong Key ID or Team ID, or a revoked key. ExpiredProviderToken: older than an hour. TooManyProviderTokenUpdates: refreshed more than once every 20 minutes. MissingProviderToken: no header at all. Make one token, keep it in memory, and replace it after about 50 minutes.

A retry rule that holds up

Apple’s own guidance, as a rule your sender can follow:

  1. 200: done.
  2. BadDeviceToken, DeviceTokenNotForTopic, Unregistered, ExpiredToken: do not retry the token. Delete it if it is really dead; for BadDeviceToken check the environment first, and for DeviceTokenNotForTopic check your bundle id, since one wrong setting fails every token.
  3. Forbidden, PayloadTooLarge: do not retry as is.
  4. Other 4xx: fix the header, token or payload the reason names, then send again.
  5. TooManyRequests: retry after a delay.
  6. 5xx: wait, then retry with backoff. Apple suggests waiting 15 minutes before retrying a 5xx payload.

Errors when iOS goes through FCM

If your iOS app uses Firebase Messaging, your server talks to FCM and FCM talks to APNs. You then see FCM’s codes instead: a bad or missing APNs key in the Firebase project comes back as THIRD_PARTY_AUTH_ERROR, and a dead iPhone token as UNREGISTERED. See FCM error codes.

What SignOps does with these

SignOps Push can send to iOS straight to APNs with your .p8 key. With the environment set to Production and sandbox, a token that gets BadDeviceToken from production is tried on sandbox before it counts as failed. A token Apple answers with 410, Unregistered, BadDeviceToken or DeviceTokenNotForTopic is removed and your webhook gets device.removed; 429 and 5xx answers are retried. One provider token is made per key and reused for 50 minutes. The dashboard’s See what Apple said shows the reason for each device. The setup, from making the .p8 key onward, is in the iOS push guide.

Sources: Apple: Handling notification responses from APNs, Apple: Sending notification requests to APNs, Apple: Establishing a token-based connection to APNs.

Frequently asked questions

More questions? Email us and the answer goes on this page.

hello@signops.dev
What does APNs error 410 Unregistered mean?

The device token is no longer active for your app, usually because the app was deleted from the phone. The response carries a timestamp of when Apple confirmed it. Stop sending to the token and delete it, unless the app registers the same token again later than that timestamp.

Why does APNs return BadDeviceToken?

Most often a token from a development build (made with the sandbox environment) is sent to production at api.push.apple.com, or a production token to api.sandbox.push.apple.com. It also happens when the token was stored wrongly, such as with spaces or angle brackets, or when an FCM token is sent to APNs.

What causes DeviceTokenNotForTopic?

The apns-topic header does not match the app the token belongs to. The topic is the app's bundle id, with .voip, .complication or .push-type.liveactivity added for those push types. Check the bundle id in your server's settings against the one in Xcode.

How often should an APNs provider token be refreshed?

Apple rejects a JWT older than one hour with ExpiredProviderToken and rejects refreshing more often than once every 20 minutes with TooManyProviderTokenUpdates. Make one token, reuse it on every request, and make a new one every 20 to 60 minutes, for example every 50.

Which APNs errors should not be retried?

Apple says not to retry BadDeviceToken, DeviceTokenNotForTopic, Forbidden, ExpiredToken, Unregistered or PayloadTooLarge. Other 4xx errors can be retried once the cause is fixed, TooManyRequests after a delay, and 5xx errors after a wait with backoff.

Send your first push today.

Free for 1,000 push devices, 100 approvals and 1,000 passkey sign-ins a month, and 500 email codes to try. No card until you pass a free amount.