Send your first push

End-to-end: upload credentials, register a device, send a test, ship a campaign.

You'll need

An Apple Developer account (for iOS) or a Firebase project (for Android), your app's credentials, and your app running with the SDK installed.

1. Upload credentials

iOS — APNs .p8

  1. 1

    Generate the key

    developer.apple.com → Keys → +. Enable Apple Push Notifications service (APNs). Download the .p8 (you only get one shot).

  2. 2

    Note the Key ID and Team ID

    Key ID is on the same screen. Team ID is in the top-right of the Apple Developer site.

  3. 3

    Upload to userGist

    During onboarding push setup, or in App settings → Push, upload the .p8, Key ID, Team ID, and Bundle ID. Use sandbox for development builds or production for TestFlight and App Store builds.

Android — FCM service account

  1. 1

    Generate the service-account JSON

    Firebase Console → Project settings → Service accountsGenerate new private key. Download the JSON.

  2. 2

    Upload to userGist

    App settings → Push → Android → Upload JSON. userGist parses the file and shows the resolved project ID — confirm it matches your google-services.json.

Push credentials page in app settings.
App settings → Push — both APNs and FCM ready.

2. Wire the SDK

Connect these examples after SDK initialization to your app's existing notification permission flow. You choose when and where permission is requested. If permission and token handling already exist, forward their results to UserGist; no additional permission prompt or screen is needed. Push needs both OS notification permission and UserGist push consent; the initial SDK installation leaves push consent off. Enable only the push purpose after the user opts in.

// After the user opts in to push in your app
await UserGist.setConsent({ push: true })
const permission = await UserGist.enablePush()
if (!permission.granted) await UserGist.setConsent({ push: false })

Flutter: the APNs token may not be ready immediately after permission is granted. Retry registration once it is available and forward refreshed tokens; see Firebase's token setup. Match the iOS environment to the signed build's aps-environment entitlement when using a custom build configuration.

On iOS, also add the Notification Service Extension as described in SDKs → iOS. Delivery analytics rely on it.

Existing permission and a missing saved preference

When adding UserGist to an app that already sends notifications, read the existing app-owned preference as three states: absent, true, or false. Do not create a new key or turn an absent value into false. The SDK's initial push: false is not evidence that the person opted out in your app.

Saved app preferenceCurrent OS permissionLaunch/resume behavior
AbsentFully authorizedPersist true in the existing preference, sync only UserGist push consent to true, then register the device token without requesting permission again.
falseAny state, including authorizedPreserve the explicit opt-out; keep UserGist push consent off and do not register with UserGist.
trueAuthorizedRestore push consent and register the current token without another permission request.
Absent or trueDenied, not determined, or unavailableKeep UserGist push consent off and preserve the app preference; leave permission requests in your existing user-initiated flow.

Provisional or ephemeral permission alone is not proof of a prior app opt-in. Preserve your app's existing policy for these states rather than treating them as full authorization for this migration.

Use a presence-aware read in the app's existing storage and account scope. On iOS, UserDefaults.bool(forKey:) returns false for a missing key, so inspect presence separately and account for registered defaults. In React Native and Expo, preserve an absent storage value instead of coercing it to a Boolean. With Android SharedPreferences, check contains(key) before getBoolean. With Flutter SharedPreferences, preserve the nullable getBool result instead of using ?? false.

For an iOS user whose preference is absent and permission is already .authorized, save the app's opt-in, call UserGist.shared.setConsent(Consent(push: true)), then call UIApplication.shared.registerForRemoteNotifications() on the main thread. Forward its token through your existing AppDelegate's didRegisterForRemoteNotificationsWithDeviceToken callback to UserGist.shared.push.didReceiveDeviceToken(deviceToken). Do not call requestPermission() in this restoration path.

Reconcile after SDK initialization and on app resume. Serialize this work against opt-outs and account changes so an old permission callback cannot restore consent for the wrong state. Token or network failures must not overwrite the app's saved preference; retry registration when ready. Inspect the actual stored state before concluding that a missing preference caused a registration problem.

Saved credentials do not confirm delivery

Saving a credential checks its format and stores it. Confirm push delivery for your app with its own credentials, permissions, registered token, and a notification you actually receive.

3. Confirm your device is registered

In onboarding push setup, find and select your signed-in or anonymous app user, then refresh device status. You can also use Audience → Users → your test user → Push. Check that push consent is approved and an active registered device appears.

If it's missing:

  • Make sure the OS permission dialog was accepted.
  • Make sure UserGist push consent is enabled and the SDK forwards the current device token.
  • If permission was already allowed, check the existing preference migration. User approved reflects UserGist's recorded push consent, while Device registered requires a registered token; OS permission alone does not mark either check complete.
  • iOS: the bundle ID in the APNs credential must match your build's bundle ID exactly.
  • Android: the google-services.json project ID must match the uploaded service-account JSON.

4. Send a test push

From onboarding push setup, choose your own test user, enter a title and body, and press Send test. Later, use the same sender in the user's profile. The test targets that user's active devices; it does not create a campaign. Put the app in the background and check for the notification. A dispatched result does not confirm receipt; if sending fails, check the displayed reason and retry after fixing it.

Title: Hello from UserGist
Body:  Your first test push is here.

5. Build a real campaign

  1. 1

    Write the message

    Title + body. Add an optional image and a destination such as acme://orders/UG-1042. Use Insert field for a saved name or event value; the picker creates the matching field definition.

  2. 2

    Pick a channel

    Android: pick a channel users can opt out of (e.g. marketing). iOS: default category.

  3. 3

    Trigger

    Triggered on order_shipped, or Scheduled for a one-shot announcement.

  4. 4

    Segment

    Pick or create a segment (e.g. Active in last 30 days, opted into marketing).

  5. 5

    Send / publish

    Send now for one-shot, Publish for triggered.

For a greeting or a continue watching push, follow Personalize messages. It covers source selection, Preview as user, and JSON tap actions that pass a string show ID and numeric playback position to your React Native app. Resolve the chosen recipient before test sending, then verify the tap opens the correct destination.

6. Watch the funnel

Open the campaign detail page. The funnel shows Sends → Delivered → Displayed → Opened → Clicked. Drop-off between Sends and Delivered usually means invalid tokens; userGist auto-invalidates them.

Push campaigns list — funnel + per-platform breakdown live inside each campaign.
Push list. Open a campaign to see its Sends → Delivered → Opened → Clicked funnel.

Common pitfalls

  • Wrong APNs environment. Register development tokens as sandbox; TestFlight and App Store tokens as production. The token environment determines the APNs destination. See Apple's APNs environment reference.
  • No NSE on iOS. Without the Notification Service Extension, you'll see Sends and Opened but no Delivered — the OS gave it to your app without telling us.
  • Firebase project mismatch. Service-account JSON and google-services.json must come from the same Firebase project.

What's next