Carillon docs
Migration

Migrate from OneSignal

Export mobile subscriptions, import devices, and switch each updated app to Carillon without overlapping sends.

We recommend keeping OneSignal running for app versions that have not migrated. An updated version registers with Carillon before disabling its OneSignal subscription. You can operate both services during rollout, with a separate audience for each.

Before you start

  • Create a Carillon app and upload its APNs or FCM credential. Use the same iOS Bundle ID, Apple team and Firebase project as the existing app.
  • You will need access to OneSignal, your notification backend and the mobile app source.
  • It helps to inventory segments, scheduled campaigns, deep links, tags and notification preferences. These are not transferred by the device CSV.
  • We recommend starting with a small test cohort and checking background, foreground and tap behavior on both platforms and on an older app version. Imported tokens alone do not provide Carillon open tracking or guarantee that existing notification handlers display its payloads.

1. Export mobile subscriptions

In OneSignal, open Audience → Subscriptions, select the required columns and export. Export subscriptions, not just user identifiers. See OneSignal's export guide.

You should import only currently subscribed iOS and Android push records with a provider token. Web push, email, SMS, Fire OS and Huawei subscriptions are outside this migration path. We recommend keeping the original file privately for reconciliation; it contains device identifiers.

2. Convert the file

Carillon accepts its own CSV schema, not an unchanged OneSignal export. Unzip the export locally if needed. Check the CSV headers first: dashboard and API exports use different names.

Dashboard export

For an export with push_token, channel and subscribed, use this mapping:

OneSignalCarillonConversion
push_tokentokenAPNs or FCM token; exclude empty values
channelplatformiOSios; Google Androidandroid; exclude other channels
external_idexternal_idOptional; keep your existing user identifier
timezone_idtimezone_idOptional IANA zone; do not substitute a numeric offset
language_codelocaleOptional valid BCP 47 tag
Your iOS build environmentenvironmentproduction for App Store/TestFlight; sandbox for development builds

The rows eligible for conversion are those where subscribed is Yes, channel is iOS or Google Android, and push_token is non-empty. Subscribed email rows are still outside this migration path. If these headings or status values differ, you should check the export settings before converting.

device describes the hardware and OS; use channel to select the platform. Neither player_id nor onesignal_id is a provider token or a replacement for your own external_id. The export does not establish the APNs environment: confirm it from the app build configuration. Android uses production.

API export

For the CSV export API, the equivalent fields are identifiertoken, device_type (0ios, 1android), external_user_idexternal_id, and languagelocale. timezone_id keeps its name. Exclude other device types and empty tokens. Keep only invalid_identifier=f (subscribed); t means unsubscribed. Request notification_types if you need the detailed status.

Request an export with an agent

An agent can request, download and convert the export using your OneSignal App ID and App API key. Provide the key through a local environment variable or secret manager, not chat. Carillon MCP authentication does not grant access to OneSignal. Run the commands in a private working directory outside version control.

With ONESIGNAL_APP_ID and ONESIGNAL_APP_API_KEY already set:

umask 077
curl --fail-with-body --silent --show-error \
  --request POST \
  "https://api.onesignal.com/players/csv_export?app_id=${ONESIGNAL_APP_ID}" \
  --header "Authorization: Key ${ONESIGNAL_APP_API_KEY}" \
  --header 'Content-Type: application/json' \
  --data '{"extra_fields":["external_user_id","timezone_id","notification_types"]}' \
  --output onesignal-export.json

A successful response contains csv_file_url, pointing to a .csv.gz archive. The agent should:

  1. Save that URL privately and poll it with a bounded delay until the file is ready. A temporary 404 means generation may still be running; retry the download, not the export request. Stop after a chosen timeout and report the pending export.
  2. Download the archive without forwarding the OneSignal API key to the download host. Decompress locally and parse it with a CSV parser, not comma splitting.
  3. Apply the API field mapping and subscription filter above. Report counts of kept, excluded and invalid rows without printing tokens or personal identifiers.
  4. Confirm the target Carillon app and APNs environment, then follow the import procedure when authorized to import.

API export limits and filters

  • One export can run at a time per OneSignal account. Finish downloading it before requesting another.
  • Generation is approximately 2,000 records per second; this is an estimate, not an SLA.
  • The generated file expires after three days.
  • segment_name limits the export to a segment. include_unsubscribed=false only filters subscriptions when segment_name is supplied; always check row status.
  • last_active_since selects by last session, not by last modification. It is not a reliable change feed for token or preference updates.

