MessagePipedocs
Guides/Channels

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.app

MessagePipe 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.

ProviderEmits
FCMNo delivery callback. sent is implicit on HTTP 200.
APNsNo 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 / APNs BadDeviceToken → invalid_recipient (terminal, no retry). Drop the token from your DB.
  • FCM Unavailable / APNs Unavailable → retryable; MessagePipe will failover or back off.