Push relay error codes (push-relay)
Answered by the central push relay (W3-RLY-01, ARCHITECTURE section 6) as
application/problem+json (code, title, status, optional detail). The detail never
contains device tokens, keys, caller details or payload content. The test
every_relay_code_is_documented in relay/crates/push-relay-core/src/error.rs keeps this page
and the code in step.
A push that reached APNs or FCM is never an error: POST /v1/push answers 200 with a result
of accepted, invalid_token, rejected or failed, plus the upstream status and reason code
(for example BadDeviceToken or UNREGISTERED). The codes below are for requests the relay
refused before sending anything. Every refused push releases its idempotency key, so the PBX may
retry it with the same key.
Push results (W3-RLY-02)
Every push the relay sends upstream ends in exactly one result, returned in the push answer,
stored for 30 days in relay_deliveries, readable later with POST /v1/receipts/query, and
counted in mpbx_relay_pushes_total{platform,kind,result}. upstream_status and
upstream_reason are those of the last attempt.
result | Meaning | Typical upstream answers | Retried by the relay | What the PBX does (W3-PUSH-01) |
|---|---|---|---|---|
accepted | APNs or FCM accepted the push for delivery. Not yet proof that the phone got it: that is the receipt (delivered_at). | APNs 200; FCM 200 | — | Keep ringing; record the push id; mark delivered when a receipt arrives. |
invalid_token | The device token is dead. The relay marks the device invalid; later pushes answer RELAY-DEVICE-INVALID until the app registers a new token. | APNs 400 BadDeviceToken, 400 DeviceTokenNotForTopic, 410 Unregistered; FCM 404, UNREGISTERED, SENDER_ID_MISMATCH; FCM INVALID_TOKEN_FORMAT (local check) | no | Mark the device degraded, tell the user, ring other devices. |
rejected | APNs or FCM refused the push for another permanent reason (payload, topic, our credentials after one refresh). Retrying the same push cannot help. | APNs 400 other reasons, 403 ExpiredProviderToken / InvalidProviderToken twice; FCM 400 INVALID_ARGUMENT, 401 twice, THIRD_PARTY_AUTH_ERROR twice; relay-local provider_token, oauth_sign, oauth_failed (4xx) | provider/OAuth token refreshed once | Ring other devices; operators see mpbx_relay_pushes_total{result="rejected"}. |
failed | Transient failures until the retry budget ran out (3 attempts, 5 s for calls, 15 s for notifications). | 429, 500, 503; FCM QUOTA_EXCEEDED, UNAVAILABLE, INTERNAL; relay-local connect, timeout, oauth_connect, oauth_body | yes, with jittered backoff and Retry-After | Retry within the ring window with the same idempotency key, ring other devices in parallel. |
upstream_reason is always an upstream or relay reason code from this table (at most 64
characters), never payload content.
Delivery receipts (W3-RLY-02)
The app acknowledges each push with POST /v1/receipts {push_id, receipt} (payload fields p
and r). A valid acknowledgement answers 204, also when it is a replay (it is recorded once).
The PBX reads results and receipts with the signed POST /v1/receipts/query {push_ids} (at most
200 ids); ids of other instances, unknown ids and ids past the 30-day retention are absent.
| Code | HTTP | Meaning | What to do |
|---|---|---|---|
RELAY-AUTH-MISSING | 401 | A signature header (x-mpbx-instance or x-mpbx-public-key, x-mpbx-timestamp, x-mpbx-nonce, x-mpbx-signature) is missing or malformed. | Use mpbx-push-relay-client, which signs every request (mpbx-relay-sig-v1). |
RELAY-AUTH-SKEW | 401 | The request timestamp is more than 300 s away from the relay clock (the detail gives the relay time), or it is older than the relay's replay epoch: the request was signed before a region failover or a Valkey restart (W10-RLY-01). | Fix the PBX host clock (NTP). After a failover, sign the request again (the PBX does on every retry). |
RELAY-AUTH-REPLAY | 401 | The nonce was already used by this instance within the last 10 minutes: a replayed or duplicated request. | Send each request with a fresh nonce (the client does). Retrying a push uses the same idempotency key, not the same nonce. |
RELAY-AUTH-INVALID | 401 | The signature does not verify, or the instance id is unknown (the two are not distinguished on purpose). | Check that MPBX_RELAY_INSTANCE_KEY is the key that was enrolled. If the key was replaced without a rotation, enroll again (the new key gets a new instance id); rotate with POST /v1/instances/rotate-key to keep the devices. A key rotation whose proof of the new key does not verify also answers this code. |
RELAY-ENROLL-CODE-REQUIRED | 403 | This relay accepts enrollments only with an activation code (MPBX_RELAY_ENROLLMENT=code_required). | Ask MonoPbx for an activation code and run the manual activation. |
RELAY-ENROLL-CODE-INVALID | 403 | The activation code is unknown, already used or expired. Codes are single use. | Ask for a new code (push-relay activation-code create on the relay). |
RELAY-ENTITLEMENT-EXPIRED | 403 | The entitlement was revoked and its 30-day grace period is over. Push is refused explicitly, never dropped silently. | Contact MonoPbx. During the grace period pushes keep flowing and every answer carries x-mpbx-entitlement: grace; ends=.... |
RELAY-BAD-REQUEST | 400 | The body is not valid JSON for the endpoint, or a field is invalid (for example a call push without call_uuid, a token of the wrong shape, an iOS push without the matching token). The detail names the field. | Fix the request. |
RELAY-BODY-TOO-LARGE | 413 | The body is larger than 16 KiB. | Requests are small; this is a client bug. |
RELAY-DEVICE-NOT-FOUND | 404 | The push_ref does not exist for this instance (it may belong to another instance, which is not revealed), or the endpoint does not exist. | Register the device again and store the new push_ref. |
RELAY-DEVICE-INVALID | 410 | APNs or FCM reported the device token as invalid earlier, so the relay stopped using it. | The app must send a fresh token; register it again (same device_id keeps the push_ref). |
RELAY-DEVICE-LIMIT | 409 | The instance already has the maximum number of registered devices (20 000 by default). | Delete devices that are gone (DELETE /v1/devices/{push_ref}). |
RELAY-IDEMPOTENCY-CONFLICT | 409 | The idempotency key was already used with a different request body. | Use a new key for a different push (the client uses <call_uuid>:<kind>:<push_ref>). |
RELAY-IDEMPOTENCY-IN-FLIGHT | 409 | A request with the same idempotency key is still being sent. | Wait for the first answer; retry after Retry-After. |
RELAY-RATE-LIMITED | 429 | The per-instance push rate (20/s, burst 200), device operations (600/min) or enrollment attempts (10/min per address) were exceeded. | Retry after Retry-After. |
RELAY-QUOTA-DAILY | 429 | The per-instance daily hard limit (100 000 pushes per UTC day by default) was reached. | Retry after Retry-After (next UTC day) or contact MonoPbx. |
RELAY-QUOTA-DEVICE | 429 | More than 30 pushes to one device in a minute. | Retry after Retry-After; check the PBX for a push loop. |
RELAY-PLATFORM-UNAVAILABLE | 503 | The relay has no APNs (iOS) or FCM (Android) credentials configured, or no Web Push key-encryption key (MPBX_RELAY_WEB_PUSH_KEK_PATH, browsers). Also printed by push-relay serve when a configured key cannot be loaded. | Operator: set MPBX_RELAY_APNS_* or MPBX_RELAY_FCM_* (docs/guides/push-relay.md). |
RELAY-WEB-ENDPOINT-REFUSED | 422 | A browser (web) device registration whose Web Push endpoint the relay will not POST to (W6-RLY-WEB, SSRF guard): not https, an IP address instead of a host name, user information or a port other than 443 in the URL, a host that is not one of the known push services (MPBX_RELAY_WEB_PUSH_HOSTS), or a subscription key of the wrong size. The same check runs again before every push, and a host that resolves to a private, loopback or link-local address is refused then. | PBX: tell the user this browser's push service is not supported (calls still ring while MonoPbx is open). Operator: add the push service's host suffix to MPBX_RELAY_WEB_PUSH_HOSTS only if it is a real public push service. |
RELAY-RECEIPT-INVALID | 401 | The receipt token does not match the push id, or the push id is not a relay push id. Tokens are bound to one push. | App: send the p and r values of the push exactly as received. |
RELAY-RECEIPT-EXPIRED | 410 | The push is more than 25 hours old; it can no longer be acknowledged. | Nothing; drop the acknowledgement. |
RELAY-RECEIPTS-UNAVAILABLE | 503 | The relay has no receipt key (MPBX_RELAY_RECEIPT_KEY_PATH), so pushes carry no receipt token. | Operator: configure the key (docs/guides/push-relay.md). The PBX treats accepted as the last known state. |
RELAY-UNAVAILABLE | 503 | The relay database or Valkey is unavailable. | Retry; the PBX keeps ringing live devices meanwhile (ARCHITECTURE 11.3). |
RELAY-INTERNAL | 500 | An unexpected relay error (logged on the relay). | Retry; report if it persists. |