These constraints come from the export endpoint reference. It does not specify a daily export quota or a paid-plan requirement; check access with the actual account rather than assuming unlimited exports.

The general rate-limit guide also advises keeping export files below 100 GB. Follow the stricter single-export rule above. On 429, respect Retry-After and back off; stop on 400, 401 or 403 and fix the request or credentials. Do not retry indefinitely.

Subscription status

Subscription eligibility depends on the status at export time, not just the presence of a token. unsubscribed_at records a past unsubscribe; a subscription may have resubscribed since then. That date alone is not a reason to exclude a currently subscribed row. See OneSignal's status fields. We recommend using a fresh export and accounting for preference changes after the snapshot; an old file does not establish today's subscription state or token validity.

Carillon's importer has no opt-out column and new imported devices default to opted in. You should therefore exclude unsubscribed users before importing. Unknown columns, including OneSignal status fields and tags, are ignored. Tags need a separate mapping through the SDK or device API.

Use the CSV format and upload commands. There is no carillon import --from onesignal command.

3. Import and inspect the report

Upload the converted file with your Carillon app's secret key. Wait for the import to finish, then check created, already_present, invalid and ignored_columns. Fix rejected rows and upload them again. Provider credentials and imported tokens must refer to the same mobile app.

Importing does not disable OneSignal or send a notification. During this staged rollout, imported-only devices should stay outside Carillon campaigns while OneSignal owns their sends.

4. Release both SDKs, then hand over each device

Follow the Swift, Kotlin or React Native integration guide. Test native notification delegates, Firebase messaging services and tap handlers together; do not assume two SDKs compose automatically. Preserve token refresh forwarding for whichever provider still owns sends.

Carillon swizzles nothing: its SDK receives tokens, notifications and taps only through the delegate and service calls you forward to it explicitly. OneSignal's swizzled delegate calls through to the original implementation, so both SDKs keep working when your own delegate forwards to Carillon. Verify that on a physical device for each platform, in the background, in the foreground and on tap, before the first cohort ships; a simulated trace does not prove it.

We recommend the following handover sequence for each installation eligible to receive push:

  1. Preserve the user's notification preference before changing OneSignal's status.
  2. Register the device with Carillon and confirm a successful server registration. Calling SDK initialization alone is not confirmation; check its registration result or device ID and the dashboard. Keep OneSignal active if registration fails.
  3. Disable that installation's OneSignal push subscription with the installed SDK's OneSignal.User.pushSubscription.optOut() method.
  4. Confirm the old subscription is unsubscribed before marking this device ready for Carillon campaigns. Persist the handover state and retry incomplete handovers after a restart or network failure.

OneSignal's optOut() changes its subscription status, not the OS notification permission. logout() alone is not a replacement. See the OneSignal mobile SDK reference.

This migration opt-out is different from a user refusal. Your app should track the user's actual preference separately from the migration state and preserve existing opt-outs. Calling OneSignal optIn() on every startup after handover would re-enable the old subscription.

There is no atomic transaction across the two services. A backend-controlled handover flag, a paused campaign window and a pilot rollout make failures easier to reconcile. Carillon does not provide this cross-provider handover controller.

5. Keep the two sending audiences separate

A first SDK registration changes the Carillon device's source from import to sdk. It keeps the same device row when the app and token match. However, source=sdk only proves registration: it does not prove that OneSignal opt-out completed.

You can use an application-controlled device tag after confirmed handover, for example push_provider=carillon, or target explicit device IDs from your backend's handover records. A Carillon audience can require both conditions:

{
  "filters": [
    { "field": "source", "value": "sdk" },
    { "field": "tag", "key": "push_provider", "value": "carillon" }
  ]
}

Use this as a saved audience definition. OneSignal sends only to installations that have not handed over. Apply the same rule to transactional sends, automations and scheduled campaigns, not just manual broadcasts. Do not use a user-level flag when that user has devices on different app versions.

6. Repeat imports while older versions remain

You can import a fresh eligible export whenever needed. Existing tokens are counted as already_present and left untouched, including SDK-updated fields and handover tags. New tokens are added as source=import; they are not immediately part of the Carillon handover audience. Token rotation can create a new row, even for the same user.

An import is additive, not a preference sync: an existing device is not updated when its OneSignal status changes, and a row missing from the next export is not deleted. Real opt-outs need separate reconciliation; an old snapshot does not establish current consent.

