Delivery errors & the log explorer
Every delivery attempt is recorded with a normalized code and the raw provider
response, verbatim. GET /v1/messages/:id/deliveries or the dashboard's
Messages tab shows the full per-device timeline.
Timeline events
accepted | Target resolved into the send |
sent | Push service accepted it (APNs 200 / FCM 200 / push service 201) |
failed | See error codes below — raw response attached |
skipped | Suppressed address, missing channel content, or missing credentials |
clicked | User engaged (web today; mobile with native SDKs) |
Error taxonomy
| Code | Meaning | Retry? | Raw examples |
|---|---|---|---|
address_gone | Token permanently dead — app uninstalled, subscription expired. Notibase auto-expires the device. | No | APNs 410 Unregistered · FCM UNREGISTERED · web push 404/410 |
address_invalid | Malformed token or token from another app | No | APNs BadDeviceToken, DeviceTokenNotForTopic · FCM INVALID_ARGUMENT |
payload_invalid | Content the provider rejects (usually size — web push ~4KB) | No | APNs PayloadTooLarge · web push 400/413 |
credentials_invalid | Your APNs/FCM/VAPID credentials are wrong or revoked — fix in Setup | No | APNs 403 InvalidProviderToken · FCM SENDER_ID_MISMATCH, oauth 401 |
provider_throttled | Apple/Google asked us to slow down | Yes (backoff) | APNs 429 TooManyRequests · FCM QUOTA_EXCEEDED |
provider_down | Push service outage or network failure | Yes | Any 5xx · FCM UNAVAILABLE/INTERNAL |
unknown | Unmapped — the raw response tells the story | No | — |
The three that page you, decoded
credentials_invalid spike → your .p8 was revoked or the
FCM service account was deleted. Upload fresh credentials in Setup; queued messages
resume.
SENDER_ID_MISMATCH → the device token was minted by a
different Firebase project than the service account you uploaded. Ship builds and
credentials from the same project.
Rising address_gone rate → normal after app updates or
long-dormant re-engagement sends. It's uninstall truth, not a delivery problem — and it's
why Notibase counts stay honest.
Too lazy to decode? Same.
Every failed row in the dashboard has ✨ explain — the raw response goes to a model grounded on this exact taxonomy, and you get meaning / likely cause / fix. Cached per error class, so it's instant after the first ask.