Importing an audience
Bringing subscribers across from another provider — including the part most migration guides leave out.
What actually travels
Push tokens are not owned by your old provider, with one exception. That exception decides whether a migration is painless or partial, so it is worth understanding before you plan around it.
| Channel | Portable? | Why |
|---|---|---|
| Android (FCM) | Yes | Tokens are issued by your Firebase project. Any provider holding your service account can send to them. Subscribers never notice the switch. |
| iOS (APNs) | Yes | Tokens are bound to your Apple team and bundle ID. Same story. |
| Web push | Only with the original keys | A browser subscription is cryptographically bound to the VAPID key pair that created it. Without that exact pair, every send returns 403 forever. |
If you do hold the pair, import it under Settings → Push platforms → Web using Replace with my own keys — not Regenerate, which mints a new pair and disconnects everyone. We verify the two halves are actually a pair before storing them, because a mismatched pair saves cleanly and then fails every single send.
The three steps
Nothing touches your audience until you have seen what will happen. There is no undo for a bad import, so the flow is deliberately not one click.
- Stage. Upload a CSV. We parse it into a staging table, detect the format, and propose a column mapping. Your audience is untouched.
- Check. We run every row through the mapping and report what would happen — rows to import, rows already known, opt-outs, duplicates, and every rejection with its reason. Still nothing written.
- Commit. Rows are written in batches with live progress. Row identity
is
(import, line number), so an import that stops at row 30,000 resumes there rather than replaying the first 30,000.
Columns
A OneSignal export is detected automatically and its columns mapped for you. Anything else you map by hand; common names are guessed.
| Field | Notes |
|---|---|
token | Required. FCM token, APNs token, or web push subscription JSON. |
platform | ios / android / web, or a OneSignal device_type number. If missing, we infer it from the token's shape — a 64-character hex string is APNs, subscription JSON is web. |
external_id | Your own user id. Preserve it and your server-side code keeps working unchanged. |
tags | A JSON object. Keys are registered in the property catalog on import, so you can segment on them immediately rather than waiting for users to come back. |
subscribed | Opt-out state. Unsubscribed rows are imported and suppressed — never silently dropped, and never messaged. |
| Plus first name, last name, email, phone, gender, country, language and timezone. | |
Rejections
Rows are rejected rather than guessed at. Importing a token that can never be delivered to is worse than skipping it, because it inflates your audience and quietly burns quota.
| Reason | Fix |
|---|---|
| No push token | Check the mapping — the token column may be named something we didn't recognize. |
| Unrecognized platform | OneSignal device types for SMS and email are not push platforms. Remove those rows. |
| Country is not a 2-letter code | We won't truncate Nepal to Ne — that invents data. Convert to ISO codes. |
| Tags column is not valid JSON | Must be a JSON object, e.g. {"plan":"pro"}. |
Every rejected row is downloadable as a CSV with its reason and original columns — fix and re-upload. Re-importing is safe: writes are upserts on natural keys, so the same file twice converges instead of duplicating.
How imported tokens get validated
No provider lets you check whether a token is alive without sending to it, so an import cannot verify what it ingests. Your first real send does that, and cleanup is automatic:
| Provider says | What happens |
|---|---|
Unregistered / gone (APNs 410, FCM UNREGISTERED, web 410) |
Unambiguous — the device is retired immediately. |
| Wrong VAPID key (web 403 mismatch) | Marked invalid. That subscriber must resubscribe. |
| Invalid argument (FCM) | FCM uses this for both a dead token and a malformed payload. We only retire the device if another device on the same channel succeeded in that send — which proves the payload was fine. Otherwise the device is left alone. |
Limits
| 100,000 rows per file | Split larger exports; imports are additive. |
| 60MB per upload | Comfortably covers 100k rows with attributes. |