Push Payload Contract
The literal notification payload Amba delivers to devices on iOS (APNs) and Android (FCM) — every field, what is guaranteed stable, and what Amba never injects.
This page is the on-device payload contract: the exact JSON your app receives when Amba delivers a push, field by field, for both platforms. It exists so you can write notification and tap handlers against a documented, stable shape instead of reverse-engineering test pushes with console logs.
The contract in one sentence: the payload contains your title, your
body, and your data keys — nothing else of yours, and nothing of ours.
What you send
Every send surface (campaigns, the test push, and per-user scheduled pushes) takes the same three content fields:
| Field | Type | Becomes |
|---|---|---|
title | string | The visible notification title on both platforms. |
body | string | The visible notification text on both platforms. |
data | object | Your custom key/value payload, handed to the app on delivery and on tap. |
Amba fans out to APNs for iOS tokens and FCM for Android tokens and builds the platform envelope below for each.
iOS — the APNs payload
Your data keys are placed at the top level of the payload, as siblings of
the reserved aps block — not nested under a data key. A campaign created
with data: { "screen": "home", "promo": "winback" } arrives on the device as:
Field by field:
aps.alert.title/aps.alert.body— always present; yourtitleandbodyverbatim.aps["mutable-content"]: 1— always present, so an iOS Notification Service Extension (rich media, decryption, badge math) is allowed to run.aps.badgeandaps.sound— included only on sends that set them; absent otherwise. Campaign and test sends today carrytitle/body/dataonly, so expect exactly the shape above.- The
apskey is reserved. If yourdataincludes a top-levelapskey it is dropped (and a warning is logged server-side) so the notification envelope can never be clobbered or spoofed from custom data.
Alongside the payload, each request is sent with apns-push-type: alert,
apns-priority: 10 (immediate delivery), and apns-topic set to your
configured bundle id. Campaign sends also set the apns-collapse-id header —
see collapse behavior below.
Android — the FCM message
Android delivery uses an FCM v1 message with a notification block (so the
system renders it when your app is backgrounded) plus your custom keys in
data:
Field by field:
notification.title/notification.body— always present; yourtitleandbodyverbatim.android.priority: "high"— always set, so delivery isn't deferred on dozing devices.android.notification.tag— set to the campaign id on campaign sends (see collapse behavior); absent on test sends.android.notification.soundandandroid.notification.channel_id— included only on sends that set them; absent otherwise (the device's default channel behavior applies).data— present only when you sent custom data; omitted entirely when you didn't.
Every data value arrives as a string
FCM requires string values in data, so Amba coerces non-string values at the
delivery boundary rather than rejecting the send. This bites typed clients:
the number you sent arrives as "42", and the object you sent arrives as JSON
text you must parse.
You send (campaign data) | The device receives |
|---|---|
"free" | "free" |
42 | "42" |
true | "true" |
{ "plan": "pro" } | "{\"plan\":\"pro\"}" |
["a", "b"] | "[\"a\",\"b\"]" |
null | "null" |
Strings pass through unchanged; everything else is JSON-stringified. If a key
can hold a non-string value, JSON.parse it in your Android handler (and only
there — on iOS the same key arrives with its original JSON type, at the top
level of the payload).
What Amba does NOT inject
This is the stability half of the contract:
- No campaign id, no segment id, no tracking keys are added to
data— on either platform, what you send is exactly what arrives (modulo the Android string coercion above). - Campaign attribution is tracked server-side, in per-delivery records you can query from the dashboard and API — analytics never ride the notification payload.
- Your tap handler can therefore rely on your own
datakeys exclusively, and the payload shape will not change underneath you. Treat any undocumented field as nonexistent; none are added today and none will be added todata.
Collapse behavior (re-sends replace, not stack)
The one place a campaign id appears in transit: campaign sends use it as the
platform collapse identifier — the apns-collapse-id request header on
iOS and android.notification.tag on Android. Its only effect is that
re-sending the same campaign replaces the previous notification in the
tray instead of stacking a duplicate. It is never inside data, and test
sends don't set it at all.
Reading data in a tap handler
There is no enforced schema for data — your keys land where each platform
puts custom data (top-level payload keys on iOS, the string-valued data map
on Android), and your notification library surfaces both through one callback.
The documented convention is a single deep_link key holding an app route
(see the data payload reference):
The full handler walkthrough lives in the SDK push guide.
Next
- Campaigns — create, target, and send.
- Setup — APNs / FCM credentials and test sends.
- Admin API — push — endpoint reference.