Moving from OneSignal
What it actually costs, what cannot come with you, and every call mapped to its equivalent.
The shapes are similar enough that most of a port is renaming things: both products register a device token, attach it to a person, tag that person, and send to a segment. This page is the mapping, plus the two places where the similarity stops.
What travels
| What | Comes across? | Why |
|---|---|---|
| Android subscribers (FCM) | Yes | The tokens were issued by your Firebase project. Any provider holding your service account can send to them, and nobody has to re-opt-in. |
| iOS subscribers (APNs) | Yes | Bound to your Apple team and bundle id, not to a vendor. Same story. |
| Web push subscribers | Only with the original VAPID pair | A browser subscription is cryptographically bound to the key pair that created it. Without that exact pair every send is a 403, forever. OneSignal does not hand out the private half. |
| People, emails, phone numbers, tags | Yes | Rows in a CSV. A person with an address and no device is a valid import and is reachable by email or SMS immediately. |
| Campaign history and delivery logs | No | Export what you need for reporting before you close the account. Nothing imports somebody else's delivery events, here or anywhere. |
The practical shape of this: your mobile audience moves on a Tuesday and never notices. Your web audience has to re-subscribe, so keep both providers running for web until the new subscriptions have built up, or accept the loss knowingly. Importing an audience has the detail, including what to do if you do hold the VAPID pair.
Four steps
- Bring your own credentials. Upload your Firebase service-account JSON
and your APNs
.p8under Settings → Push platforms. These are the same credentials your current provider uses; notifications go out on your own keys, from your own Firebase project and Apple team. Nothing is shared and nothing is proxied through a vendor identity. - Export and import the audience. A OneSignal CSV export is
detected automatically —
identifierbecomes the push token,device_typebecomes the platform. Upload it under Audience → Import. Nothing is written until you have seen a full report of what would happen, row by row, with a reason for every rejection. - Swap the SDK. One dependency out, one in. The table below is the whole surface. Ship it, and both providers hold live tokens for the same devices — which is fine, and is what makes step four safe.
- Move the sends. Point one campaign at Notibase, compare the numbers, then move the rest. There is no cutover moment to get right: a device registered with both providers can be sent to from either, so you can move channel by channel and roll back by not sending.
Sending: the API
OneSignal's POST /notifications becomes
POST /v1/messages. Targeting moves out of a flat list of
include_* keys into one audience object, because they were always
mutually exclusive and a flat list lets you write two of them.
| OneSignal | Notibase | Notes |
|---|---|---|
included_segments: ["Subscribed Users"] | "audience": {"all": true} | |
included_segments: ["My segment"] | "audience": {"segment_id": "…"} | Segments are saved filters here too; the id comes from POST /v1/segments. |
include_subscription_ids | "audience": {"device_ids": […]} | A OneSignal subscription id is a device; the equivalent id is returned when you register one. |
include_aliases: {external_id: […]} | "audience": {"filter": {"attr": "external_id", "op": "in", "value": […]}} | |
filters: [{field, relation, value}, …] | "audience": {"filter": <AST>} | A nested boolean tree rather than a flat array with {"operator": "OR"} separators — see Audience filters. Anything you can express there you can express here. |
headings: {en: "…"} | "content": {"title": "…"} | One language per send today. Multi-language is not built. |
contents: {en: "…"} | "content": {"body": "…"} | |
subtitle | "content": {"subtitle": "…"} | iOS renders it; the others ignore it, in both products. |
url | "content": {"url": "…"} | |
data: {…} | "content": {"data": {…}} | Handed to your app untouched. |
big_picture / chrome_web_image / ios_attachments | "content": {"image": "…"} | One field for all three. Per-platform overrides still exist under content.android, content.web and content.ios when you want them to differ. On iOS an image needs the Notification Service Extension in your app, exactly as it does today. |
large_icon / small_icon | "content": {"android": {"largeIcon": …, "smallIcon": …}} | |
android_channel_id | "content": {"android": {"channelId": "…"}} | |
ios_interruption_level | "content": {"ios": {"interruptionLevel": "…"}} | Same four values. |
buttons | "content": {"buttons": […]} | Up to five — see Message content for which platforms draw them and what your app has to register. |
collapse_id | "content": {"advanced": {"collapseId": "…"}} | |
priority / TTL | "content": {"advanced": {"priority": "high", "ttl": 43200}} | |
send_after | "send_at" | An absolute instant. Prefer send_at_local + timezone whenever a person typed the time — why. |
delayed_option: "timezone" + delivery_time_of_day | "local_time": "09:00" | The same hour in every recipient's own zone. Paid plans. |
delayed_option: "last-active" | — | Not built. Send-time optimisation by last-active is not modelled. |
throttle_rate_per_minute | — | Not exposed. Delivery is paced per channel from the provider's own rate limits. |
template_id | — | Not built. Templates are on the list and are not there yet; content is written per send or generated by your own code. |
| — | "platforms": ["ios"] | No equivalent needed — restricting a send to some platforms is one field rather than a channel with empty content. |
Two things ride the same send with no OneSignal equivalent: content.inapp
writes a persistent in-app inbox item, and content.email
/ content.sms reach the same audience by address. One campaign, one audience,
five channels.
Identifying people: the SDK
| OneSignal | Notibase |
|---|---|
OneSignal.initialize(appId) | Notibase.init(context, clientKey) — Android · Notibase.configure(clientKey:) — iOS, Flutter and React Native · new Notibase({clientKey}) — web |
OneSignal.login(externalId) | Notibase.identify(externalId) · nb.identify(externalId). Both take an HMAC signature your backend mints once identity verification is switched on — see Security & keys. |
OneSignal.logout() | Nothing to call. Identity is per device and re-identifying replaces it. |
OneSignal.User.addTag(k, v) | Attributes go on the person: identify(id, {attributes: {…}}), or POST /v1/users from your server, which is the better place for anything your server already knows. See User attributes. |
OneSignal.Notifications.requestPermission() | nb.promptForPush() on web. On mobile you ask the platform yourself, or prime it with an in-app message so the one system prompt an install gets is spent on somebody who already said yes. |
OneSignal.User.pushSubscription.optOut() | nb.unsubscribe() · POST /v1/unsubscribe, which can also opt a person out across every device, address and number at once. |
| Outcomes / events | POST /v1/events · Notibase.track(name, properties). Purchases feed attribution revenue. |
What Notibase does not have
Written down here rather than discovered after a migration.
- Message templates. Not built.
- A/B testing. Not built.
- A visual journey builder. Automations cover an event, a delay and a cancelling event; anything more branching than that is your code.
- Notification categories / a preference centre. Not built. Opt-out is per channel and per app.
- RCS. Not built.
- Multi-language sends. One language per campaign.
If any of those is load-bearing for you, the honest answer is to wait or to stay.
The pricing question
The reason most people read this page in 2026, so it is worth being precise rather than promotional.
OneSignal's own billing documentation states that “on the Free plan, you must have fewer than 1,000 monthly active users (MAU) to send mobile push and in-app messages”, applying to new customers from 1 September 2026 and to existing customers from 1 October 2026. Their documentation is the authority on their own plans and it is where you should confirm this before making a decision: documentation.onesignal.com/docs/en/billing-faq. Read there on 28 August 2026; plans change, and this page will not always be the first thing to notice.
What is worth understanding is the shape rather than the number. A cap on monthly active users prices the size of your audience. A cap on messages sent prices what you actually do with it. Notibase meters sends: subscribers are unlimited on every plan including the free one, and an app with fifty thousand users who gets one notification a month is a smaller bill than an app with five thousand who gets one a day — which is the right way round, because the second app is the one using the service.
Our own numbers are on the pricing page, live from the same source the product bills against, so they cannot go stale the way a hand-typed comparison table does.