7. Retire OneSignal

We recommend tracking remaining eligible installations and your supported app versions. Dormant devices may never update, so a cutoff policy is usually more practical than waiting for a database count to reach zero. Imported-only devices need separate verification before you route their sends through Carillon without an app update.

Once the remaining population has migrated or is covered by that policy, disable the old campaigns, journeys and backend sends. Remove the OneSignal SDK, services, extensions and configuration from a later app release, keeping anything Carillon still needs. Test push and taps again, then retire the OneSignal credentials and account when no supported installation depends on them. Removing an SDK from a new release does not remove it from already installed older versions.

We recommend having a rollback plan during the pilot: restoring OneSignal sending requires reconciling both subscription state and the Carillon audience so the two do not overlap.

Target existing user IDs

If your OneSignal sends use include_external_user_ids or include_aliases.external_id, keep the same IDs in Carillon's device external_id and send with audience.external_ids, up to 2000 per request. One user can have several devices. The list is resolved within the app selected by your secret key, and unknown IDs are ignored. See user ID targeting.

Replace the country filter

OneSignal detects a subscription's country and offers it as a segment filter. Carillon does not infer a country from the locale, the timezone or the IP address. Set a country tag from your own user record instead, with setTags() at sign-in and whenever the record changes, and filter on it.

not_in matches devices that do not carry the tag at all. A segment such as "every country except France and Belgium" must combine not_in with exists: true on the same tag, otherwise every device that never set country is included:

{
  "filters": [
    { "field": "tag", "key": "country", "not_in": ["FR", "BE"] },
    { "field": "tag", "key": "country", "exists": true }
  ]
}

See audience filters.

Map targeting and scheduling

OneSignalCarillon
include_external_user_ids, include_aliases.external_idaudience.external_ids, up to 2000 per request
included_segmentsaudience: { "all": true } for everyone, or audience.audience_id for a saved audience
include_subscription_ids, include_player_idsaudience.device_ids, up to 100 per request
send_aftersend_at, up to 30 days ahead; see scheduling
throttle_rate_per_minutedelivery_rate, in notifications per second, capped by the app's ceiling
DELETE /notifications/:idPOST /v1/messages/{id}/cancel

Map notification options

OneSignalCarillon
headings, contents, subtitleBase title/body/subtitle and payload.localizations
url, app_urlA key of your own in data, such as data.url, read in onOpened. Carillon never opens a URL itself
big_picture, ios_attachmentspayload.image; add the iOS extension. See image formats
large_iconNo equivalent
collapse_id, thread_idpayload.collapse_id, payload.thread_id
android_groupthread_id groups on iOS and in the SDK's foreground display on Android; Android notifications rendered by the system are not grouped
android_channel_idpayload.android.notification.channel_id; create the channel in your app
ios_sound, android_soundpayload.sound; default when omitted. Android 8+ uses the channel's sound
ios_badgeType: SetTopayload.badge
ios_badgeType: IncreaseNo increment operation: badge sets an absolute value. Send the unread count and reset it in your app
ttl, prioritypayload.ttl in seconds (default 72 hours), payload.priority (high or normal)

Map delivery statistics

GET /v1/messages/{id} returns the trace described in delivery.

OneSignal GET /notifications/:idCarillon trace
successfuloutcomes.sent
failed, erroredoutcomes.failed, with error_code on each delivery
convertedoutcomes.opened
remainingremaining
platform_delivery_statsplatforms.ios and platforms.android
receivedNo equivalent. Neither Apple nor Google confirms arrival on the handset; sent means the provider accepted the notification

We recommend replacing the OneSignal service extension with Carillon's extension when you switch iOS image handling. Test foreground, background and tap behavior after a native rebuild. Use onReceived to suppress banners for your own in-app UI, and onOpened to route using your own data keys. Carillon does not navigate to a URL automatically.

If the subscription ID was your identity

Some integrations used the OneSignal subscription or player ID as a stable identifier for the installation, sometimes as a login factor. Store the Carillon device ID after registration and observe changes with deviceId() and onDeviceIdChanged (getDeviceId in React Native). The SDK preserves it across token rotations using an installation proof, but it is a public, mutable push address: it travels in registration responses, can be read by anyone with the app, and changes after a reinstall. It is not an authentication factor. Issue your own secret to the installation, store it in the platform keychain or keystore, and authenticate with that. See devices.

On this page