Skip to main content
Push tokens belong to your app, not to the provider that stored them. An Apple device token is bound to your bundle id and the device, an Android registration token to your Firebase project. Once the same APNs key and the same Firebase project are connected as credentials, every token you export from the previous provider keeps delivering through BuzzKit. Nobody has to open the app first. BuzzKit never talks to the previous provider. You export on their side, you import here.

Get the export

Every provider stores subscriptions differently, and some make the file harder to get than others. One guide per provider covers where the export is, which columns matter, and what to do when the provider only offers an API:

OneSignal

Dashboard CSV or the export endpoint. Recognized automatically.

Pushwoosh

Segment export to CSV with push tokens included.

Braze

API export of user profiles, flattened to one row per device.

Airship

The channel listing API, one row per channel.

Customer.io

The devices export from the People section.

Firebase or your own backend

Tokens you stored yourself, and why Expo tokens cannot move.
Any other provider works the same way: a CSV with one row per subscription, holding your user id and the device token or email address, is enough. The import asks which column is which.

What the file needs

Tokens the previous provider had already marked invalid stay invalid. Import them and BuzzKit flips them to invalid on the first delivery attempt, or leave those rows out.

Import in the dashboard

The import lives where a migration happens:
  • Right after you connect your first provider, the setup asks whether you are migrating. Import subscribers opens the import as the last setup step, Start fresh goes to the dashboard.
  • On a tenant with no subscribers yet, the Subscribers page shows Import subscribers in its header.
  • Under Settings, then Tenants, every tenant’s row menu has Import subscribers, for a second tenant or a later re-import.
Drop the file in. A OneSignal export is recognized from its columns. Any other file asks for the column holding your user id, the column holding the token or address, what that column contains, and whether the remaining columns should be kept as attributes. Before anything is written, the dialog shows how many rows will be imported, how many devices of each kind that is, and every row it will skip with the reason. Three choices shape the import: The dialog closes when the import starts, so you can keep using the dashboard. A persistent toast follows you across pages, updates after every confirmed batch, and finishes with a summary of how many subscribers are new, how many subscriptions were written, and how many rows the API refused.

Import through the API

The dialog uses POST /v1/imports, which you can call yourself with rows you normalized on your side, up to 1,000 per request:
Every row goes through the same path as identifying a subscriber and registering a subscription, so a second import of the same file changes nothing. Attributes merge into what is already there, lastSeenAt never moves an existing subscription backwards, and enabled: false imports a subscription muted. The response counts what was created, updated, unchanged and refused, and lists refused rows by index with the same error codes the single-row endpoints use. A push row for a channel with no credential fails the whole request with channel_not_connected before any row is written. Email rows never do: the address is always saved on the subscriber’s profile as the email attribute, and the email subscription is registered only once an email provider is connected. The reverse holds too: any row whose attributes.email is an address subscribes it on a tenant with email connected, exactly like identifying a subscriber, unless the row carries subscribe: { email: false }.