# Connect a push provider

> Connect Firebase Cloud Messaging, APNs, Expo Push or OneSignal to Whisperr, send a test push, and send your app's push tokens to Whisperr.

Whisperr sends push through your own push provider account. This page shows how to connect the account, test it, and send your app’s push tokens to Whisperr. Two parts must be in place:

1. **The provider account**, connected in the dashboard. Whisperr uses it to send.
2. **The device’s push token**, sent by the Whisperr SDK in your app. It tells Whisperr where to send.

## Pick a provider

Whisperr chooses the account for each device by the token’s kind. Connect the provider that sends to the tokens your app produces.

| Provider                        | Fits                               | Sends to                                                |
| ------------------------------- | ---------------------------------- | ------------------------------------------------------- |
| Firebase Cloud Messaging        | Android and Flutter apps           | FCM registration tokens (`kind: fcm`)                   |
| Apple Push Notification service | Native iOS apps                    | Raw APNs device tokens (`kind: apns`)                   |
| Expo Push                       | Expo and React Native apps on Expo | Expo push tokens, `ExponentPushToken[…]` (`kind: expo`) |
| OneSignal                       | Apps that already use OneSignal    | OneSignal users or subscriptions                        |

You can connect an APNs account and an Expo account next to another provider. Each takes over its own token kind. Firebase Cloud Messaging and OneSignal share one slot, the app’s default push account: the one you connected last sends, and the other does not. When no connected account can send to a device’s token kind, Whisperr skips that device.

## Connect the account

In the dashboard, open **Integrations** and go to **Push**. Click **Connect** next to the provider and follow the three steps:

