Documentation

Push notifications

Send push notifications to iOS, Android, and web — token registration, topics, and broadcast targeting.

Push uses two providers: FCM (Firebase Cloud Messaging) for Android and the web, and APNs (Apple Push Notification service) for iOS. AppEngine wraps both behind the same Broadcast surface — register tokens, target devices, send.

Connect FCM and APNs

Both go through Upstream as separate vendors:

  • FCMProvider — needs a Firebase service-account JSON
  • APNsProvider — needs the team ID, key ID, bundle ID, and the .p8 auth key

Connect via:

POST/upstream/save-integrationJWT
{ "type": "FCMProvider", "config": { "serviceAccount": { /* paste JSON */ } } }
{
  "type": "APNsProvider",
  "config": {
    "teamId": "ABCD12EFGH",
    "keyId": "XYZ987",
    "bundleId": "com.acme.app",
    "authKey": "<contents of AuthKey_XYZ987.p8>",
    "environment": "production"
  }
}

Test each integration with POST /upstream/test/{integration}/sendTest.

Register a device token

The mobile app or web app obtains a token from the platform SDK (FCM getToken(), web Notification API, or APNs application:didRegisterForRemoteNotificationsWithDeviceToken:) and POSTs it to AppEngine:

POST/repository/create/push_tokenJWT
{
  "data": {
    "platform": "ios",
    "token": "<APNs token>",
    "userId": "user-abc",
    "deviceId": "iPhone-stable-uuid",
    "appVersion": "1.4.2",
    "topics": ["news", "promotions"]
  }
}

Fields:

FieldNotes
platformios | android | web
tokenProvider-specific token. APNs is hex; FCM is opaque base64.
userIdOptional — link the token to a logged-in user / customer
deviceIdStable identifier so re-registrations replace the old token
topicsList of topics this device should receive
appVersion, osVersion, modelOptional metadata

Re-register on every app launch (tokens can rotate). The endpoint deduplicates by deviceId so refreshes don't pile up.

Sending to a single device

Use the broadcast pipeline with channel: 'push' and a recipient list of token ids, or send directly through the Upstream call interface for one-off sends:

POST/upstream/call/FCMProvider/sendNotificationJWT
{
  "data": {
    "token": "<fcm token>",
    "title": "New message",
    "body": "Alice replied to your comment",
    "data": { "threadId": "abc-123", "deepLink": "/threads/abc-123" }
  }
}

Topic broadcasts

Topics are server-side mailing lists. A device subscribed to topic:news receives anything sent to that topic without you tracking individual tokens.

{
  "data": {
    "broadcastName": "Big news",
    "channel": "push",
    "type": "push",
    "topic": "news",
    "title": "Acme launches v2",
    "body": "Tap to read what's new",
    "data": { "deepLink": "/announcements/v2" }
  }
}

Topic membership is managed through the push_token record's topics array. Subscribe by updating the record; AppEngine syncs membership to FCM topic subscriptions on the next send.

APNs doesn't have topics natively — AppEngine fans the topic out into per-token sends behind the scenes.

Per-user broadcasts

For "send to every device of every user matching segment X", use the segment recipient method:

{
  "data": {
    "broadcastName": "Re-engage dormant users",
    "channel": "push",
    "type": "push",
    "title": "We miss you",
    "body": "Come back and see what's new",
    "recipientMethod": "segment",
    "selectedSegments": ["dormant-30d"],
    "data": { "deepLink": "/" }
  }
}

The pipeline expands the segment to contacts, finds every push_token linked by userId, and sends one push per token. Rate limits are FCM's per-project (~600k req/min) and APNs' per-connection (concurrent streams) — AppEngine paces the sends to stay under both.

Payload size

Both providers cap notification payloads:

  • FCM — 4 KB total
  • APNs — 4 KB for normal alerts, 5 KB for VoIP, 256 bytes for Apple Watch complications

Keep title and body short; put structured data in data and let the app render the rich UI from a deep link or local fetch.

Silent (data-only) pushes

Sometimes you want the app to wake up and refresh without showing an alert.

{ "channel": "push", "silent": true, "data": { "syncRequest": "messages" } }

The pipeline flags content_available for APNs and a data-only payload for FCM. Background execution time is short (30s typical) so the app should kick off any work and acknowledge quickly.

Failure handling

When a token is unregistered (uninstall, signed out) the provider returns an error. The pipeline marks the push_token as inactive: true and stops trying. Re-activation happens automatically when the app re-registers with the same deviceId.

Bounce reasons are recorded in the broadcast_delivery row so per-campaign analytics show how many devices were stale.

For VoIP and rich-media pushes (images, action buttons) the same pipeline is used — fields like mutableContent, category, and attachments are passed through to APNs. FCM equivalent fields go in the android.notification and webpush.notification blocks.