Push notifications
FCM (Android, web), APNs (iOS), and OneSignal, credentials, payloads, silent vs alert.
Push covers Firebase Cloud Messaging (Android + web), Apple Push Notification
service (iOS, iPadOS, macOS), and OneSignal (cross-platform, addressed by
external id). Their auth models differ; the per-send payload is broadly the
same. OneSignal credentials are an app_id and a REST API key, see
Providers.
FCM credentials
FCM uses an OAuth2 access token minted from a service account. MessagePipe accepts:
project_id: your-firebase-project
access_token: ya29.… (rotated by your provider service)The token is short-lived; refreshing it is your responsibility — MessagePipe does not call Google's OAuth endpoint.
APNs credentials
APNs uses token-based auth with an ES256 JWT minted from your .p8
private key:
team_id: ABCDE12345
key_id: FGHIJ67890
private_key: -----BEGIN PRIVATE KEY-----... (the .p8 contents)
bundle_id: com.acme.appMessagePipe signs a fresh JWT per send. The connection is HTTP/2.
config.environment: "sandbox" routes to api.sandbox.push.apple.com for
dev builds.
Sending a push
Push sends go to POST /v1/push/send. The body is flat (no content
wrapper, no channel field). The target is a to object holding the device
token, platform, and an optional user_id:
curl https://api.dev.messagepipe.dev/v1/push/send \
-H "Authorization: Bearer mp_live_..." \
-H "Content-Type: application/json" \
-d '{
"to": { "device_token": "<device token>", "platform": "ios", "user_id": "usr_123" },
"title": "New message from Maya",
"body": "Hey! Are you around?"
}'platform is ios or android. To render a stored template instead, drop
title/body and pass template plus a data object of variables:
{
"to": { "device_token": "<device token>", "platform": "android" },
"template": "new-message",
"data": { "thread_id": "thr_8c4e2a1f" }
}Keep payloads small (FCM limits messages to 4 KB).
A successful send returns 202 Accepted:
{
"id": "msg_01HZ...",
"status": "queued",
"channel": "push",
"template_id": null,
"queued_at": "2026-06-23T12:00:00Z",
"_links": { "self": "/v1/sends/msg_01HZ..." }
}The identifier is id. Poll GET /v1/sends/{id} for status. Optional on any
send: scheduled_for (RFC3339) and idempotency_key (or the
Idempotency-Key header); idempotency is optional.
Delivery events
A send moves through queued -> sent -> delivered | bounced | failed.
| Provider | Emits |
|---|---|
| FCM | No delivery callback. sent is implicit on HTTP 200. |
| APNs | No delivery callback. sent is implicit on HTTP 200. |
For both, “was it shown?” analytics come from the app reporting
back. MessagePipe does not synthesise opens, and emits no opened/clicked
events. Read the current status with GET /v1/sends/{id}.
Token freshness
Both platforms can reject a previously-valid device token: app uninstalled, user reset push permissions, etc. MessagePipe maps:
- FCM
UNREGISTERED/ APNsBadDeviceToken→invalid_recipient(terminal, no retry). Drop the token from your DB. - FCM
Unavailable/ APNsUnavailable→ retryable; MessagePipe will failover or back off.