1. **Key.** Enter the credentials listed below for your provider and click **Check key**.
2. **Check.** Whisperr checks the credentials with the provider and lists what it tested. No check sends a notification. If the provider refuses part of the key, go back and fix it.
3. **Test push.** Send one real push to a user with a device. See [Send a test push](#send-a-test-push).

Only owners and admins of the app can connect a provider. Whisperr stores the credentials encrypted and never shows a secret again. It checks each connected account again every 6 hours.

A connected provider sends Whisperr messages once sending is on for your app. Open **Messages** and use the **Sending** control: **Drafts only** sends nothing, **You approve each** holds each message for your approval, and **Sends on its own** sends without asking.

### Firebase Cloud Messaging

| Field                | Where to get it                                                                                                                                              |
| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Service account JSON | Firebase console, **Project settings → Service accounts**. The service account needs Cloud Messaging permission. Drop the JSON file on the form or paste it. |

The check proves that the key works, that the Cloud Messaging API is on and that the key can send. It shows a warning when the key holds more than send access.

To reach iOS devices through Firebase, upload your APNs key to Firebase too. The check cannot see that key. The first iOS delivery proves it.

### Apple Push Notification service

| Field                          | Where to get it                                                                                                                                                                                        |
| ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| APNs auth key (.p8)            | Apple Developer, **Certificates, IDs & Profiles → Keys**: a key with Apple Push Notifications service enabled. Drop the `.p8` file on the form.                                                        |
| Key ID                         | The 10-character ID of that key. When the file keeps Apple’s name (`AuthKey_<key ID>.p8`), the form fills it in.                                                                                       |
| Team ID                        | Your Apple Developer Team ID, from **Membership details**.                                                                                                                                             |
| Bundle ID                      | Your app’s bundle identifier, for example `com.example.app`. Whisperr sends it as the APNs topic.                                                                                                      |
| Default environment (optional) | **Production** or **Sandbox**. Whisperr uses it only for a device that did not report its environment (`push_env`). TestFlight and App Store builds use Production. Builds run from Xcode use Sandbox. |

APNs has two environments, and a device token works in one only. The SDKs send the environment with each APNs token, so one account serves both. The check tries the key in both environments. It fails only when neither accepts the key, and it shows a warning when the default environment does not.

### Expo Push

| Field                     | Where to get it                                                                                                    |
| ------------------------- | ------------------------------------------------------------------------------------------------------------------ |
| Access token              | expo.dev, **Account settings → Access tokens**.                                                                    |
| EAS project ID (optional) | The `projectId` in `app.json` (`extra.eas.projectId`). Whisperr checks that the access token can see this project. |

Expo sends to Apple and Google with the push credentials stored in your Expo project. Set them up in EAS first. The check cannot see them. The first Expo receipt proves them.

### OneSignal

| Field           | Where to get it                                                                                                                                 |
| --------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| App ID          | OneSignal, **Keys & IDs**.                                                                                                                      |
| REST API key    | An app REST API key (`os_v2_app_…`) with message-send access. Whisperr refuses organization keys (`os_v2_org_…`), because they cover every app. |
| Target users by | **External ID** (default) or **Subscription ID**.                                                                                               |

Choose how Whisperr addresses your users:

- **External ID.** Your app calls `OneSignal.login(userId)` with the same user id it passes to Whisperr’s `identify()`. Whisperr sends to that OneSignal user and holds no push tokens. Users who did not sign in cannot get push. The check cannot prove that your app calls `OneSignal.login`, so add it wherever you call `identify()`.
- **Subscription ID.** Your app sends each OneSignal subscription id to Whisperr as a push token. See [Send the push token](#send-the-push-token).

A key rotation keeps the mode.

## Send the push token

The SDK sends the token with its kind, so Whisperr can route it. Call `setPushToken` on every launch and on each token refresh. The SDK skips a token that it already sent. A token set before login goes out at once under the device’s anonymous ID, and the next `identify()` moves it to the user.

| Token                  | React Native                                                             | Flutter                                                                                                                                                                     | Swift                                                            |
| ---------------------- | ------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------- |
| FCM                    | `setPushToken({ token, kind: "fcm" })`                                   | `whisperr_firebase_messaging`, or `setPushToken(token, kind: WhisperrPushTokenKind.fcm)`                                                                                    | `Whisperr.setPushToken(fcmToken:firebaseProjectID:)`             |
| APNs                   | `setPushToken({ token, kind: "apns", pushEnv })`                         | `registerFirebaseMessaging(messaging, useApnsToken: true, apnsEnvironment: …)`, or `getAPNSToken()` and `setPushToken(token, kind: WhisperrPushTokenKind.apns, pushEnv: …)` | `Whisperr.setPushToken(deviceToken)`                             |
| Expo                   | `setPushToken(await Notifications.getExpoPushTokenAsync({ projectId }))` | —                                                                                                                                                                           | —                                                                |
| OneSignal subscription | `setPushToken({ token, kind: "onesignal_sub" })`                         | `setPushToken(id, kind: WhisperrPushTokenKind.oneSignalSubscription)`                                                                                                       | `Whisperr.setPushToken(oneSignalSubscriptionID:oneSignalAppID:)` |

For an APNs token, send the environment: `sandbox` for development-signed builds, `production` for TestFlight and the App Store. The Swift SDK finds it by itself. The React Native and Flutter SDKs cannot read it, so set it from your build configuration.

Also report the notification permission, and report taps with `trackPushOpened` so Whisperr learns which messages work. Each quickstart shows the full wiring:

- [React Native / Expo](/sdks/react-native/#push-notifications)
- [Flutter](/sdks/flutter/#push-notifications)
- [Swift](/sdks/swift/#push-notifications)

A coding agent connected to the [Whisperr MCP server](/agents/) adds the push token, permission and push-open calls for you.

## Send a test push

The last connect step sends one real push. Search for a user by name or ID and pick one with a device. **Opens** sets what a tap opens: the app’s home screen or one of your [tap targets](#what-are-tap-targets). Use your own account: install the app, sign in and allow notifications first, so the SDK sends your token.

The result lists each device as **Accepted** or **Rejected** with the reason. “This user has no device with push on yet” means Whisperr has no opted-in token for the user. Check that the app calls `setPushToken` and that notifications are allowed.

With OneSignal in External ID mode, the test push goes to the user’s external id, not to a device token. The result is one line for all of the user’s devices. It reaches the user only when the app called `OneSignal.login(userId)` with the same id and notifications are allowed. An anonymous user cannot get it.

**Accepted** means the provider took the message. It does not prove that the device showed it. Expo also reports a receipt about 15 minutes later: it says whether Apple or Google accepted the push from Expo, not whether the device got it. FCM, APNs and OneSignal report nothing after the send. Whisperr learns about opens from the SDK’s `push_opened` event.

## What are tap targets?

Tap targets are the screens a Whisperr push can open. Whisperr picks one of the links that you list and never writes a link of its own. Without tap targets, a push opens the app’s home screen.

Open **Brand & rules → Links in messages** and click **Add destination**. **Integrations → Push → Tap targets** links to the same place. A destination has these fields:

| Field                     | What to enter                                                                                                     |
| ------------------------- | ----------------------------------------------------------------------------------------------------------------- |
| Key                       | Lowercase letters, numbers and `_`, for example `paywall`. It cannot change later.                                |
| Name                      | A short name that Whisperr sees, for example `Paywall`.                                                           |
| When to use it (optional) | One sentence that Whisperr reads to choose this destination.                                                      |
| Link or app route         | An `https://` link, an app route such as `/paywall`, or a link such as `myapp://paywall`.                         |
| Image (optional)          | An `https://` image shown in the notification. On iOS, the app needs a Notification Service Extension to show it. |

The push carries the link in its data as `whisperr_deep_link`. Your app’s notification tap handler opens it. The SDKs read the same key when you call `trackPushOpened`.

Email uses the same destinations for its button, but only those with an `https://` link. See [How does an email link back to your app?](/integrations/email-providers/#how-does-an-email-link-back-to-your-app).

## Common questions

### Can I connect Firebase Cloud Messaging and OneSignal at the same time?

They share one slot, the app's default push account. The one you connected last sends. APNs and Expo accounts can sit next to either, because each takes over its own token kind.

### What happens to a device that no connected provider can reach?

Whisperr skips it. The Devices row on the Integrations page counts devices that no provider can reach.

### Does Accepted in the test push mean the device showed the notification?

No. Accepted means the provider took the message. Whisperr learns about opens from the SDK's push_opened event.

### How often does Whisperr re-check push credentials?

Every 6 hours for each connected push account.

Source: https://docs.whisperr.net/integrations/push-providers/
