# A/B Testing
Source: https://documentation.onesignal.com/docs/en/ab-testing
Optimize your push and email messaging with OneSignal's A/B testing to improve engagement and campaign performance.
A/B Testing helps you optimize messages by testing up to **10 variants** and evaluating performance based on metrics like open rates and click-through rates. This allows you to improve user engagement and make data-driven messaging decisions.
Marketers focused on Lifecycle and Growth can use insights from A/B tests to make impactful improvements that align with broader business goals. Whether you’re testing push notifications or emails, A/B testing allows you to experiment with different designs, copy, calls-to-action (CTAs), and more. For example, test whether:
* A push with an image outperforms text-only
* A CTA like “Claim Offer” works better than “Get Started”
* A short subject line gets more opens than a longer one
***
## Plan availability
* **Pro & Enterprise Plans**: Up to **10 variants**
* **Free & Growth Plans**: **2 variants**
[Compare Plans](https://onesignal.com/pricing)
***
## How A/B Testing works
A/B testing is only available when sending through the dashboard. It is not available with the API.
To create A/B tests with Journeys, use [Journey Split Branches](./journeys-overview#split-branches).
When creating [push](./push) and [email](./email-messaging) campaigns through the dashboard, click the **A/B Test** button and **Add Variant** to create additional tests.
Your Audience (included and excluded segments) will be all the users eligible for the campaign.
After you create the variants, you can select a portion of your audience to receive randomized variants i.e. 25% means 25% of the segment(s) selected will randomly receive one of the variants. A message with 2 variants (A & B) targeting 25% will have 12.5% of the audience get variant A and another 12.5% get variant B. A message with 10 variants (A-J) targeting 25% will have 2.5% of the users get each variant.
By default, 25% of your audience receives the A/B test. For valid results, each variant must receive enough users. The more variants you use, the larger your test group must be.
If you set 100%, the message will be sent evenly to all users in the audience and eliminates the ability to send the "winner" to remaining users.
## Select a Winner
Messages that are sent as A/B tests will be marked as such under the **Messages Tab**. Click into the report for each test to see the full report or view the variant-specific reports.
We provide some statistics for you to view the performance and choose a winner. The below screenshot describes how to view different stats and select a winning variant. We will then send the winner variant to all the remaining members of your target audience.
***
## Platform-specific instructions
### Create a Push A/B Test
1. Go to **Messages > Push > New Push**
2. Name your message (e.g., "Push AB Test - CTA Button")
3. Select your segment(s)
4. Click the **A/B Test** button
5. Add variants
* Click **Add Variant** to duplicate and edit each new version.
* Only change **one variable** at a time for meaningful insights.
6. A/B Test settings
Select the percentage of your target audience that should receive each variant. See [How A/B Testing works](#how-a%2Fb-testing-works) for more details.
The percentage is applied to the total audience, evenly distributed across each variant. Examples:
* 25% of a message with 2 variants will sent 12.5% to each variant.
* 25% of a message with 10 variants will sent 2.5% to each variant.
* 50% of a message with 2 variants will sent 25% to each variant.
* 50% of a message with 3 variants will sent 16.67% to each variant.
* 100% of a message with 2 variants will sent 50% to each variant.
* 100% of a message with 4 variants will sent 25% to each variant.
Any percentage other than 100% will allow you to select a winner.
7. Review results
View results under **Messages > Push > A/B Tests Tab**. Click any test to view variant-specific reports.
8. [Select a Winner](#select-a-winner)
Use performance metrics to manually select a winner.
### Create an Email A/B Test
1. Go to **Messages > Email > New Email**
2. Name your message (e.g., "Email AB Test - Subject Line")
3. Select your segment(s)
4. Click the **A/B Test** button
5. Add variants
* Click **Add Variant** to duplicate and edit each new version.
* Only change **one variable** at a time for meaningful insights.
6. A/B Test settings
Select the percentage of your target audience that should receive each variant. See [How A/B Testing works](#how-a%2Fb-testing-works) for more details.
The percentage is applied to the total audience, evenly distributed across each variant. Examples:
* 25% of a message with 2 variants will sent 12.5% to each variant.
* 25% of a message with 10 variants will sent 2.5% to each variant.
* 50% of a message with 2 variants will sent 25% to each variant.
* 50% of a message with 3 variants will sent 16.67% to each variant.
* 100% of a message with 2 variants will sent 50% to each variant.
* 100% of a message with 4 variants will sent 25% to each variant.
Any percentage other than 100% will allow you to select a winner.
7. Review results
View results under **Messages > Email > A/B Tests Tab**. Click any test to view variant-specific reports.
8. [Select a Winner](#select-a-winner)
Use performance metrics to manually select a winner.
***
## Best practices for A/B Testing
### Understand benchmarks
Review past performance data so you can evaluate test success meaningfully.
### Set a goal and hypothesis
Clearly define what you're testing and what success looks like.
### Change one variable at a time
Control your experiment by isolating variables like:
* Subject lines
* Layouts
* CTA copy
* Length of copy
* Images
* Offers
* Emojis
* Colors
* Fonts
* Icons
* GIFs
### Use controls
Include your "usual" version as a baseline for measuring improvements.
Create control groups by tagging users randomly and exclude that segment. You can [Export user data](./exporting-data) and create a segment from the CSV [import](./import).
### Test simultaneously
Send all variants at the same time to avoid timing bias.
### Continue testing
Iterate based on results to continuously optimize message performance.
***
## FAQ
### Can I A/B test different segments?
Not within the standard message form. However, you can test different segments using [Journeys](./journeys-overview) with **Split Branches** and **Yes/No Branches**.
### Can I automatically select a winner?
Not yet. You must manually choose the winning variant or use 100% to send all variants.
***
You're done! You can now test different variants of your messages and select a winner.
Need help?
Chat with our Support team or email `support@onesignal.com`
Please include:
* Details of the issue you're experiencing and steps to reproduce if available
* Your OneSignal App ID
* The External ID or Subscription ID if applicable
* The URL to the message you tested in the OneSignal Dashboard if applicable
* Any relevant [logs or error messages](/docs/en/capturing-a-debug-log)
We're happy to help!
***
# Action buttons for push notifications
Source: https://documentation.onesignal.com/docs/en/action-buttons
Add interactive action buttons to your push notifications on iOS, Android, and the web. Learn setup via Dashboard & API, supported platforms, click handling, icons, and event tracking.
This guide applies only to push notifications. For in-app messages, see [In-App Messages: How to add Click Actions](./iam-click-actions).
Action Buttons let you add multiple, labeled actions to a single push notification, so users can respond without opening your app or site first. Depending on the operating system and device, users reveal buttons by expanding the notification (long press, swipe + View, or an expand affordance).
***
## Add action buttons
You can configure Action Buttons in [Templates](./templates), directly when composing a message in the dashboard, or via the [API](/reference/push-notification).
### Dashboard & template setup
When creating a push, open **Advanced Options > Action Buttons**.
### API setup
* **Mobile Apps**: Use the `buttons` parameter. Pass an array of up to 3 objects with `id`, `text`, and `icon`.
* **Web (Chrome)**: Use `web_buttons`, passing up to 2 objects with `id`, `text`, `icon`, and `url`.
***
## Action button properties
* **Action ID**: A unique identifier for the specific button action. The ID of the clicked button is passed to you so you can identify which button was clicked. (e.g. 'accept-button')
* Must be unique per button.
* Available in the OSNotification Payload and can be accessed within the SDK Notification Opened Event Handler.
* API: `id` property
* **Label**: The text the button should display to your users. (e.g. 'Accept')
* API: `text` property
* **Icon**: Optional icon displayed with the button label. Not available on all platforms and operating systems. See FAQ below for details.
* Mobile apps must include the button within its image resources. See Action Button Icons below for details.
* Websites can use a valid publicly reachable URL to an icon. Keep this small because it's downloaded on every notification display. (e.g. `http://site.com/icon.png`)
* API: `icon` property
* **Launch URL 1**: The URL to open when the action button of the first button is clicked. Pass `'do_not_open'` to prevent opening any URL. (e.g. 'do\_not\_open')
* Web only
* Max 2 web buttons
* API: `web_buttons.url` property
### Action button icons
* iOS supports action button icons for iOS 15+
* Android stopped supporting action button icons for [Android N (AKA 7)](https://android-developers.googleblog.com/2016/06/notifications-in-android-n.html)
***
## Handle action button clicks
When a user taps a button, OneSignal delivers the Action ID to your app/site. You can either use the default behavior (open your app/site) or override it.
### Default behavior (Open App/Site, Then Handle)
1. The app/site opens (or is focused on web).
2. Your click/open listener receives the event with the Action ID. (See [mobile SDK reference](./mobile-sdk-reference#addclicklistener-push) or [web SDK reference](./web-sdk-reference#addeventlistener-notifications) for click listener details.)
3. Use the Action ID to track which button was clicked and handle the click event accordingly. This could mean deep linking to a specific screen in your app or triggering a custom event.
#### Prevent app launch from action button clicks
* **Android:** Follow [Android SDK setup > Disable default open behavior](./android-sdk-setup#disable-default-open-behavior). This lets you intercept clicks in a [Service Extensions](./service-extensions) and run custom logic (e.g. call an API) without opening the app.
* **iOS:** Include an `ios_category` on the notification to associate actions via the [UNNotificationCategory Object](https://developer.apple.com/documentation/usernotifications/unnotificationcategory). See Apple's [Declaring Your Actionable Notification Types](https://developer.apple.com/documentation/usernotifications/declaring_your_actionable_notification_types) for more details.
* **Web (Chrome):** Use the `_osp=do_not_open` magic string to prevent opening any URL. This is supported on Chrome and Firefox, but not supported for the Safari web browser.
***
## Supported platforms & limits
| Platform | Buttons Supported | Notes |
| ------------------------- | ----------------- | -------------------------------------------------------------- |
| iOS | Up to 4 | Icons on iOS 15+. Requires categories for background handling. |
| Android / Amazon / Huawei | Up to 3 | No button icons from Android 7+. |
| Web – Chrome | Up to 2 | Buttons and icons supported. `_osp=do_not_open` supported. |
| Web – Firefox | No buttons | `_osp=do_not_open` works for the launch URL only. |
| Web – Safari | No buttons | `_osp=do_not_open` not supported. Provide a real URL. |
Users often need to **expand** the notification to see buttons (e.g., long press on iOS, swipe + **View** on some Android OEMs).
***
## Troubleshooting
### Buttons don’t show up
* Expand the notification (long press, swipe + View, or expand).
* Verify you added Action ID and Label for each button.
* Check platform limits (e.g., only 2 buttons on Chrome).
### Clicking a button doesn’t open the browser on mobile web
If the browser is in the background or fully closed, most mobile browsers (including Chrome) will not come to the foreground or open the URL, even though click events still trigger in the service worker. This is intentional browser behavior to prevent background apps from interrupting the user.
* Most mobile browsers won’t foreground themselves from a background service worker. Clicks still fire in the worker, but the tab doesn’t open. This is intentional.
* Ensure the launch URL and the button URL are exactly identical (including trailing slashes) if you expect the tab to be focused instead of opening a new one.
### Icons don’t appear
* iOS must be 15+ for button icons.
* Android 7+ does not render action button icons.
* On web, confirm the icon URL is publicly accessible and small (fast to download).
### Why is there a close action button?
By default web push notifications on Windows 10 include the Close button. However, if you add your own action button, then this close button is removed. So, in either case the notification will remain on-screen till the user interacts with it. This is designed by Google to give the users the chance to interact with the notification.
Need help?
Chat with our Support team or email `support@onesignal.com`
Please include:
* Details of the issue you're experiencing and steps to reproduce if available
* Your OneSignal App ID
* The External ID or Subscription ID if applicable
* The URL to the message you tested in the OneSignal Dashboard if applicable
* Any relevant [logs or error messages](/docs/en/capturing-a-debug-log)
We're happy to help!
***
# Android notification categories
Source: https://documentation.onesignal.com/docs/en/android-notification-categories
Set up Android notification categories (channels) in OneSignal to improve user control and customization of push notifications.
Android notification categories (aka notification channels) were introduced in Android 8.0 (Oreo) to give users greater control over how they receive notifications from your app. This allows you to categorize your notifications and define different experiences such as display behavior, sound, vibration, badges, and lock screen visibility.
For example, if you have breaking news notifications, you can create a category for them and set "Urgent" importance and a custom sound to ensure they are displayed prominently versus less important notifications that may be muted or have a default sound.
OneSignal makes it easy to create and manage these categories directly within the dashboard. Alternatively, you can define categories programmatically in your app. See [Android’s guide to creating notification channels](https://developer.android.com/develop/ui/views/notifications/channels).
OneSignal's Android notification categories work for Google Android, Huawei Android, and Amazon FireOS.
***
## Default notification categories
OneSignal automatically creates two default categories:
### Miscellaneous
Used when you do not set a category.
* **Importance:** High
* **Sound:** Default
* **Vibration:** Default
* **Badges:** Enabled
* **Lockscreen:** Private
### Restored
Used when your app is force-quit and reopened. If your app has push notifications in the Notification Center when the app is force-quit, they are removed from the device. Reopening the app recreates (restores) those notifications. The OneSignal SDK automatically sets the category to "Restored" with the following settings to prevent unwanted sounds and pop-ups from multiple restored notifications.
* **Importance:** Low
* **Sound:** Off
* **Vibration:** Off
* **Badges:** Disabled
* **Lockscreen:** Private
If you always send push notifications with a custom category, the "Miscellaneous" channel won’t appear on user devices. The "Restored" channel will always appear to handle restored notifications after force-quitting the app.
### Huawei-specific behavior
On Huawei devices, OneSignal does **not** set a default category. If you don't include one, Huawei will apply **High** importance by default.
For badge control on Huawei devices, you can also use dedicated Huawei badge parameters (`huawei_badge_class`, `huawei_badge_set_num`, `huawei_badge_add_num`) in the [Create message](/reference/create-message) API. See [Badges](./badges#huawei-badges) for details.
***
## Create Android notification categories in OneSignal
Before you begin, make sure you have a [OneSignal app configured with an Android platform](./android-sdk-setup).
1. Go to **Settings > Push & In-App > Android Notification Channels** in the OneSignal Dashboard.
2. Click **Add Group** to organize your categories (e.g., “News Updates”, “Social Activity”).
3. Click **Add Channel** within the group to create a new category.
You’ll be asked to define the following:
### Name
*User-visible.* Keep it clear and descriptive.
### Description
*User-visible.* Briefly explain the type of notifications this category will handle.
### Importance
Controls how visible and interruptive the notification is:
* **Low:** Silent, no alerts
* **Medium:** No sound/vibration, minimal visual interruption
* **High:** Plays sound or vibrates, no screen pop-up
* **Urgent:** Plays sound and appears as a heads-up or banner-style notification.
### Sound
* **Off:** No sound
* **Default:** Device’s default notification tone
* **Custom:** Upload and reference a custom sound (no file extension).
Example: `alert_beep` (not `alert_beep.wav`)
Full setup instructions for adding custom sounds to your notifications.
### Vibration
* **Off:** No vibration
* **Default:** Uses device’s vibration pattern
* **Custom:** Define your own using a pattern (in ms).
Example: `0, 300, 500, 300` → Wait 0ms, vibrate 300ms, pause 500ms, vibrate 300ms.
### LED Color
Some Android devices support LED indicators:
* **Off:** No LED
* **Default:** Device default
* **Custom:** ARGB hex value (e.g., `FF0000FF` for blue)
### Badges
Shows badge count on app icon:
* **Enabled:** Badge is shown
* **Disabled:** No badge displayed
### Lockscreen visibility
* **Public:** Full content shown
* **Private:** Only app name, hides content
* **Secret:** No notification visible on lock screen
Once your category is created, you can use it in your notifications.
***
## Updating categories
After a device receives a notification with a category, Android locks that category’s behavior. **Changes to importance, sound, vibration, or other settings will not apply retroactively**. For example, if you send a push notification with a category using "High" importance and sound, then change the importance to "Urgent" and use a different sound file, the next push notification to that same device with that same category will not have "Urgent" importance or the new sound.
**Options**:
* **To update behavior:** Create a new category.
* **To test changes:** Clear app data or uninstall and reinstall the app.
You can update:
* Category name (shown as "channel name" in Android settings)
* Category group name (shown as "channel group name" in Android settings)
These update in Android's notification settings when the next notification is received using that category.
***
## Deleting categories
To remove a deleted category from the user’s device:
1. Delete the category from the OneSignal dashboard.
2. Ensure all notifications are cleared from the Notification Center.
3. Have the user:
* Put the app in the background for 60+ seconds
* Open it again (triggers SDK sync)
The SDK will re-sync and remove the deleted category from Android settings.
***
## Adding categories to notifications
Depending on how you created the Android Category and how you are sending the message, these are the ways you can reference the category in your push notifications.
### Sending from the OneSignal Dashboard
1. Within your Template or Push Message Composer, navigate to the Android settings.
2. Under **Category**, select your category if created within the OneSignal dashboard or select **(Created in App)** if created programmatically in your app.
* If created programmatically, also set the **Existing Channel** field to the name defined in your code.
### Sending with the REST API
If you created the category in the OneSignal dashboard, use the `android_channel_id` in the [Create message](/reference/push-notification) API request. You can find the Channel ID in the Android Category setup screen.
If using your own programmatically created Android channels, use the `existing_android_channel_id` parameter in the [Create message](/reference/push-notification) API request and set the name as defined in your code.
***
## FAQ
### Can categories play sounds in Do Not Disturb (DND) mode?
No. OneSignal does not set `setBypassDnd` on categories. To override DND, create your own channel programmatically and enable this setting. See [setBypassDnd](https://developer.android.com/reference/android/app/NotificationChannel#setBypassDnd\(boolean\)).
### Can I localize category names or descriptions?
No. OneSignal does not support multiple languages for categories. To support localization, define your own Android channels and reference them via `existing_android_channel_id` in your push API requests.
### Why is my Android category not working?
There are several reasons why your Android category may not be working as expected. To troubleshoot, check the following:
* **What is not working?**
* Is the sound file not playing?
* Is it not displaying on the device?
* Do you not see the category in the Android Notification Settings?
* **How was the category created?**
* If created in the OneSignal dashboard, make sure the settings are defined as you expect.
* If created programmatically in your app, review your code. See [Android’s guide to creating notification channels](https://developer.android.com/develop/ui/views/notifications/channels).
* **Review the category settings:**
* Make sure the settings are defined as you expect.
* Is the sound file being referenced correctly? See the [Sound](#sound) section above.
* Is the category name or ID being referenced correctly when sending the message?
* **Did you update the settings after sending a notification?**
* If you updated the settings after sending a notification, Android will not apply those updates to your device. See [Updating categories](#updating-categories) above.
* **Check OneSignal SDK initialization:**
* Make sure OneSignal is initialized in the `Application` class, not an `Activity`. See [Android SDK Setup](./android-sdk-setup).
Still need help? We're here to assist! Email `support@onesignal.com` with the above information including:
* The Android category code if created programmatically in your app
* The URL to the message in your OneSignal dashboard with the issue
We'll assist you as soon as possible!
***
## Related pages
Set up custom notification sounds for Android, iOS, and other platforms.
Configure badge counts on app icons across platforms.
Install and initialize the OneSignal Android SDK in your app.
Send push notifications programmatically using the REST API.
# Apps, Organizations, & Accounts
Source: https://documentation.onesignal.com/docs/en/apps-organizations
Add, rename, move, and delete OneSignal apps and organizations. Understand the relationship between accounts, apps, and organizations.
When you sign up at [OneSignal.com](https://onesignal.com), a user account is created and tied to your email address. With your account, you can create, manage, or be invited to multiple **Apps** and **Organizations**.
If your company already has a OneSignal App or Organization, email the account admin to request an invite. Share the [Manage Team Members](./manage-team-members) guide to help them give you access.
***
## OneSignal account structure
Your email address is used to log in to your OneSignal account. From there, you can access the **Apps** and **Organizations** in which you are a [Team Member](./manage-team-members).
### Apps vs. Organizations
* A **OneSignal App** holds user and messaging data for a single project, across all platforms (web, iOS, Android, email, etc.). Each app exists within a single Organization.
* A **OneSignal Organization** is a container for managing multiple apps, billing, and team permissions.
You can have:
* Unlimited apps (free to create)
* Multiple organizations (free or paid)
* Apps for separate environments (e.g., dev, staging, production)
* Different access levels per app or organization
***
## Access levels and roles
OneSignal supports two levels of access:
### App-level access
* Access to only the specific app(s) a user is invited to.
* Cannot view billing or upgrade plan settings.
### Organization-level access
* Access to all apps within the organization.
* Only **Admin** roles can manage billing and upgrades.
### User roles
Access can be further scoped by roles:
#### Organization Roles
| Role | Best for | Access summary |
| ----------- | -------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Admin | Developers, Owners | Full control over all org settings, billing, and messaging. Automatically includes all App Admin privileges across every app in the org |
| Finance | Finance teams | View org settings, apps, members, and billing. Edit billing. No app-level permissions |
| Operations | Ops teams | View access across all apps plus manage suppressions and sender identities |
| Editor | Marketers, PMs | Full messaging workflow: create segments, build and send messages, manage webhooks and imports. Cannot modify underlying user or subscription records, or change app settings |
| Composer | Content writers, Designers | Create and edit messages, templates, segments, and journeys. Cannot send, activate, or delete most content. No export access |
| Viewer | Analysts, Read-only users | View-only access across all apps. Cannot edit, send, or export |
| Team Member | Minimal access users | Can view the org and its apps list. No app-level permissions on its own. Access is layered on through app-level role assignments |
#### App Roles
| Role | Best for | Access summary |
| ---------- | --------------------------- | -------------------------------------------------------------------------------------------------------------------- |
| Admin | App owners, Lead developers | Full control over the app including settings, keys, integrations, and team management |
| Operations | Ops teams | View access across app features plus manage suppressions and sender identities |
| Editor | Marketers, PMs | Create, edit, send, and delete messages and related content. Manage webhooks and imports. Cannot change app settings |
| Composer | Content writers | Create and edit messages, templates, and segments. Cannot send or activate. No export access |
| Viewer | Read-only users | View-only access to app data. Cannot edit, send, or export |
Invite users, assign roles, and manage access at the app or organization level.
***
## Managing your user account
Enable [two-factor authentication](./2-step-authentication) to add an extra layer of security to your account. See [Data collection & security FAQ](./data-questions) for details.
### Reset password or email
1. Navigate to [Account Management](https://dashboard.onesignal.com/profile) or click **your email drop-down > Manage Account**.
2. Add your Email, New Password, and Confirm Password.
3. Click **Submit**.
### Delete your account
Deleting your user account only removes your email from OneSignal. It does not delete apps or downgrade paid plans. Be sure to [Downgrade your plan](./billing-faq) if needed.
To delete:
1. Navigate to [Account Management](https://dashboard.onesignal.com/profile) or click **your email drop-down > Manage Account**.
2. Scroll down to **Delete Account**.
3. If you don't have the option to delete your account, contact `ar@onesignal.com`.
***
## Manage apps
* **Create an app**:
* Log in at [onesignal.com](https://onesignal.com) and click **New App/Website**.
* Or use the [Create an App API](/reference/create-an-app).
* **Rename an app**:
* From the dashboard **All Apps** page, click **Options > Rename** next to the app.
* Or use the [Update an App API](/reference/update-an-app)
* **Find App ID**:
* See [Keys & IDs](./keys-and-ids).
* **Delete an app**:
* You can delete apps within Free organizations and under 5,000 total subscriptions from the **All Apps** page via **Options > Delete**.
* For larger apps, contact `support@onesignal.com`.
***
## Manage organizations
Enable [two-factor authentication](./2-step-authentication) at the organization level to require all team members to use 2FA.
* **Create an organization**:
* Visit the **Organizations** page or click **New Organization** in the dashboard (no paid plan required).
* **Rename an organization**:
* In **Organizations**, click **Options > Rename** next to the organization.
* **Find Org ID**:
* Go to **Organizations**, select your organization, and copy the UUID from the address bar. Example: `https://dashboard.onesignal.com/organizations/THE_ORG_ID/apps`
* **Delete an organization**:
* Go to **Organizations**, click **Options > Remove**.
* You must move the apps out of the organization before deleting it.
* If you need assistance, contact `support@onesignal.com` with the Org ID you want to delete.
### Add or move apps between organizations
* **Add app to organization**:
* Go to **Organizations > select your Organization > Move Apps Into Organization**.
* Select your apps and click **Move Apps**.
* You need:
* Admin access to the source Organization (where the app currently lives)
* Admin access to the destination Organization (where you want to move it)
* Admin access to the App itself
* The app must currently belong to a Free Organization (apps in paid organizations cannot be moved self-service).
When moving an app between two organizations, you must be an Admin on both. Being an Admin on the destination Organization and the app is not enough. You also need Admin access to the source Organization. If you are unsure who the source Organization admin is, contact `support@onesignal.com` with your App ID and we can help identify them.
If you need assistance, email `support@onesignal.com` with:
* The App ID(s) you want to move
* The Org ID to move them to.
* You must contact Support from an email with Admin access to the Apps and Organization.
***
## FAQ
### Where can I see when my app was created?
Use the [View App](/reference/view-an-app) or [View Apps](/reference/view-apps) API to get the `created_at` timestamp.
### Why do I see limitations on a paid plan?
Your app might not be assigned to the correct paid Organization. Follow [Add or move apps between organizations](#add-or-move-apps-between-organizations) to move your app to the paid org. If you need help, see the support contact details in that section.
### What are the best practices for agencies?
Agencies can manage client apps using one of two approaches:
1. **Centralized billing**
Use a single Organization to manage and pay for all client apps.
2. **Client-managed billing**
Each client sets up their own Organization and handles their own billing.
You can mix paid and free apps by assigning them to the appropriate Organization.
Need help? [Contact our Sales Team](https://onesignal.com/contact).
### How can we access analytics, messages, and users across multiple Apps?
OneSignal does not have a single cross-app dashboard view. Each app's data is accessed separately. Here are the recommended approaches for working across apps:
* **Analytics** — Use [Event Streams](./event-streams) to route message delivery and engagement data from each app to a centralized analytics platform like Snowflake, BigQuery, or Amplitude.
* **Messaging** — Use the [Create message API](/reference/create-message) to send messages to multiple apps in parallel from your backend. You can also create [Templates](./templates) once and copy them across apps using the [Copy template API](/reference/copy-template-to-another-app).
* **Users** — Use the [REST API](/reference/view-user) to query user data per app. If you need a unified view across apps, export Subscription records per app via [CSV export](/reference/csv-export) or stream events via Event Streams to an external data warehouse.
### How do we change our dashboard timezone?
Dashboard graphs display in UTC and cannot be changed. All other dates and times use your browser's timezone. To change the displayed timezone, update the timezone setting in your browser or operating system.
***
## Related pages
Invite users, assign roles, and manage access at the app or organization level.
Find your App ID, REST API key, and other credentials.
Enable 2FA for your account or require it for your organization.
Manage plans, billing, and subscriptions across organizations.
# Audit Logs
Source: https://documentation.onesignal.com/docs/en/audit-logs
Track and review user activity across your OneSignal organization for security, compliance, and accountability. Export to JSONL or query via the REST API.
## Overview
Audit Logs provide a **read-only** record of user actions across your OneSignal organization. Use audit logs to:
* **Investigate security incidents** by reviewing login activity, IP addresses, and user sessions.
* **Meet compliance requirements** by providing evidence for SOC 2, HIPAA, and internal audits.
* **Maintain accountability** by tracking who created, updated, or deleted templates, journeys, and notifications.
* **Export data** to JSONL for offline analysis, or **query the REST API** for programmatic access and SIEM integration.
Audit logs are **immutable**. You cannot edit or delete entries, ensuring an accurate and trustworthy audit trail.
### Plan availability
Audit log retention varies by plan.
| Plan | Retention Period | Notes |
| ------------ | ---------------- | ------------------------------------------------------- |
| Free | 48 hours | Included |
| Growth | 48 hours | Included |
| Professional | 48 hours | Upgrade to 90 days with Legal & Security Package add-on |
| Enterprise | 90 days | Included |
To increase retention or learn more about the Legal & Security Package, contact your Account Manager or our [Sales Team](https://onesignal.com/contact).
***
## Accessing Audit Logs
### Org Level Audit Logs
Audit logs are available at the **Organization level** and include activity across **all apps** in that organization.
**Requirements:**
* You must be an **Organization Admin**. See [Manage Team Members](./manage-team-members) to update roles.
To access audit logs:
1. Navigate to **Organizations**
2. Select your organization
3. Click **Audit Logs**
### App Level Audit Logs
In addition to organization-level audit logs, you can view audit logs for a specific app:
1. Navigate to your **App**
2. Go to **Settings**
3. Click **Audit Logs**
App-level audit logs show only activity for that specific app, making it easier to investigate app-specific changes.
**Requirements:**
* You must be an **App Admin** or **Organization Admin**
***
## Understanding the Audit Log
Each row represents a single user action. You can customize visible columns using the Columns button in the top-right corner.
| Column | Description |
| --------------- | ---------------------------------------------------------------- |
| **Date & Time** | When the action occurred |
| **User Action** | The type of action performed (e.g., Logged In, Template Updated) |
| **Email** | Email address of the user who performed the action |
| **Org Role** | The user's organization-level role (Admin, Editor, Viewer) |
| **App Role** | The user's app-level role, if applicable |
| **Item Type** | The type of object affected (Template, Notification, Journey) |
| **Item Name** | The name of the affected object |
| **App Name** | The app where the action occurred |
| **User** | Display name of the user |
| **IP Address** | The IP address from which the action was performed |
***
## Tracked events
Audit logs track user actions across your organization, including logins, template changes, notification activity, journey updates, member management, billing events, and more.
For a comprehensive list of all tracked events, see the [Complete event reference](#complete-event-reference).
***
## Viewing event details
Click the expand arrow (›) next to any event to view additional details:
Expanded details include:
| Field | Description |
| --------------- | --------------------------------------------------------------- |
| **API Version** | API version used (when applicable) |
| **App ID** | Unique identifier for the app |
| **App Name** | Name of the app where the action occurred |
| **App Role** | User's role within the specific app |
| **Browser** | User agent string (browser and OS information) |
| **Channel** | Notification channel (push, email, sms) for notification events |
| **Email** | User's email address |
| **Event ID** | Unique identifier for this specific event |
| **IP Address** | Full IP address of the user |
| **Item Name** | Name of the affected object |
| **Item Type** | Type of object (template, notification, etc.) |
| **Location** | Geographic location based on IP address |
| **Name** | Display name of the user |
| **Org ID** | Unique identifier for the organization |
| **Org Role** | User's organization-level role |
| **Target ID** | Unique identifier for the affected object |
| **Target Role** | Role associated with the target (for member events) |
| **Timestamp** | Formatted date and time of the event |
| **User Action** | The action type (e.g., notification.sent) |
| **User Agent** | Full browser/client user agent string |
| **Version** | Event schema version |
***
## Search and filters
Use the search and filter options to find specific events:
### Search by email
Enter an email address in the search bar to find all actions performed by a specific user.
### Date range
Select from the following preset date ranges:
* **Last 24 hours**
* **Last 48 hours** (default)
* **Last 7 days**
* **Last 30 days**
* **Last 90 days** (requires extended retention)
* **Custom** - Select specific start and end dates
Date range options beyond your plan's retention period will display a lock icon. Contact sales for extended audit log history.
### Filters
Click **Filters** to narrow results by:
| Filter | Description |
| --------------- | --------------------------------------------------------- |
| **User Action** | Filter by action type (Logged In, Template Updated, etc.) |
| **App ID** | Filter by a specific app's unique identifier |
| **IP Address** | Filter by IP address |
| **Item Type** | Filter by object type (Template, Notification, Journey) |
| **Target ID** | Filter by the unique ID of the affected object |
### Item type values
Filter audit logs by the type of object affected:
| Item Type | Description |
| ----------------- | --------------------------------- |
| A/B Test | A/B test experiments |
| API Key | API keys and tokens |
| App | Application settings |
| Data Feed | Data feed configurations |
| Dynamic Content | Dynamic content blocks |
| Email Domain | Email sending domains |
| Email Suppression | Email suppression list entries |
| Event Stream | Event stream destinations |
| Export | Data exports |
| In-App Message | In-app message campaigns |
| Integration | Third-party integrations |
| Journey | Automated journey workflows |
| Member | Team members |
| Notification | Push, email, or SMS notifications |
| Organization | Organization settings |
| Segment | Audience segments |
| Subscription | User subscriptions |
| Template | Message templates |
| User | End users/subscribers |
| Webhook | Webhook configurations |
***
## Exporting audit logs
Export audit log data to a zipped `.json` file for offline analysis, compliance reporting, or integration with external tools.
To export audit logs:
1. Navigate to the **Audit Logs** page (at the organization or app level).
2. Apply any desired [filters](#search-and-filters) and date range.
3. Click **Export**.
4. The `.json.gz` file(s) will be emailed to you.
You'll have 3 days from creation to download your file before it expires.
### Export availability
The export window matches your plan's retention period.
| Plan | Export Window |
| ------------ | ---------------------------------------------------- |
| Free | 2 days |
| Growth | 2 days |
| Professional | 2 days (up to 90 days with Legal & Security Package) |
| Enterprise | Up to 90 days |
Combine exports with the [Audit Logs API](/reference/list-audit-logs) for automated, recurring data pulls.
***
## API access
Query audit logs programmatically using the REST API. The API supports pagination, time-scoped queries, and filtering by app, action type, actor, target, and IP address.
**Requirements:**
* **Enterprise plan** with the audit logs entitlement enabled
* **Organization API Key** for authentication (app-level keys are not accepted)
See the [List audit logs API reference](/reference/list-audit-logs) for endpoint details, parameters, and example responses.
***
## Data retention
Audit log data is retained based on your plan:
* **Free and Growth plans**: 48-hour retention
* **Professional plan**: 48-hour retention by default, or 90-day retention with the Legal & Security Package add-on
* **Enterprise plan**: 90-day retention included
After the retention period, audit log entries are automatically removed and cannot be recovered.
Need longer retention? Contact your Account Manager or our [Sales Team](https://onesignal.com/contact) to discuss upgrading your plan.
***
## Complete event reference
The following is a comprehensive list of all events tracked in audit logs, organized by category.
| Action | Description |
| ---------------------- | ------------------------------------ |
| App Created | A new app was created |
| App Renamed | An app was renamed |
| App Deleted | An app was deleted |
| App Settings Updated | App settings were modified |
| App API Key Reset | App API key was reset |
| App Auth Token Created | App authentication token was created |
| App Auth Token Updated | App authentication token was updated |
| App Auth Token Deleted | App authentication token was deleted |
| App Auth Token Rotated | App authentication token was rotated |
| Action | Description |
| ---------------- | ---------------------------------- |
| Template Created | A new message template was created |
| Template Updated | An existing template was modified |
| Template Deleted | A template was removed |
| Action | Description |
| ---------------------- | ------------------------------------- |
| Notification Created | A new notification was created |
| Notification Sent | A notification was sent |
| Notification Updated | A notification was modified |
| Notification Canceled | A scheduled notification was canceled |
| Notification Deleted | A notification was removed |
| Notification Previewed | A notification was previewed |
| Action | Description |
| ------------------------ | ------------------------------------- |
| A/B Test Created | A new A/B test was created |
| A/B Test Updated | An A/B test was modified |
| A/B Test Deleted | An A/B test was deleted |
| A/B Test Canceled | An A/B test was canceled |
| A/B Test Winner Selected | A winner was selected for an A/B test |
| Action | Description |
| ----------------- | -------------------------------- |
| Journey Created | A new journey was created |
| Journey Updated | An existing journey was modified |
| Journey Set Live | A journey was activated |
| Journey Archived | A journey was archived |
| Journey Resumed | A paused journey was resumed |
| Journey Scheduled | A journey was scheduled |
| Journey Deleted | A journey was deleted |
| Action | Description |
| ----------------------- | -------------------------------- |
| In-App Message Created | A new in-app message was created |
| In-App Message Updated | An in-app message was modified |
| In-App Message Deleted | An in-app message was deleted |
| In-App Message Enabled | An in-app message was enabled |
| In-App Message Disabled | An in-app message was disabled |
| Action | Description |
| ---------------------- | ---------------------------- |
| Segment Created | A new segment was created |
| Segment Updated | A segment was modified |
| Segment Deleted | A segment was deleted |
| Segment Paused | A segment was paused |
| Segment Resumed | A segment was resumed |
| Segment Set as Default | A segment was set as default |
| Action | Description |
| -------------------------- | ---------------------------------------------- |
| Member Created | A new team member was created |
| Member Deleted | A team member was deleted |
| Member Role Changed in App | A member's app-level role was changed |
| Member Role Changed in Org | A member's organization-level role was changed |
| Member Added to App | A member was added to an app |
| Member Removed from App | A member was removed from an app |
| Member Added to Org | A member was added to the organization |
| Member Removed from Org | A member was removed from the organization |
| Action | Description |
| --------------------- | ------------------------------------------- |
| Organization Created | A new organization was created |
| Organization Updated | Organization settings were modified |
| Organization Deleted | An organization was deleted |
| Organization Disabled | The organization was disabled |
| Organization Enabled | The organization was enabled |
| Logged In | A user logged in |
| Logged Out | A user logged out |
| User Added to Org | A user was added to the organization |
| User Removed from Org | A user was removed from the organization |
| User Role Changed | A user's role was changed |
| App Added to Org | An app was added to the organization |
| App Removed from Org | An app was removed from the organization |
| 2FA Required | Two-factor authentication was made required |
| 2FA Made Optional | Two-factor authentication was made optional |
| Auth Token Created | Organization auth token was created |
| Auth Token Updated | Organization auth token was updated |
| Auth Token Deleted | Organization auth token was deleted |
| Auth Token Rotated | Organization auth token was rotated |
| Action | Description |
| ---------------------- | ------------------------------ |
| Subscription Created | A new subscription was created |
| Subscription Updated | A subscription was modified |
| Subscription Canceled | A subscription was canceled |
| Payment Method Added | A payment method was added |
| Payment Method Updated | A payment method was updated |
| Action | Description |
| ------------------- | ------------------------- |
| Webhook Created | A new webhook was created |
| Webhook Updated | A webhook was modified |
| Webhook Deleted | A webhook was deleted |
| Webhook Tested | A webhook was tested |
| Webhook Activated | A webhook was activated |
| Webhook Deactivated | A webhook was deactivated |
| Action | Description |
| ------------------------ | ------------------------------- |
| Event Stream Created | A new event stream was created |
| Event Stream Updated | An event stream was modified |
| Event Stream Deleted | An event stream was deleted |
| Event Stream Activated | An event stream was activated |
| Event Stream Deactivated | An event stream was deactivated |
| Event Stream Tested | An event stream was tested |
| Action | Description |
| --------------------- | --------------------------- |
| Data Feed Created | A new data feed was created |
| Data Feed Updated | A data feed was modified |
| Data Feed Deleted | A data feed was deleted |
| Data Feed Activated | A data feed was activated |
| Data Feed Deactivated | A data feed was deactivated |
| Data Feed Tested | A data feed was tested |
| Action | Description |
| ---------------------------- | ---------------------------------- |
| Integration Activated | An integration was activated |
| Integration Deactivated | An integration was deactivated |
| Integration Settings Updated | Integration settings were modified |
| Action | Description |
| -------------- | ------------------------ |
| Import Created | A new import was created |
| Import Updated | An import was modified |
| Import Started | An import was started |
| Import Failed | An import failed |
| Action | Description |
| -------------- | ------------------------ |
| Export Created | A new export was created |
| Action | Description |
| ------------ | ---------------------- |
| User Created | A new user was created |
| User Updated | A user was modified |
| User Deleted | A user was deleted |
| Action | Description |
| -------------------- | ------------------------------ |
| Subscription Created | A new subscription was created |
| Subscription Updated | A subscription was modified |
| Subscription Deleted | A subscription was deleted |
| Action | Description |
| ------------------------------ | --------------------------------------------- |
| Custom Event Retention Updated | Custom event retention settings were modified |
| Action | Description |
| ----------------------- | --------------------------------------- |
| Dynamic Content Created | A new dynamic content block was created |
| Dynamic Content Updated | A dynamic content block was modified |
| Dynamic Content Deleted | A dynamic content block was deleted |
| Action | Description |
| --------------------------- | ---------------------------------------- |
| Email Domain Created | A new email sending domain was added |
| Email Domain Activated | An email domain was activated |
| Email Domain Deactivated | An email domain was deactivated |
| Email Domain Deleted | An email domain was removed |
| Email Domain Sends Canceled | Sending was canceled for an email domain |
| Action | Description |
| ------------------- | ------------------------------------------------------ |
| Suppression Created | An email address was added to the suppression list |
| Suppression Deleted | An email address was removed from the suppression list |
***
## FAQ
### Who can access audit logs?
**Organization-level audit logs:** Organization Admins can access audit logs for the entire organization.
**App-level audit logs:** App Admins can access audit logs for apps they administer. Organization Admins can also access app-level audit logs for all apps in their organization.
See [Manage Team Members](./manage-team-members) to update roles.
### Can I view audit logs for a specific app?
Yes. Navigate to your app's **Settings** page and select **Audit Logs** to view activity for that specific app only. This is useful for investigating changes to a particular app without seeing activity from other apps in your organization.
### Can I export audit logs?
Yes. Click **Export** on the Audit Logs page to download a `.jsonl` file of the currently filtered results. The export window depends on your plan — see [Export availability](#export-availability) for details. For programmatic access, use the [List audit logs API](/reference/list-audit-logs) (Enterprise plans only).
### Can audit logs be deleted?
No. Audit logs are immutable and cannot be deleted by any user.
### Are API actions logged?
Yes, for select actions. API actions performed with Organization and App API keys are logged for auth token management (create, update, rotate, delete), app management (create, update), segment management (create, update, delete), template management (create, update, delete, copy to app), and subscription CSV export. The actor type displays as "API Key" with the key's name and scope. Additional API actions are planned for future releases.
### How do I increase my retention period?
Enterprise customers receive 90-day retention automatically. Professional customers can upgrade to 90-day retention by adding the Legal & Security Package. Contact your Account Manager or our [Sales Team](https://onesignal.com/contact) to discuss your requirements.
***
# Badges
Source: https://documentation.onesignal.com/docs/en/badges
Manage app icon badge counts for push notifications on iOS, Android, and Huawei, including auto-clearing, API control, and native badge logic.
Badges are the little numbered dots that appear on your mobile app icon. They help capture user attention and can influence engagement behavior. On iOS in particular, badges require additional setup and offer flexible control, as outlined below.
For Android Web Push notifications, the badge refers to the small icon shown on notifications—not the app icon—and can be customized. See [Web Push Badges](./push#badges).
***
## Android badges
Android app icon badge behavior can be managed through [Android notification categories](./android-notification-categories). You can control whether a category (channel) displays a badge and set badge behavior on a per-category basis.
***
## Huawei badges
On Huawei devices, a badge can be displayed as a number or a dot on the app icon, depending on the user's device settings. Badges help indicate unread messages or pending actions, encouraging users to open the app. OneSignal lets you control Huawei badge counts directly through the dashboard or API.
The badge displays on the app icon as either a **numeric count** or a **dot**, depending on the user's device setting (**Settings > Notifications > App icon badges**). Your API call controls the underlying count; the device decides the visual style.
**Parameters**
| Parameter | Type | Range | Description |
| ---------------------- | ------- | ----- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `huawei_badge_class` | string | — | **(Required)** Fully qualified class name of your app's launcher Activity (e.g., `com.example.myapp.MainActivity`). Tells the Huawei system which app icon to badge. |
| `huawei_badge_set_num` | integer | 0–99 | Sets the badge to an exact number. `0` clears the badge. |
| `huawei_badge_add_num` | integer | 1–99 | Increments the existing badge count by this amount. |
**Behavior rules**
* `huawei_badge_class` is required for any badge operation.
* If both `huawei_badge_set_num` and `huawei_badge_add_num` are provided, `huawei_badge_set_num` takes priority.
* If neither is provided (but `huawei_badge_class` is set), the badge count increments by 1 by default.
**Send Huawei push with badges**
1. Go to **Messages > Push** or **Templates**
2. Under **Platform Settings > Send to Huawei Android > Badge**
3. Choose either:
* **Don't set** — badge is not affected by this notification
* **Set to** — sets the badge to a specific number (0–99)
* **Increase by** — increments the existing badge count (1–99)
Use the following parameters in the [Create message](/reference/create-message) API:
* `huawei_badge_class` — *(string, required for badge)* The fully qualified class name of the app's launcher activity (e.g., `"com.example.myapp.MainActivity"`)
* `huawei_badge_set_num` — *(integer, 0–99)* Sets the badge count to this exact number. Set to `0` to clear the badge.
* `huawei_badge_add_num` — *(integer, 1–99)* Increments the existing badge count by this number. If omitted along with `huawei_badge_set_num`, defaults to incrementing by 1.
**Set badge to a specific number:**
```json theme={null}
{
"huawei_badge_class": "com.example.myapp.MainActivity",
"huawei_badge_set_num": 5
}
```
**Increment badge by a specific amount:**
```json theme={null}
{
"huawei_badge_class": "com.example.myapp.MainActivity",
"huawei_badge_add_num": 1
}
```
**Clear the badge:**
```json theme={null}
{
"huawei_badge_class": "com.example.myapp.MainActivity",
"huawei_badge_set_num": 0
}
```
**Clearing badges**
Huawei does **not** automatically clear the badge when a user opens the app or taps a notification. To clear the badge, you have two options:
* **Via the API or dashboard**: Send a notification with `huawei_badge_set_num` set to `0` (or use **Set to > 0** in the dashboard). This can be a [data/background notification](./data-notifications) if you don't want a visible notification to appear.
* **Via client-side code**: Your app can clear the badge locally using the Huawei badge API. This requires the `com.huawei.android.launcher.permission.CHANGE_BADGE` permission in your `AndroidManifest.xml`. See [Huawei's badge development guide](https://developer.huawei.com/consumer/en/doc/hmscore-guides/android-badging-0000001050042083) for implementation details.
The `huawei_badge_set_num` parameter requires EMUI 10.0.0 or later and Push SDK 10.1.0 or later. On older devices, only `huawei_badge_add_num` is supported.
***
## iOS badges
To ensure badge counts increment correctly on iOS, you must configure:
* The `OneSignalNotificationServiceExtension`
* App Groups
See [Mobile SDK setup](./mobile-sdk-setup) for full instructions.
By default, the OneSignal SDK will:
1. Clear the app icon badge when the app is opened.
2. Remove notifications from the Notification Center.
If you want to retain notifications and manage badge logic manually (e.g., using your own counter or syncing state across devices), you can disable this automatic behavior.
**Common use cases for manual badge control**
* Reset badge when the app launches or resumes
* Increment badge when a notification is received in the foreground
* Decrement when a message is read or dismissed
* Sync badge state across devices or app extensions via App Groups or your backend
### Disable automatic notification and badge clearing
In your app's `info.plist`, add the Key: `OneSignal_disable_badge_clearing` with Boolean type to Value `YES`
```xml theme={null}
OneSignal_disable_badge_clearing
```
This prevents the SDK from automatically removing notifications or resetting the badge when the app opens.
#### iOS native badge management
If you disable OneSignal's automatic badge clearing, you can use Apple's native APIs to control badge behavior.
Apple deprecated `UIApplication.shared.applicationIconBadgeNumber` in iOS 17. You should now use the following methods from the [UserNotifications framework](https://developer.apple.com/documentation/UserNotifications/UNUserNotificationCenter/setBadgeCount\(_:withCompletionHandler:\)):
**Set badge count**
To set the badge on the app icon to a specific value:
```swift theme={null}
import UserNotifications
import UIKit
if #available(iOS 17.0, *) {
UNUserNotificationCenter.current().setBadgeCount(5) { error in
if let error = error {
print("Failed to set badge count: \(error.localizedDescription)")
} else {
print("Badge count updated successfully.")
}
}
} else {
UIApplication.shared.applicationIconBadgeNumber = 5
}
```
**Get current badge count**
iOS does not provide a method to retrieve the current badge count from the system. You must keep track of the badge count in your app's state (for example, using `UserDefaults`, your app's data model, or syncing with your backend).
```swift theme={null}
// Example: Store and retrieve badge count using UserDefaults
let badgeCount = UserDefaults.standard.integer(forKey: "badgeCount")
// Update badge count as needed
UserDefaults.standard.set(badgeCount, forKey: "badgeCount")
```
**Increment or decrement badge**
You must manage badge logic manually, as relative changes (like +1 or -1) are not supported in payloads. Update your stored badge count and then set it:
```swift theme={null}
// Example: Increment badge count and update system badge
let currentCount = UserDefaults.standard.integer(forKey: "badgeCount")
let updatedCount = max(0, currentCount + 1)
UserDefaults.standard.set(updatedCount, forKey: "badgeCount")
if #available(iOS 17.0, *) {
UNUserNotificationCenter.current().setBadgeCount(updatedCount)
} else {
UIApplication.shared.applicationIconBadgeNumber = updatedCount
}
```
**Clear badge**
To remove the badge entirely:
```swift theme={null}
if #available(iOS 17.0, *) {
UNUserNotificationCenter.current().setBadgeCount(0)
} else {
UIApplication.shared.applicationIconBadgeNumber = 0
}
```
### Send iOS push with badges
You can set the badge count in the OneSignal dashboard or using the API.
1. Go to **Messages > Push** or **Templates**.
2. Under **Platform Settings > Send to Apple iOS > Badges**.
3. Choose either:
* Set to a specific number.
* Increase by a relative amount.
Use the following parameters in the [Create message](/reference/create-message) API:
* `ios_badgeType` — Set to `SetTo` or `Increase`.
* `ios_badgeCount` — The number to set or increase by.
```json theme={null}
{
"ios_badgeType": "Increase",
"ios_badgeCount": 1
}
```
When sending iOS push notifications, the badge count changes based on these options.
If the app is open, the badge count resets unless you [disable automatic badge clearing](#disable-automatic-notification-and-badge-clearing).
***
## FAQ
### Why isn't my badge count incrementing on iOS?
Ensure you have configured the `OneSignalNotificationServiceExtension` and App Groups. Without these, badge counts cannot increment correctly. See [Mobile SDK setup](./mobile-sdk-setup) for full instructions.
### How do I clear badges on Huawei?
Send a notification with `huawei_badge_set_num` set to `0`, or use **Set to > 0** in the dashboard. You can also use a [data/background notification](./data-notifications) to clear badges without showing a visible notification. Alternatively, clear the badge client-side using the Huawei badge API.
### Can I set badges for web push?
No. App icon badges are only supported on iOS, Android, and Huawei. For Android web push, the "badge" refers to the small icon shown on the notification itself — see [Web Push Badges](./push#badges).
***
## Related pages
Control badge display and behavior per notification channel on Android.
Set up Huawei push messaging with OneSignal.
Full reference for push notification features including web push badges.
Configure the OneSignal SDK including badge prerequisites for iOS.
# Usage & billing
Source: https://documentation.onesignal.com/docs/en/billing-faq
OneSignal billing overview covering invoices, payment methods, plan changes, usage metrics, and cost management for all plan types.
For plan features and pricing tiers, see the public [Pricing page](https://onesignal.com/pricing).
This guide explains:
* How to complete common billing tasks
* What affects your invoice amount
* How usage is calculated
* How to manage or reduce costs
* How billing relates to Apps and Organizations
***
## Common billing tasks
View your Organization's Billing and Usage within **Organizations > Billing**.
You must be an Organization Admin to view the billing page. See [Manage Team Members](./manage-team-members) and ask an Org Admin to add you as a Billing Contact. You can also view App-level usage in **Settings > Usage**.
### Download an invoice
**Self-serve (Growth & legacy plans)**
1. Go to **Organizations > Billing**.
2. Under **Invoices**, select the invoice.
3. Click **Download**.
The invoice downloads immediately as a PDF.
**Professional & Enterprise (contract plans)**
Email `ar@onesignal.com` with:
* Full company name
* Billing email (if available)
* Organization ID (found in **Organizations > Keys & IDs**)
***
### Update your payment method
**Self-serve (Growth & legacy plans)**
1. Go to **Organizations > Billing**.
2. Next to **Payment Method**, click **Update**.
3. Enter your updated payment details and **Save**.
Your updated payment method appears immediately in the billing page.
**Professional & Enterprise (contract plans)**
1. Open the secure billing portal from your **Billing Welcome Email**.
2. Enter your updated payment details.
3. Save changes.
If you cannot find the billing portal link, email `ar@onesignal.com` with:
* Full company name
* Billing email (if available)
* Organization ID (found in **Organizations > Keys & IDs**)
***
### Change billing information
**Self-serve (Growth & legacy plans)**
1. Go to **Organizations > Billing**.
2. Next to **Billing Profile**, click **Edit**.
3. Enter your updated billing information and **Save**.
Your updated billing information appears immediately in the billing page.
**Professional & Enterprise (contract plans)**
1. Open the secure billing portal from your **Billing Welcome Email**.
2. Enter your updated billing information.
3. Save changes.
If you cannot find the billing portal link, email `ar@onesignal.com` with:
* Full company name
* Billing email (if available)
* Organization ID (found in **Organizations > Keys & IDs**)
***
### Upgrade your plan
If you need higher limits or additional features, [contact our Sales team](https://onesignal.com/contact).
***
### Downgrade to Free
**Self-serve (Growth & legacy plans)**
1. Go to **Organizations > Billing**.
2. Select **Change Plan**.
3. Click the **Free** plan.
4. Confirm the downgrade.
You will continue on the same plan until the end of your current billing cycle.
Paid features are removed at the end of your current billing cycle.
**Professional & Enterprise (contract plans)**
Contract plans must follow agreement terms. Contact your Account Manager or `ar@onesignal.com` to discuss changes.
***
### Reactivate a suspended account
Self-serve accounts are disabled after **three failed payment attempts**.
To reactivate:
1. Pay any outstanding invoices.
2. Allow up to one business day for reactivation.
If your account is not restored, email `support@onesignal.com` with:
* Full company name
* Billing email (if available)
* Organization ID (found in **Organizations > Keys & IDs**)
***
## What affects your invoice amount?
Your invoice is based on one or more of the following:
* Active **Mobile Monthly Active Users (MAU)**
* Active **Web Push Subscribers**
* **Email sends**
* **In-App message impressions**
* **Event Streams volume**
* **Custom Event storage**
To confirm your pricing model, go to **Organizations > Billing**.
You will see either:
* **Mobile MAU + Web Push Subscribers** (current pricing model)
* **Push Subscribers** (legacy plans only)
***
## How billing is calculated
### Monthly Active Users (MAU)
A Monthly Active User (MAU) is a mobile push subscription active within the last 30 days, regardless of current subscription status.
* Each active subscription counts separately.
* One user with two active subscriptions counts as **2 MAU**.
Billing is calculated in **increments of 10 MAU**, rounded **down** to the nearest multiple of 10.
See [Subscriptions](./subscriptions) for more details.
### Web Push Subscribers
Users who subscribed to receive web push notifications from a supported web browser.
* Each subscribed browser counts separately.
* One user subscribed on two browsers counts as **2 subscribers**.
Billing is calculated in **increments of 10 subscribers**, rounded **down** to the nearest multiple of 10.
See [Subscriptions](./subscriptions) for more details.
***
### Push Subscribers (legacy plans only)
Push Subscriber pricing includes total subscribed mobile and web push subscriptions.
To reduce subscriber count, remove users via the dashboard or API. See [Delete Users](./delete-users).
To migrate from Push Subscriber pricing to MAU pricing, [contact our Sales team](https://onesignal.com/contact).
***
## Messaging usage
### Email sends
You are billed for emails that leave OneSignal, including:
* Delivered
* Bounced
* Most Failures
Exceptions:
* **Invalid ESP credentials**
* **Delivery Error**
***
### RCS
RCS messages are billed per message segment based on the message type:
| Type | Audience | Description |
| -------------- | ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Rich** | US | Text-only messages — cannot include a title. Can include actions such as replies, dials, or links, but link actions must open in a new tab. Messages longer than 160 characters are broken into segments and charged per segment. |
| **Rich Media** | US | Messages with media, cards with titles and/or actions that open in a web view (not a new tab), and carousel content. |
| **Basic** | International | Text messages up to 160 UTF-8 characters. Does not include any actions, media, or carousels. |
| **Single** | International | Text messages greater than 160 UTF-8 characters, up to the max message length of 3,072 characters. Can also include actions, media, or carousels. |
For more on RCS message types, see [Rich Communication Services (RCS)](./rcs-messaging#types-of-rcs-messages).
***
### In-App impressions
Counts each display of an [in-app message](./in-app-messages-setup) in mobile apps.
This metric does not include push notifications or email.
***
## Event-related billing
Event billing includes both **delivery volume** and **storage volume**.
### Event Streams (delivery volume)
Includes:
* [Event Streams](./event-streams)
* Webhooks
* Integration destinations
Events delivered through integrations count toward your monthly limit.
| Plan | Active Event Streams | Monthly Event Limit |
| ------------ | -------------------- | ------------------- |
| Free | 1 | Up to 1,000 events |
| Growth | 3 | Up to 10,000 events |
| Professional | Custom | Based on volume |
| Enterprise | Custom | Based on volume |
Webhooks are a type of Event Stream destination. Events delivered through webhooks and integrations count toward your Event Streams monthly event limit.
***
### Custom Events (storage volume)
Custom Events storage is different from Event Streams. **Event Streams** send message data (delivery, clicks, failures) **out** of OneSignal to external destinations like webhooks and integrations. **Custom Events** are sent **into** OneSignal from your app or backend systems to power Segments, Journeys, and analytics. They have separate billing and separate plan limits.
[Custom Events](./custom-events) are billed based on the number of events **stored** per month. Each plan includes a monthly storage allotment; overages are charged at your contracted unit rate.
By default, all events are stored with unlimited retention. You can adjust the retention period per event type at any time.
Each plan includes:
| Plan | Included Stored Events / Month |
| ---------- | ------------------------------ |
| Growth | 1M |
| Pro | 5M |
| Enterprise | 10M |
### How retention affects Custom Event cost
* Retention is measured per event name.
* Default retention is **unlimited**.
* Minimum retention is **30 days**.
* Billing is based on stored events at the end of the billing cycle.
* Shorter retention reduces billable stored events.
To adjust retention, go to **Data > Custom Events** in the OneSignal dashboard, then select the **Event Storage** tab. From there you can set retention per event name. Changes take effect at the next billing cycle.
Events received through integrations (e.g., Amplitude, Segment) also count toward your stored event total.
***
## Analytics data retention
Dashboard reporting data — including message delivery reports, conversion metrics, and template analytics — is retained based on your plan:
| Plan | Retention period |
| ------------ | ---------------- |
| Free | 30 days |
| Growth | 90 days |
| Professional | 1 year |
| Enterprise | 2 years |
See the [Pricing page](https://onesignal.com/pricing) for more details on plan features.
## Message event retention
Message event data powers [segment filters](./segmentation#message-event-filters), letting you target users based on how they interacted with your messages. How far back that data is available depends on your plan:
| Plan | Message event retention |
| ------------ | ----------------------- |
| Growth | 30 days |
| Professional | 60 days |
| Enterprise | 90 days |
The segment filter UI shows a time window of up to 90 days, but queries beyond your plan's retention period will not return results.
***
## FAQ
### How can I manage or reduce costs?
You can reduce billing by:
**If you are on MAU pricing:**
* Reduce duplicate mobile subscriptions or limit the number of subscriptions per user.
* Use our [SDK privacy methods](./mobile-sdk-reference#privacy) to delay SDK initialization and subscription creation until you are ready to create a subscription for the user.
**If you rely heavily on email:**
* Send to more targeted segments.
* Clean inactive and invalid email addresses.
**If you use Custom Events:**
* Shorten retention periods.
* Consolidate similar event names.
**If you use Event Streams:**
* Audit integrations.
* Reduce unnecessary events.
For volume discounts (for example, more than 50k MAU or 250k emails per month), [contact our Sales team](https://onesignal.com/contact).
### How is billing related to Apps and Organizations?
* You can create unlimited apps and organizations.
* Apps are not billed individually.
* Billing occurs at the **Organization level**.
* You can create multiple Organizations to:
* Separate apps that you want to be billed for vs those you do not want to be billed for.
* Add apps for different brands, clients, or environments.
* Example: a "Free" Organization for development or testing and a "Paid" Organization for production.
See [Apps & Organizations](./apps-organizations).
### When am I charged?
* **Self-serve (Growth & legacy plans)**: Monthly on the same calendar date you started.
* **Professional & Enterprise (contract plans)**: According to your contract terms.
### Can I cancel anytime?
* **Self-serve (Growth & legacy plans)**: Yes. Downgrade to Free.
* **Professional & Enterprise (contract plans)**: Contract terms apply.
### Can I pay by invoice?
**Custom Professional and Enterprise (contract plans)** can be paid by invoice. Ask your Account Manager or [contact our Sales team](https://onesignal.com/contact).
### Can someone else request invoices?
* **Self-serve (Growth & legacy plans)**: Only [Org Admins](./manage-team-members) can view and edit billing information in the Organization Billing page.
* **Professional & Enterprise (contract plans)**: Email `ar@onesignal.com` with:
* Full company name
* Billing email (if available)
* Organization ID (found in **Organizations > Keys & IDs**)
### How do I add or remove billing contacts?
See [Manage Team Members](./manage-team-members) to remove team members from accessing the App and/or Organization.
To remove a billing contact, email `ar@onesignal.com` with:
* Full company name
* Billing email (if available)
* Organization ID (found in **Organizations > Keys & IDs**)
### What payment methods are accepted?
Visa, MasterCard, American Express, Discover, Diners Club International, JCB, and PayPal.
Payments are processed by Recurly using 128-bit SSL. OneSignal does not store your card details.
**Currency**
We currently accept USD only.
### How does deleting subscriptions affect my bill?
For **web push**, billing is calculated based on number of subscriptions, so deleting subscriptions before the end of your billing cycle will lower your bill.
For **mobile push**, billing is calculated based on monthly active users. Deleting a subscription that has been active in your current billing cycle before the end of the cycle will lower your bill.
For **in-app messages, SMS, and Email**, billing is not based on your subscription count, so deleting subscriptions will not affect these parts of your bill.
***
## Related pages
Manage apps, organizations, and how billing is structured across them.
Add or remove team members and control access to billing.
How mobile and web push subscriptions are counted toward billing.
Compare plan features and pricing tiers.
# Web push browser behavior
Source: https://documentation.onesignal.com/docs/en/browser-behavior-and-unsubscribes
Learn how to unsubscribe from notifications and understand how browsers handle web push subscriptions.
This guide explains how to manage web push [subscriptions](./subscriptions) and how subscription status is affected by both user action and browser behavior.
***
## Understand push permissions
Users must give your website permission to send them push notifications. It is not possible to receive push notifications without explicitly granting the site permission using the system-level permission prompt.
Permissions can be either:
* **Default**: permission has not been granted to denied.
* **Granted**: you allowed the website to send you notifications.
* **Denied**: you blocked the website to send you notifications. This can be a temporary block if you clicked the **x** to close the prompt repeatedly or a permanent block if you clicked **Block** or toggled off permission in the browser settings.
For more details on the native system-level permission prompt and/or any of the OneSignal prompts, see [Web permission prompts](./permission-requests).
***
## How to unsubscribe from web notifications
You can unsubscribe from web push notifications in three ways:
### Unsubscribe within browser settings
You can manage or remove notification permissions directly in browser settings. Here are quick-access URLs and official docs to learn more:
* **Chrome**: `chrome://settings/content/notifications` ([Learn more on Chrome's docs](https://support.google.com/chrome/answer/3220216?hl=en\&co=GENIE.Platform%3DDesktop\&sjid=12874758545589453111-NA))
* **Edge**: `edge://settings/content/notifications` ([Learn more on Microsoft's docs](https://www.microsoft.com/en-us/edge/learning-center/how-to-turn-off-block-browser-notifications?form=MA13I2))
* **Firefox**: `about:preferences#privacy` scroll to Permissions > Notifications > Settings ([Learn more on Mozilla's docs](https://support.mozilla.org/en-US/kb/push-notifications-firefox))
* **Safari**: Settings > Websites > Notifications ([Learn more on Safari's docs](https://support.apple.com/guide/safari/customize-website-notifications-sfri40734/16.1/mac/13.0))
On these pages, just click the options to remove or block the website(s) you don't want notifications from.
### Unsubscribe while on the website
**Reset permission**
Most browsers have a "lock" or "settings" icon next to the URL. Clicking it reveals site-specific permissions where users can disable push notifications.
**OneSignal prompts**
If the website contains the OneSignal [Bell Prompt](./permission-requests) or [Custom Link prompt](./permission-requests) users can unsubscribe directly via those UI elements and be able to resubscribe using the same as desired.
### Deleting browser data, clearing cookies and site data
If you delete history and/or delete your cookies and site data, it will temporarily prevent notifications from showing. However, if you don't remove push permissions from the site, you may be automatically re-subscribed and start getting notifications again upon returning to the site.
***
## How to test your permission prompts
These steps explain how to test your prompt and subscription flow like a first time visitor.
**Do not use an incognito, private, or guest browser setting.** This example uses Chrome version 135 on macOS but the flow should be relatively the same for most browsers.
Click the site settings or lock icon next to the site URL and select **Reset permission** or remove permissions for Notifications.
Skip to the next step if you don't see this permission option.
Click **Cookies and site data > Manage on-device site data** or follow the browser's flow to see your site's data option.
Delete the data for your site and exit the settings to get back to your site.
Usually you can just right click the screen and press **Inspect**.
If you do not see the prompt or don't know the steps, see [Web permission prompts](./permission-requests).
If you see anything in red related to OneSignal, see our [Web SDK troubleshooting](./troubleshooting-web-push) docs.
In the **Console** type or copy-paste this code: `OneSignal.User.PushSubscription.id`
1. This will log your OneSignal subscription ID. Copy-paste this into your OneSignal Dashboard Audience > Subscriptions tab.
2. If a subscription ID was not logged in the console, then you are not successfully subscribed. Please see [Web SDK troubleshooting](./troubleshooting-web-push) for details.
See [Push](./push) for more details if needed.
If you did not receive a push, see [Web push: Notifications not shown](./notifications-not-shown-web-push) for further debugging.
You have successfully setup web push with OneSignal. Next steps:
* [Web push setup](./web-push-setup) - additional non-developer web setup steps.
* [Web SDK setup](./web-sdk-setup) - developer web SDK setup steps.
* [Web SDK troubleshooting](./troubleshooting-web-push) - troubleshooting if you see errors in the console or not getting a subscription ID.
* [Web push: Notifications not shown](./notifications-not-shown-web-push) - troubleshooting notifications not displaying on your device.
***
### Receiving Notifications When the Browser is Closed
Browsers behave differently across platforms. Please refer to the table below for support for receiving notifications even when the browser is closed.
| Browser Name | Android | Windows | macOS |
| ----------------- | ------- | ------- | ----- |
| Chrome / Chromium | Yes | Yes | No |
| Firefox | Yes | Yes | No |
| Safari | N/A | N/A | Yes |
| Opera | Yes | Yes | No |
| Edge | Yes | Yes | No |
**Chrome** - Chrome runs as a background process by default even when all the windows are closed. As long as the background process is running, notifications will still be received. If the Chrome background process is not running, notifications will not be received.
**Firefox** - On Mac OS X, the process still exists even if windows are closed, and a notification can be received if all windows are closed (as long as there is still a dot in the dock showing Firefox is still running). On Windows, the process exits after all windows are closed so notifications cannot be received unless a Firefox window is still open.
**Safari** - Safari does not have to be running to receive notifications, as they are sent directly to the operating system. The user still has to sign up for Safari web notifications, but after that they will be received even when Safari is completely closed.
Subscribers have up to 3 days to retrieve the last known missing notification before messages expire permanently.
For example, suppose a subscriber was supposed to receive a Firefox web push notification, but Firefox was closed. If the subscriber opens Firefox within 3 days, the subscriber will receive only the last known web push notification that didn't expire. If the subscriber opens Firefox after 3 days, the web push notification sent more than 3 days ago will not be received.
***
# Channel setup
Source: https://documentation.onesignal.com/docs/en/channel-setup
Set up push notifications, in-app messages, email, SMS, RCS, and Live Activities in OneSignal to reach Users across every channel.
OneSignal supports multiple messaging channels, each with different capabilities and setup requirements. Choose the channels that match your goals, or combine several to build a multi-channel experience with [Journeys](./journeys-overview).
Email and SMS can be configured without a developer. Push notifications, in-app messages, and Live Activities require SDK integration — see the [Developer guide](./developers) or [invite a developer](./manage-team-members) to your team.
***
## Messaging channels
Alert-style notifications on iOS, Android, Huawei, and Amazon — even when the app isn't open.
Browser-based notifications that reach Users even when your site isn't open.
Marketing and transactional emails with built-in deliverability tools.
Rich, interactive messages displayed while Users are active in your app.
Direct text messages for urgent or time-sensitive communication.
Rich messaging with branded content and read receipts on Android.
Real-time updates on the iOS lock screen. Similar capabilities available for Android.
***
## Channel comparison
| Channel | Developer required? | Setup time | Best for |
| --------------- | ------------------- | ---------- | -------------------------------------------------------- |
| Email | No | 15–60 min | Marketing campaigns, transactional messages, newsletters |
| SMS | No | 15–60 min | Time-sensitive alerts, OTPs, appointment reminders |
| Web push | Yes | 15–45 min | Re-engaging website visitors, announcements |
| Mobile push | Yes | 30–60 min | App re-engagement, real-time alerts, promotions |
| In-app messages | Yes (SDK) | 30–45 min | Onboarding, feature announcements, surveys |
| RCS | No | Varies | Rich branded messaging on Android with read receipts |
| Live Activities | Yes (SDK) | 45–60 min | Live scores, delivery tracking, event countdowns |
***
## Next steps
After setting up a channel, continue with the rest of your OneSignal implementation.
Full implementation walkthrough: Users, segments, sending messages, and analytics.
SDK reference, API docs, User identity, and testing for engineering teams.
Compose and send your first message after setting up a channel.
Automate multi-channel flows based on User behavior.
***
## FAQ
### Which channel should I set up first?
Start with the channel that matches your most immediate goal. **Email** and **SMS** are the fastest to configure and don't require a developer. If you already have a mobile app, **push notifications** provide the highest visibility for re-engagement.
### Can I use multiple channels in the same OneSignal app?
Yes. A single OneSignal app supports all channels. Adding multiple channels lets you use [Journeys](./journeys-overview) to automate multi-channel flows — for example, sending a push notification, then following up with an email if the User doesn't engage.
### Do I need separate apps for testing and production?
Yes, this is strongly recommended. Use separate OneSignal apps for development, staging, and production to avoid sending test messages to real Users. See [Apps, Organizations, and accounts](./apps-organizations) for details.
# Confirmed receipt
Source: https://documentation.onesignal.com/docs/en/confirmed-delivery
Confirmed receipt tracks when a device receives a push notification sent through OneSignal. Covers requirements, platform limitations, and troubleshooting for iOS, Android, Huawei, and Web.
Confirmed receipt (also known as "Confirmed Delivery") tracks when a device actually **receives** a push notification sent from OneSignal. When the OneSignal SDK on the device receives a push, it sends a **confirmation receipt** back to OneSignal containing the notification ID and the device's [Subscription ID](./subscriptions). This lets you see exactly which Subscriptions received which notifications.
In your OneSignal Dashboard, "Confirmed receipt" appears in the [Push Notification Message Reports](./push-notification-message-reports). For all delivery and engagement metrics, see the [Analytics Metrics Glossary](./analytics-metrics-glossary).
Confirmed receipt is different from "Delivered." Platform push services (APNs, FCM, ADM, HMS) report whether a notification was accepted by the service — not whether the device actually received it. Confirmed receipt is the device-side confirmation.
***
## Requirements
* Available only on **paid plans**. [Compare plans](https://onesignal.com/pricing).
* Complete the [Web SDK Setup](./web-sdk-setup) and/or [Mobile SDK Setup](./mobile-sdk-setup).
* Confirmed receipt only works if the device has the OneSignal SDK installed.
* **Not supported** for subscriptions created via API only.
### Platform limitations
#### iOS
* Requires both the **Notification Service Extension** and **App Group** to be set up correctly.
* Push notifications must include the `mutable-content` key set to `1`. This is set automatically by OneSignal. Make sure you are not explicitly setting `mutable_content: false`.
* APNs keeps only **one message per app** when offline. If multiple pushes are sent while offline, only the latest is delivered.
#### Huawei
* Supported only for the `data` [Huawei message type](/reference/push-notification#body-huawei-msg-type).
* For the `message` type, Huawei provides receipt data only in their own dashboard.
#### Web
* Safari does **not** support confirmed receipt.
***
## Troubleshooting confirmed receipt
If you are not receiving push notifications at all, see [Notifications not showing](./notifications-show-successful-but-are-not-being-shown) first.
### iOS
Confirmed receipt on iOS requires two things working together:
1. A **Notification Service Extension** (NSE) that runs OneSignal code when a push arrives
2. An **App Group** shared between your main app target and the NSE target so they can exchange data
If either is missing or misconfigured, the device receives the push but never reports it back to OneSignal.
#### Verify your iOS setup
In Xcode, check that you have a **OneSignalNotificationServiceExtension** target listed under your project targets. If it does not exist, follow [Step 2 of the iOS SDK Setup](./ios-sdk-setup#step-2-add-a-notification-service-extension-nse).
Open the NSE's `NotificationService.swift` (or `.m`) file. It must call `OneSignalExtension.didReceiveNotificationExtensionRequest` inside `didReceive(_:withContentHandler:)`. If the file still contains Apple's default template code, replace it with the [OneSignal NSE code](./ios-sdk-setup#step-2-add-a-notification-service-extension-nse).
Select your **NSE target > General > Frameworks and Libraries** (or **Build Phases > Link Binary With Libraries**). Verify that `OneSignalExtension` is listed. The main app target uses `OneSignalFramework`, but the NSE target must use `OneSignalExtension` — these are different packages.
The OneSignal SDK uses an App Group to share data between your main app and the NSE. There are two ways to configure this — pick the one that matches your setup.
1. Select your **main app target > Signing & Capabilities > App Groups**.
2. Confirm the App Group.
If your App Group is `group.YOUR_MAIN_APP_BUNDLE_ID.onesignal` — where `YOUR_MAIN_APP_BUNDLE_ID` is your main app target's Bundle Identifier (found under **General > Identity**), then follow the Default App Group tab. Otherwise, follow the Custom App Group tab.
1. Select your **NSE target > Signing & Capabilities > App Groups**.
2. Confirm the **exact same** App Group is listed. If missing, add it via **+ Capability > App Groups** and select the same group.
A common mistake is using the NSE's bundle identifier instead of the main app's:
* **Correct:** `group.YOUR_MAIN_APP_BUNDLE_ID.onesignal`
* **Wrong:** `group.YOUR_MAIN_APP_BUNDLE_ID.OneSignalNotificationServiceExtension.onesignal`
Your main app target and NSE target should have the same App Group identifier.
Use this option if your App Group identifier does not follow the `group.YOUR_MAIN_APP_BUNDLE_ID.onesignal` format.
1. Select your **main app target > Signing & Capabilities > App Groups**.
2. Confirm your custom App Group is listed.
3. Select the `Info.plist` of your main app target and add the following key:
```xml theme={null}
OneSignal_app_groups_keygroup.your-custom-group-name
```
Replace `group.your-custom-group-name` with your actual App Group name.
4. Select your **NSE target > Signing & Capabilities > App Groups**.
5. Confirm the **exact same** custom App Group is listed. If missing, add it via **+ Capability > App Groups** and select the same group.
6. Select the `Info.plist` of your NSE target and add the following key:
```xml theme={null}
OneSignal_app_groups_keygroup.your-custom-group-name
```
Replace `group.your-custom-group-name` with your actual App Group name.
Your main app target and NSE target should have the same App Group identifier.
If you use `.xcconfig` files for build settings, verify the App Group is not being overridden or omitted in those files.
Select your **NSE target > General > Minimum Deployments**. This value must match your main app target's minimum deployment. A mismatch can prevent the NSE from running on certain OS versions.
Select your **main app target > Build Phases > Embed App Extensions**. Make sure **"Copy only when installing" is unchecked**. If checked, the NSE is not embedded during development builds, so it never runs when testing.
Select your **NSE target > Info** tab and expand the `NSExtension` key. Confirm it contains:
```xml theme={null}
NSExtensionPointIdentifiercom.apple.usernotifications.serviceNSExtensionPrincipalClass$(PRODUCT_MODULE_NAME).NotificationService
```
If your NSE is written in Objective-C, use `NotificationService` instead of `$(PRODUCT_MODULE_NAME).NotificationService`.
OneSignal automatically sets `mutable-content: 1` in the push payload, which tells iOS to invoke the NSE. If you send pushes via the [REST API](/reference/push-notification), verify you are not explicitly setting `mutable_content: false`. Without `mutable-content`, iOS does not run the NSE and confirmed receipt cannot fire.
Add this line temporarily inside `didReceive` before the OneSignal call:
```swift theme={null}
bestAttemptContent.body = "[Modified] " + bestAttemptContent.body
```
Send yourself a test push. If the notification body starts with `[Modified]`, the NSE is running correctly. If it does not, revisit the steps above — the NSE is not being invoked. Remove this line after testing.
For advanced NSE debugging with Xcode Console logs, see [Debugging the iOS Notification Service Extension](./service-extensions#debugging-the-ios-notification-service-extension).
### Android
* If notifications are not displaying, see [Mobile push troubleshooting](./notifications-show-successful-but-are-not-being-shown).
* If notifications show but confirmed receipt is missing, a custom Android Service Extension may be blocking it. Check the [Android Service Extension guide](./service-extensions#android-service-extension).
### Web
* Safari is not supported.
* For other browsers, ensure migration to **v16 SDK** is complete:
* Correct SDK init:
```html theme={null}
```
* Correct Service Worker reference:
```html theme={null}
importScripts("https://cdn.onesignal.com/sdks/web/v16/OneSignalSDK.sw.js");
```
***
## FAQ
### Why are my confirmed receipt numbers low or missing?
The most common causes are setup issues (especially on iOS), inactive devices, and platform limitations.
1. **iOS misconfiguration:** The Notification Service Extension or App Group is missing or incorrect. See [Troubleshooting confirmed receipt on iOS](#ios).
2. **Inactive or abandoned devices:** Devices that are offline or no longer in use do not receive pushes or send confirmed receipt events. See [How do I handle inactive devices?](#how-do-i-handle-inactive-devices).
3. **Platform limitations:** Huawei `message` type and Safari do not support confirmed receipt.
4. **Android force quit:** Some device manufacturers treat swiping the app away as a force quit, which stops SDK events. See [Mobile push not shown guide](./notifications-show-successful-but-are-not-being-shown).
### How do I handle inactive devices?
Devices that are offline or abandoned do not receive push notifications or send confirmed receipt events. This is common when users replace or abandon devices.
To re-engage inactive users:
* Use **Audience Activity** to resend to users who did not confirm receipt.
* Create [Segments](./segmentation) based on **Last Session** (for example, inactive for 90+ days).
* Combine with a [Re-engagement Journey](./journeys-examples#re-engagement-campaign) to win them back.
* Periodically target inactive users to prune unreachable devices.
See [When do push Subscription statuses update?](./subscriptions#when-do-push-subscription-statuses-update) for more details on how OneSignal updates subscription status.
### Why does confirmed receipt show but the notification doesn't appear?
A confirmed receipt event means the device received the push payload. In rare cases, the notification may not display.
Possible causes:
* **Missed notification:** Send yourself a test push via [Find and set Test Users](./test-users) to rule this out.
* **iOS Focus Mode:** "Do Not Disturb," "Sleep," or other [Focus modes](./ios-focus-modes-and-interruption-levels) delay or group notifications. You may have dismissed a grouped notification without seeing it.
* **App code suppressing display:**
* `event.preventDefault()` in the [foreground lifecycle listener](./mobile-sdk-reference#addforegroundlifecyclelistener-push) or [Notification Service Extension](./service-extensions) stops the notification from displaying.
* Calls to [`removeDeliveredNotifications(withIdentifiers:)`](https://developer.apple.com/documentation/usernotifications/unusernotificationcenter/removedeliverednotifications\(withidentifiers:\)) or [`removeAllDeliveredNotifications()`](https://developer.apple.com/documentation/usernotifications/unusernotificationcenter/removealldeliverednotifications\(\)) remove notifications after they arrive.
* **Push payload settings:**
* Ensure `priority` is set to high. See [Push priority](./push#priority).
* [`collapse_id`](./push#collapse_id) replaces older pushes with newer ones using the same ID.
***
## Related pages
Review delivery, engagement, and confirmed receipt metrics for each push.
Definitions for every delivery and engagement metric in OneSignal.
Add the Notification Service Extension and App Group required for iOS.
Customize notification behavior on iOS and Android with service extensions.
Need help?
Chat with our Support team or email `support@onesignal.com`
Please include:
* Details of the issue you're experiencing and steps to reproduce if available
* Your OneSignal App ID
* The External ID or Subscription ID if applicable
* The URL to the message you tested in the OneSignal Dashboard if applicable
* Any relevant [logs or error messages](/docs/en/capturing-a-debug-log)
We're happy to help!
# Create a custom email unsubscribe page
Source: https://documentation.onesignal.com/docs/en/create-custom-unsubscribe-page
Learn how to replace OneSignal's default unsubscribe link with a branded, multi-language, and personalized email preferences page, while maintaining compliance and tracking.
## Overview
OneSignal provides a [default email-compliant unsubscribe experience](./unsubscribe-links-email-subscriptions) that injects a link into your email templates so users can unsubscribe with ease and their [subscription statuses](./subscriptions) are updated in real-time. If you want full control over branding, copy, and fields (such as category opt-outs), you can replace the default link with your own custom page and use the OneSignal API to unsubscribe or update user preferences.
This guide explains how to add your own custom unsubscribe page to emails (removing the default OneSignal link) and which of our APIs to use to unsubscribe the user's email Subscription.
If you want to add more functionality to your custom unsubscribe page (like opt out of specific email categories instead of all), this is detailed in our [Preference Center](./preference-center) tutorial.
***
## Remove OneSignal's default unsubscribe link
OneSignal automatically inserts a special link in the format `[unsubscribe_url]` to your email templates. This URL unsubscribes the user from all emails in OneSignal. See [Email Unsubscribe Links](./unsubscribe-links-email-subscriptions) for details.
To use your own page, **locate and remove the default link** in your template.
In the drag-and-drop editor, the default link may appear nested like:
```html theme={null}
Unsubscribe
```
***
## Add your custom unsubscribe link
Now that you have removed our special link, you can replace it with your own URL.
Many times, these links require some additional data to be passed to your page. Use [Liquid variables](./message-personalization) to pass OneSignal data to your page.
Common parameters:
| Parameter | Description |
| -------------------------------- | ------------------------------------- |
| `subscription.email` | Subscriber’s email address |
| `subscription.external_id` | User’s external ID |
| `app.id` | OneSignal App ID |
| `message.id` | ID of the email notification |
| `subscription.language` | Preferred language (for localization) |
| `subscription.unsubscribe_token` | Security token for API verification |
**Example URL:**
```liquid theme={null}
https://examplesite.com/unsubscribe?app_id={{app.id}}¬ification_id={{message.id}}&email={{subscription.email}}&language={{subscription.language}}&token={{subscription.unsubscribe_token}}
```
```html HTML theme={null}
```
### Disable click tracking
Unsubscribe clicks are generally not used for engagement metrics. If you want to disable link tracking, you can add the `data-disable-tracking="true"` attribute to your link like this:
```html HTML theme={null}
Unsubscribe
```
**Provider-specific attributes:**
| Provider | Attribute |
| --------- | ------------------------------ |
| OneSignal | `data-disable-tracking="true"` |
| Mailgun | `disable-tracking=true` |
| SendGrid | `clicktracking=off` |
| Mandrill | `mc:disable-tracking` |
***
## Hosting your custom unsubscribe page
Deploy a web page that:
* Reads query parameters from the unsubscribe link.
* Displays user-friendly opt-out or preference options.
* Sends the unsubscribe or update request to OneSignal via API.
We provide a working [GitHub sample project](https://github.com/OneSignalDevelopers/custom-email-unsubscribe-page-sample) you can fork and deploy.
***
## Calling the OneSignal API
Depending on your use case, you can use the following APIs to unsubscribe or update user preferences:
* [Update Subscription by Token](/reference/update-subscription-by-token)
* [Unsubscribe Email with Token](/reference/unsubscribe-with-token)
* [Update User](/reference/update-user)
This API is most commonly used when you have the user's email address and just want to subscribe or unsubscribe them from all emails.
**Required query parameters:**
* `app_id`
* `token`
**Authentication required**
* Call this API from your server.
Use this API if you want to track which specific email caused the unsubscribe. This uses the email message ID to track the unsubscribe. See [Unsubscribe Email with Token](/reference/unsubscribe-with-token) API for more details.
**Required query parameters:**
* `app_id`
* `notification_id`
* `token`
**Optional parameters:**
* `email`
* `language`
**Example JavaScript:**
```javascript JavaScript theme={null}
const unsubscribeURL = (href) => {
const unsubscribeURL = new URL(href)
const appID = unsubscribeURL.searchParams.get("app_id")
const notificationID = unsubscribeURL.searchParams.get("notification_id")
const unsubscribeToken = unsubscribeURL.searchParams.get("token")
const language = unsubscribeURL.searchParams.get("language")
const email = unsubscribeURL.searchParams.get("email")
return {
unsubscribeURL: `https://api.onesignal.com/apps/${appID}/notifications/${notificationID}/unsubscribe?token=${unsubscribeToken}`,
meta: { language, email },
}
}
```
Update the user's preferences using the [Update User](/reference/update-user) API.
**Required query parameters:**
* `app_id`
* `alias_id` - The user's external ID is recommended.
**Authentication required**
* Call this API from your server.
***
You should now be equipped with everything you need to know about creating a custom unsubscribe page.
***
# Data feeds
Source: https://documentation.onesignal.com/docs/en/data-feeds
Pull real-time data from your APIs into OneSignal email messages at send time using Data Feeds, with Liquid syntax for personalization.
Data Feeds let you pull **real-time data from your APIs directly into messages** at send time. This allows you to deliver highly personalized content without pre-loading data into OneSignal.
Use Data Feeds when your data changes frequently, such as:
* A user’s current rewards balance
* Latest order status
* Personalized product recommendations
Other personalization methods (like [Tags](./add-user-data-tags) or [Dynamic Content](./dynamic-content)) work well for static data. **Data Feeds are best for live, fast-changing values**.
Data Feeds are currently available **only for email messages sent through Journeys**.
Need another channel? [Fill out this short survey](https://survey.survicate.com/954895d3e7ed1348/?p=anonymous).
***
## How Data Feeds work
1. **Create a Data Feed** – Configure how OneSignal connects to your API.
2. **Attach the Data Feed** to a message template.
3. **Insert response fields** in your message using [Liquid syntax](./using-liquid-syntax).
4. **At send time**, OneSignal makes an API call for each recipient, parses the response, and injects the data into your message.
### Example: Display reward points
Suppose you want to show each customer their rewards balance:
```liquid theme={null}
Hi {{ first_name }},
You have {{ data_feed.rewards.points }} points!
Your membership status is {{ data_feed.rewards.status_level }}.
Keep shopping to earn more points!
```
When Sarah receives this email, the Liquid variables are replaced with her actual points balance and membership status. The following sections walk through setting up this example step by step.
## Creating and using a Data Feed
### 1. Set up your Data Feed configuration
Navigate to **Data > Data Feeds** in the sidebar to see the list of existing Data Feeds and create a new one.
Each Data Feed must have:
* **Name**: A descriptive name like "Customer Rewards API" to help you distinguish feeds. Unique names are recommended but not required.
* **Alias**: A short identifier like `rewards` used in Liquid syntax (e.g., `{{ data_feed.rewards.points }}`). Must be unique, lowercase, alphanumeric, with no spaces or special characters.
* **Method**: The HTTP method OneSignal uses to contact your API. Usually `GET`, but `POST` is also supported.
* **URL**: Your API endpoint. Supports Liquid syntax so OneSignal can fetch user-specific data.
For example, your rewards endpoint might accept the user's `external_id` (stored in OneSignal) as a URL parameter:
```liquid theme={null}
https://acme.com/customers/user_id={{ external_id }}/rewards
```
* **Headers**: Key-value pairs required by your API (e.g., authentication tokens). Supports Liquid syntax.
* **Body**: Optional JSON request body. Supports Liquid syntax, the same as [Journey webhooks](./journeys-webhook#personalization).
For example, your API might require the user's ID in the request body instead of the URL:
```json theme={null}
{
"customer_id": "{{ subscription.external_id }}"
}
```
A complete Data Feed configuration looks like this:
Test your feed before using it in production. Data Feed tests run against your [Test Users](./test-users), so verify that those subscriptions have attributes that return a real result from your API.
Finally, **Activate** your new Data Feed so it’s ready for use.
### 2. Attach the Data Feed to your message template
Attach your Data Feed to your message template so that OneSignal knows to use it.
1. Navigate to **Messages > Templates**
2. In the Message section, select the **Personalization** button
3. Toggle on **Data Feeds** and select your feed
4. Save your template
### 3. Use the data in your message
Use Liquid syntax to insert response data anywhere in your message. Continuing the rewards example, the API response for Sarah (whose `external_id` is `a1-b2c3`) might look like this:
```json theme={null}
{
"external_id": "a1-b2c3",
"points": 193,
"status_level": "Gold"
}
```
To insert the number of points and the status level, use dot notation to reference the Data Feed alias and response fields:
```liquid theme={null}
You have {{ data_feed.rewards.points }} points!
Your membership status is {{ data_feed.rewards.status_level }}.
```
This tells OneSignal:
* Use a Data Feed
* Use the `rewards` Data Feed
* Recall: the `rewards` feed knows to call the API with the `external_id` of the recipient
* From the response, insert the value of the `points` item (193) and the `status_level` item (Gold)
***
## Requirements and limits
Your API needs to:
* **Accept single-step authentication** with auth tokens in headers
* **Respond quickly.** Under 250ms recommended (this directly affects send speed)
* **Return JSON.** Other formats are not supported at this time.
* If you need a different format, [share your use case](https://survey.survicate.com/954895d3e7ed1348/?p=anonymous).
* **Handle your send volume.** A low rate limit on your API slows message delivery.
* **Return reasonably-sized payloads.** Keep responses under 50 KB for best performance.
Current limits:
* **One Data Feed per template.** Fetch everything you need in a single API response.
* **One API call per Data Feed per message.**
* **Journeys only.** Not yet available for other sending methods.
* **No chaining calls.** The payload from one Data Feed cannot be used to call another.
Need multiple Data Feeds per template or support for other channels? [Share your use case](https://survey.survicate.com/954895d3e7ed1348/?p=anonymous) to help prioritize these features.
***
## Setting up your API
Before creating a Data Feed, ensure your API can handle these requirements:
### Authentication
Your API should accept authentication via headers:
```
Authorization: Bearer YOUR_TOKEN
```
or
```
X-API-Key: YOUR_KEY
```
#### Disallowed headers
The following headers are restricted and cannot be set:
* `content-length`
* `referer`
* `metadata-flavor`
* `x-google-metadata-request`
* `host`
* `x-onesignal*`
### JSON request body
If you need to include a body in the request, your API should accept JSON. This may mean your headers need to include `Content-Type: application/json`.
### JSON response
Your API should return a JSON object. Typically this means your headers will include `Accept: application/json`.
### Personalization parameters
You'll typically pass user identifiers in the URL like this:
```liquid theme={null}
https://api.example.com/users/{{ external_id }}/data
https://api.example.com/rewards?email={{ email | url_encode }}
```
And/or in the body:
```json theme={null}
{
"customer_id": "{{ external_id }}",
"email": "{{ subscription.email }}"
}
```
Make sure this data will exist in OneSignal (usually as Tags, but other options are available such as [custom event properties](#examples-and-advanced-use-cases)).
### Rate limits
Consider your API's rate limits. Sending to 10,000 users means 10,000 API calls in rapid succession. Ensure your API can handle this volume.
### Error handling
If your API returns an error or doesn't have data for a user, the message won't be sent to that recipient. Make sure your API returns data for all expected users.
***
## Getting started checklist
Before implementing Data Feeds, answer these questions:
* What data do I want to show in my message? Working backwards from a simple outline with the items to be populated from your API identified will help you organize your thinking.
* Is this data available via a single API endpoint?
* How will I authenticate API requests?
* What identifier or other data item will I use to fetch personalized data?
* Is that identifier already stored in OneSignal? If not, how will it be populated?
* Can my API handle the volume of requests I'll generate?
* What happens if my API doesn't have data for a user?
***
## Examples and advanced use cases
Data Feeds can be used with Liquid syntax or in combination with other features in creative ways to produce more complex personalization.
Let's say you have a Data Feed cart that returns an array of items in the user's cart, plus the cart total dollar amount:
```json theme={null}
{
"items": [
{
"name": "Blue Running Shoes",
"price": 84.00,
"image_url": "https://acme.com/blue-running-shoes.png"
},
{
"name": "Protein Bar",
"price": 5.99,
"image_url": "https://acme.com/protein-bar.png"
}
],
"total": 89.99
}
```
If you want to show each item in the cart, plus the cart total, you can use a for loop in Liquid:
```html theme={null}
{% for item in data_feed.cart.items %}
{{ item.name }}
${{ item.price }}
{% endfor %}
Cart total: ${{ data_feed.cart.total }}
```
This will result in:
```text theme={null}
- Blue Running Shoes
- $84.00
-
- Protein Bar
- $5.99
-
Cart total: $89.99
```
If you're using the email block editor, when inserting this sort of complex Liquid syntax, particularly if you need to include images or links, for best results use the custom HTML block element.
Want to see how to trigger this abandoned cart email using custom events? Check out the **Custom events properties** section below for the complete workflow.
Continuing the previous abandoned cart example, how might we know how to fetch that particular cart in the first place?
One method might be to create a Journey triggered by a `cart_abandoned` [custom event](./custom-events), where the properties includes a cart\_id. In this example that event is being sent to OneSignal via API:
```bash theme={null}
curl --request POST \
--url https://api.onesignal.com/apps/{app_id}/custom_events \
--header 'Accept: application/json' \
--data '{
"events": [
{
"name": "cart_abandoned",
"external_id": "user_12345",
"properties": {
"cart_id": 98765
}
}
]
}'
```
The user `user_12345` enters the journey when this event is fired, then reaches a node sending an email. That email template is set up with the `cart` Data Feed, where the URL is set to retrieve the contents of a particular cart like so:
```liquid theme={null}
https://acme.com/carts/{{ journey.event.cart_abandoned.data.cart_id }}
```
Thus, when this particular event is ingested and triggers the Journey:
1. The `cart_id` value of `98765` will be stored to the Journey
2. When the email step is reached, the `cart` Data Feed will reference that `cart_id` value and use it to call the cart API
3. The returned JSON properties will be parsed and inserted into the email as in the previous example above
Want to see how to conditionally display Data Feed content based on order status? Check out the **Conditional display: order status** section below to learn more.
Let's say you want to include the status of a customer's order, but only include a tracking number link if the order has been shipped. You can use an `if` statement to do so:
```liquid theme={null}
Your order is {{data_feed.order.status}}!
{% if data_feed.order.tracking_number != empty %}
Track it here: {{data_feed.order.tracking_url}}
{% endif %}
```
Here, the tracking link will only be displayed if the `tracking_number` exists.
Data Feeds can be used to automatically insert up-to-date information into your messages without necessarily needing to be personalized per recipient.
For example, perhaps you insert a banner image at the top of your emails and change it monthly to keep up with holidays and other monthly events. Rather than remembering to upload a new image to OneSignal and changing all your templates each month, you might set up a Data Feed that fetches the current banner image URL from your CMS or other asset-management location.
You would set up a `banner` Data Feed that points to an endpoint **without** any variables in the URL like so:
```liquid theme={null}
https://acme.com/assets/email-banner
```
Which returns a response with the current banner URL:
```json theme={null}
{
"banner_url": "https://acme.com/assets/email-banner/2025july.png"
}
```
You'd set your email template to use `{{ data_feed.banner.banner_url }}` as the image source URL, automating this process going forward.
This example covers how to send personalized, single-use coupon codes in emails using a Data Feed that fetches a unique value from your API for each recipient.
### Goal
Send an email such as:
> Hi George,
>
> Complete your booking in the next 2 hours and save 10% with your personal code: XYZ123ABC
Each user receives a unique coupon code, generated live via an external API call when the email is sent, valid for the given time window, and scoped to that user.
### Prerequisites
* [Email channel configured](./email-setup) in OneSignal (your app has email capability enabled)
* An external API that accepts a user identifier (for example, [`external_id`](./users#external-id)) and campaign identifier, and returns JSON with the coupon code, discount, and expiration
* A unique identifier (such as `external_id`) for each user that your API can use to generate coupons
* A [segment](./segmentation) or trigger (for example, "abandoned search in last 24h") used to send the email through a OneSignal Journey
### Step 1: Create the Data Feed
In your OneSignal dashboard, select **Data > Data Feeds** from the navigation bar, then click **New Data Feed**.
Configure the following fields:
* **Data Feed Name**: `coupon_code_generator`
* **Alias**: `coupon`
* **Method**: `GET`
* **URL**: `https://api.example.com/generate-coupon?userId={{ external_id }}&campaign=AbandonedBooking10`
* **Headers**:
```json theme={null}
{
"Authorization": "Bearer YOUR_API_TOKEN"
}
```
When the API returns JSON like this:
```json theme={null}
{
"code": "AB10-5F3K-HT9L",
"discount_percent": "10%",
"expires_in_hours": 2
}
```
You can reference its values in your email template as:
* `{{ data_feed.coupon.code }}`
* `{{ data_feed.coupon.discount_percent }}`
* `{{ data_feed.coupon.expires_in_hours }}`
Click **Send Test** to verify your configuration, then click **Activate** to make the Data Feed available for use in templates.
### Step 2: Create the email template
1. In OneSignal, go to **Messages > Email > New Template**
2. Use the Data Feed alias and fields within your message body. For example:
```html theme={null}
Hi {{ first_name }},
Complete your booking in the next {{ data_feed.coupon.expires_in_hours }} hours and save
{{ data_feed.coupon.discount_percent }} with your personal code:
{{ data_feed.coupon.code }}
```
* Use Liquid syntax in the form `{{ data_feed.. }}`
* Make sure the Data Feed is attached to the template
* If using the drag-and-drop editor with custom Liquid logic, use an HTML block
### Step 3: Attach the Data Feed and trigger the email
1. In the template composer, under **Personalization > Data Feeds**, toggle the feed on and select `coupon_code_generator`
2. This ensures OneSignal makes the API call at send time for each recipient, populates the data, and injects it into the email
3. Set up a Journey to automate the message:
* **Entry condition**: Segment such as "abandoned search in last 24 hours"
* **[Wait until](./custom-events#wait-until-event)**: Create a `booking_completed` custom event. Configure the wait to exit the Journey if this event fires, or continue after 1 hour. If they complete a booking, they exit without receiving the email; otherwise, they continue to receive the coupon.
* **Send email**: Use the personalized coupon email template
* Ensure the Journey uses the email channel and that recipients are subscribed to email
### Step 4: Manage coupon redemption and tracking
Your backend should:
1. Record each generated code along with `userId`, `code`, `campaign`, `expiration_time`, and a `redeemed` flag
2. Validate and mark codes as redeemed upon checkout
3. Log the redemption data (user, campaign, and time) for ROI analysis
### Complete workflow example
| Step | Event | Action |
| ---- | ---------------------------------------- | -------------------------------------------------------------------------------------------- |
| 1 | User triggers "abandoned search" segment | User enters Journey via segment |
| 2 | Journey triggers email send | OneSignal attaches Data Feed, calls your API with the user's `external_id` and campaign name |
| 3 | API returns JSON | OneSignal populates data feed coupon fields and sends email |
| 4 | User receives email | Example: "Hi George! Save 10% with your personal code: AB10-5F3K-HT9L" |
| 5 | User redeems code | Backend validates and logs redemption |
### Testing
* Test with a user who has a known `external_id`
* Call your API manually to confirm correct JSON responses
* Use **Preview** in the OneSignal template editor to verify that data feed coupon fields populate
* Check API logs for latency and errors
* If a Data Feed call fails, OneSignal will skip sending the message to that recipient
If you want to send dynamic content in your emails but don't have a backend database or API, Google Sheets combined with Google Apps Script is a simple alternative. This guide walks you through setting up a Google Sheet as a live JSON endpoint that OneSignal can pull from at send time.
This approach works well for any manually curated content that updates on a regular cadence — weekly newsletters, product roundups, editorial digests, event listings, and more.
### How it works
1. You maintain a Google Sheet with your content (one row per item)
2. A small Apps Script converts the sheet into a JSON endpoint
3. OneSignal fetches that JSON at send time via a Data Feed
4. Liquid tags in your email template populate with the sheet data
### Step 1: Set up your Google Sheet
Create a new Google Sheet with your content. Use the first row as column headers — these become your Liquid variable names.
**Example structure:**
| title | image | price | link | category |
| --------- | ------------ | ------- | ------------ | ---------- |
| Product A | https\://... | \$24.00 | https\://... | Category 1 |
| Product B | https\://... | \$68.00 | https\://... | Category 2 |
Keep column headers lowercase with no spaces — this makes them easier to reference in Liquid tags.
### Step 2: Add the Apps Script
In your Google Sheet, go to **Extensions > Apps Script** and paste the following code:
```javascript theme={null}
function doGet() {
const sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
const data = sheet.getDataRange().getValues();
const headers = data[0];
const rows = data.slice(1);
const result = {};
rows.forEach((row, index) => {
const slot = index + 1;
headers.forEach((header, i) => {
result[`item_${slot}_${header}`] = row[i];
});
});
return ContentService.createTextOutput(JSON.stringify(result))
.setMimeType(ContentService.MimeType.JSON);
}
```
This script reads your sheet and transforms it into a flat JSON object. Each row becomes a numbered item slot:
```json theme={null}
{
"item_1_title": "Product A",
"item_1_price": "$24.00",
"item_2_title": "Product B",
"item_2_price": "$68.00"
}
```
### Step 3: Deploy the script as a web app
Click **Deploy > New deployment**.
Select type: **Web app**. Set **Execute as**: Me. Set **Who has access**: Anyone.
Click **Deploy** and copy the web app URL. This is the URL you'll use in OneSignal.
Any time you update the script, you must create a **new version** under Manage Deployments for changes to take effect on the live URL. Saving the script alone is not enough.
### Step 4: Create a Data Feed in OneSignal
In your OneSignal dashboard, go to **Data > Data Feeds** and click **New Data Feed**.
Set a descriptive **Name** (e.g. `Weekly Content`) and an **Alias** you'll reference in Liquid tags (e.g. `weekly_content`). Set the **URL** to your Apps Script web app URL.
Save and activate the Data Feed so it's available for use in templates.
### Step 5: Use Liquid tags in your email template
Reference your sheet data in any email template using the following syntax:
```liquid theme={null}
{{ data_feed.weekly_content.item_1_title }}
{{ data_feed.weekly_content.item_1_price }}
{{ data_feed.weekly_content.item_1_image }}
{{ data_feed.weekly_content.item_2_title }}
```
The pattern is always:
```liquid theme={null}
{{ data_feed.{alias}.item_{row number}_{column header} }}
```
### Step 6: Attach the Data Feed to your message
When creating a new email message or template, attach the Data Feed under **Personalization > Data Feeds** before sending. OneSignal fetches the latest data from your sheet at send time.
### Updating content
To update your email content for the next send, open your Google Sheet and update the rows. No script changes or redeployment needed — the URL always serves the current sheet data.
### Tips
* **Add or remove rows freely.** Just make sure your Liquid tags in the template match the number of rows in your sheet.
* **Column headers are your variable names.** Renaming a column means updating the Liquid tags in your template to match.
* **Test your endpoint first.** Paste your Apps Script URL directly in a browser to verify the JSON output before connecting it to OneSignal.
* **Format numbers as text.** If you include currency or other formatted values, set those cells to Plain Text in Google Sheets to prevent formatting from being stripped.
* **The script is reusable.** The same Apps Script works for any sheet structure — no modifications needed.
***
## FAQ
### My Data Feed values are not appearing in the message. What should I check?
Verify these in order:
1. The Data Feed is **attached** to the template (under **Personalization > Data Feeds**).
2. Your Liquid syntax matches the JSON response structure exactly — `{{ data_feed.. }}`.
3. The API endpoint returns valid JSON when called manually with the same identifiers.
4. The recipient has the required identifier (e.g., `external_id`) stored in OneSignal.
### Why are messages sending slowly?
Data Feed API calls run at send time for every recipient. If your API responds slowly or cannot handle concurrent requests, message delivery slows proportionally. Aim for under 250 ms response time and ensure your infrastructure can handle your send volume.
### Why are some recipients not getting messages?
If the Data Feed API call fails for a recipient — due to a 404, timeout, or missing data — OneSignal skips that recipient entirely. Check the error log in your Data Feed configuration and your own API logs for failures. Verify those users have the required identifiers in OneSignal.
### Can I use more than one Data Feed in a template?
Not currently. Each template supports one Data Feed. Fetch all the data you need in a single API response. If you need multiple feeds, [share your use case](https://survey.survicate.com/954895d3e7ed1348/?p=anonymous).
### Are Data Feeds available for push notifications or SMS?
No. Data Feeds are currently available only for email messages sent through Journeys. Support for additional channels is planned — [share your use case](https://survey.survicate.com/954895d3e7ed1348/?p=anonymous) to help prioritize.
### What happens if my API is down when the message sends?
OneSignal skips any recipient whose Data Feed call fails. The message is not sent to that recipient, and no fallback value is inserted. Monitor your API uptime and error rates during scheduled sends.
## Related pages
Full reference for Liquid templating in OneSignal messages.
Build automated messaging workflows that trigger Data Feed emails.
Trigger Journeys and pass event properties for Data Feed URLs.
Overview of all personalization methods — tags, Liquid, dynamic content, and Data Feeds.
# Data & background notifications
Source: https://documentation.onesignal.com/docs/en/data-notifications
Send silent push notifications to sync data or trigger background tasks on iOS and Android without displaying a visible message.
## What are silent notifications?
Silent notifications let you wake your app and perform background tasks—such as syncing or refreshing data—without showing a visible message or playing a sound.
On iOS, these are called **Background Notifications**, and on Android, they're known as **Data Notifications**. Together, they're often referred to as silent pushes and behave differently from normal, visible notifications.
### Limitations
* **Apps cannot receive silent pushes if**:
* **iOS**: The app has been closed by the user, such as swiping it away from the app switcher. (See [Apple support](https://support.apple.com/en-us/HT201330)).
* **Android**: The app has been force-quit via device settings or automatically by some manufacturers when swiped away. ([Android force-stop behavior](./notifications-show-successful-but-are-not-being-shown#android-app-is-force-stopped)).
* **Delivery is not guaranteed**:
* Both Apple and Google treat silent notifications as *best-effort*. iOS may delay or drop delivery under Low Power Mode, Background App Refresh off, or if the app was closed by the user. Android may throttle or batch delivery under Doze or OEM power-saving rules.
* Because of this, **silent notifications should never be used for critical updates**.
* **Subscribed users only**: The OneSignal SDK only sends data notifications to subscribed [Subscriptions](./subscriptions). To reach unsubscribed users, see [this iOS SDK workaround](https://github.com/OneSignal/OneSignal-iOS-SDK/issues/302).
* **Limited support for cross-platform SDKs**:
* Silent notifications must be handled in native code (Java/Kotlin for Android, Swift/Obj-C for iOS).
* iOS requires implementation of [`application:didReceiveRemoteNotification:fetchCompletionHandler:`](https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1623013-application).
* Android requires implementation of a [Notification Service Extension](./service-extensions).
***
## Sending silent notifications from OneSignal
Follow these steps to send a silent notification from OneSignal:
Remove any visible text or titles from the message. This includes:
* **API**: `contents`, `headings`, `subtitle` in your [Create notification](/reference/create-message) API request.
* **Dashboard**: Message, Title, Subtitle
* **API**: Set `content_available` to `true`.
* **Dashboard**: Check **Content available** under "Send to Apple iOS". Despite the label, this setting applies to all platforms and signals to OneSignal that no visible message is included.
* **API**: Use the `data` parameter.
* **Dashboard**: Use the **Additional Data** fields.
### Example API payload
```bash theme={null}
curl -X POST https://api.onesignal.com/notifications \
-H "Authorization: Key YOUR_APP_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"app_id": "YOUR_APP_ID",
"include_aliases": { "external_id": ["user-123"] },
"target_channel": "push",
"content_available": true,
"data": {
"action": "sync_data",
"version": "2"
}
}'
```
***
## Platform-specific setup
### iOS background notification setup
To handle background notifications, your iOS app must have the **Background Modes > Remote Notifications** capability enabled in Xcode. This is typically added if you followed the [Mobile SDK Setup](./mobile-sdk-setup).
To process the notification, implement the `AppDelegate` method [`application(_:didReceiveRemoteNotification:fetchCompletionHandler:)`](https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1623013-application).
Apple documentation:
* [Pushing Background Updates to Your App](https://developer.apple.com/documentation/usernotifications/setting_up_a_remote_notification_server/pushing_background_updates_to_your_app?language=objc)
* [Generating a Remote Notification](https://developer.apple.com/documentation/usernotifications/generating-a-remote-notification)
If the user has closed the app (swiped it away from the app switcher), iOS will not deliver the notification.
In such cases, include a visible `contents` message and process data in the [`UNNotificationServiceExtension.didReceive`](./service-extensions#ios-notification-service-extension).
***
### Android data notification setup
Handle data notifications on Android using the [Notification Service Extension](./service-extensions).
This lets you:
* Process notifications as long as the app hasn't been force closed
* Customize how notifications are displayed or suppressed
Android documentation:
* [FCM data messages](https://firebase.google.com/docs/cloud-messaging/concept-options#data_messages)
* [Optimize for Doze and App Standby](https://developer.android.com/training/monitoring-device-state/doze-standby)
If the app has been force-stopped (via device settings or by some OEM battery optimization), Android will not deliver the notification. See [Android force-stop behavior](./notifications-show-successful-but-are-not-being-shown#android-app-is-force-stopped).
***
## Sending VoIP notifications
VoIP notifications are supported but require additional configuration outside the standard OneSignal SDKs. OneSignal does not register VoIP tokens automatically.
Configure VoIP push notifications for real-time calling on iOS.
***
## FAQ
### Can silent notifications be used to detect uninstalls or unsubscribes?
Not reliably. Silent notifications are best-effort and not guaranteed to be delivered, as explained in the [Limitations](#limitations) section.
Instead:
* Send visible notifications (with content) to all your users at least once a month.
* Optionally send silent notifications as a supplemental check.
For more details on handling subscription status changes, see the [Subscriptions](./subscriptions) guide.
### Do confirmed deliveries work with silent notifications?
No. Confirmed deliveries require the SDK to execute a callback when the notification is displayed. Silent notifications are not displayed, so the SDK receipt callback is never triggered.
### Can I use silent notifications to measure how many users are reachable?
No, not reliably. Silent notifications aren't guaranteed delivery.
1. **Delivery is not guaranteed** — both Apple and Google treat silent notifications as best-effort and may delay or drop them. For example, iOS will drop them if the app is closed (force quit) and Android and iOS will throttle them under power-saving rules.
2. **Delivery is not confirmed** — confirmed deliveries do not work with silent notifications, so you have no way to know which users actually received the push.
To measure reachable users, send a visible notification and track delivery metrics in [Analytics](./analytics-overview).
***
Handle notification processing in native code on iOS and Android.
Understand Subscription types and how they connect to Users.
Send notifications programmatically, including silent pushes.
# Deep linking
Source: https://documentation.onesignal.com/docs/en/deep-linking
Send users from OneSignal push, in-app, email, and SMS messages to a specific screen in your Android or iOS app using Universal Links, App Links, or custom URI schemes.
## Overview
A deep link routes the recipient of a OneSignal message to a specific screen in your app instead of a web page. OneSignal delivers the link to the device; the operating system and your app are responsible for resolving it.
This guide covers how to use deep links **inside OneSignal messages** — it does not cover how to configure your app to receive them. For platform setup, see Apple's [Supporting universal links in your app](https://developer.apple.com/documentation/xcode/supporting-universal-links-in-your-app) and Google's [About deep links](https://developer.android.com/training/app-links).
***
## Prerequisites
Before you can deep-link from a OneSignal message, your app must already be set up to receive the link type you plan to use:
* The [OneSignal Mobile SDK](./mobile-sdk-setup) is installed and initialized.
* Your app developers have configured a deep link handler for **each platform you target**:
* **iOS** — [Universal Links](https://developer.apple.com/documentation/xcode/supporting-universal-links-in-your-app) (Associated Domains entitlement + [apple-app-site-association (AASA) file](https://developer.apple.com/documentation/xcode/supporting-associated-domains) hosted on your domain) and/or a [custom URL scheme](https://developer.apple.com/documentation/xcode/defining-a-custom-url-scheme-for-your-app) registered in `CFBundleURLTypes`.
* **Android** — [App Links](https://developer.android.com/training/app-links) (intent filters with `autoVerify="true"` + [Digital Asset Links file (`assetlinks.json`)](https://developer.android.com/training/app-links/verify-applinks) hosted on your domain) and/or a custom URI scheme registered in your `AndroidManifest.xml`.
* For email and SMS/RCS, only Universal Links / App Links work — custom URI schemes are not clickable from those clients.
Once a link is working on the device for a regular tap (paste into Notes on iOS or run `adb shell am start -a android.intent.action.VIEW -d ""` on Android), OneSignal can deliver it through any supported channel.
***
## Choose a link type
| Type | URL format | Supported in OneSignal | Fallback when app is not installed |
| ---------------------------- | ----------------------------- | ---------------------------- | ---------------------------------- |
| **Universal Links** (iOS 9+) | `https://yourdomain.com/path` | Push, in-app, email, SMS/RCS | Opens the URL in Safari |
| **App Links** (Android 6+) | `https://yourdomain.com/path` | Push, in-app, email, SMS/RCS | Opens the URL in the browser |
| **Custom URI schemes** | `myapp://path` | Push, in-app only | Fails silently or shows an error |
**Recommendation:** Use Universal Links / App Links (`https://`) for anything sent via email or SMS — custom schemes do not work in those clients. For push and in-app messages, either works.
On Android 12+, an `https://` link that is **not** a verified App Link opens in the user's default browser, not your app. If deep links from push or email are opening the browser on Android, the App Link verification is the first thing to check.
***
## Send a deep link in a push notification
OneSignal supports two ways to attach a deep link to a push notification. Pick one based on how much control you want over navigation.
### Launch URL (`url` / `app_url`)
Set the Launch URL in the dashboard, or pass `url` (all platforms) or `app_url` (mobile only) in the API.
```json theme={null}
{
"app_id": "YOUR_APP_ID",
"contents": { "en": "Your order shipped" },
"url": "https://yourdomain.com/orders/12345"
}
```
| Platform | Behavior when the notification is tapped |
| -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Android | Android resolves the URL. Verified App Link → opens directly in your app. Non-verified `https://` → opens the browser. Custom scheme → opens the registered app. |
| iOS | OneSignal's iOS SDK calls `openURL`, which opens Safari first, then bounces to your app through the Universal Link handshake. See [iOS Launch URL behavior](#ios-launch-url-behavior). |
### Additional Data (`data`) — recommended for mobile
Put the destination in Additional Data instead and do the navigation yourself in the [push click listener](#handle-deep-links-in-your-app). This avoids the iOS browser bounce entirely and lets you pass structured context.
```json theme={null}
{
"app_id": "YOUR_APP_ID",
"contents": { "en": "Your order shipped" },
"data": {
"deep_link": "https://yourdomain.com/orders/12345",
"order_id": "12345"
}
}
```
For mobile push, prefer Additional Data + a click listener. You get full control over navigation, no browser flash on iOS, and you can pass extra fields (like `order_id`) that aren't part of the URL.
### iOS Launch URL behavior
OneSignal's iOS SDK uses `openURL` to handle the `url` / `app_url` property. Even with Universal Links correctly configured, this causes the link to open in the browser first and then redirect back into the app — a noticeable "flash" for users.
You have two ways to avoid it:
1. **Use `data` instead of `url`** and route from your [push click listener](#handle-deep-links-in-your-app). Recommended.
2. **Suppress Launch URLs globally.** Add a Boolean `OneSignal_suppress_launch_urls` with value `YES` to your app's `Info.plist`. OneSignal will stop calling `openURL`, and you must handle every deep link in the click listener.
***
## Send a deep link in an in-app message
In-app messages use click actions instead of a URL property. For the full list of click action types, see [In-app click actions](./iam-click-actions).
### Drag-and-drop editor
1. Add a button, image, or background click target.
2. Open **Add click action**.
3. Choose one of:
* **URL** — opens the link in the device's browser (or, if Universal Links / App Links are configured for that domain, opens your app).
* **Custom action ID** — passes the value to your [in-app click listener](#handle-deep-links-in-your-app) without opening anything. Use this when you want to navigate inside the app you control.
4. Enter your deep link (e.g. `https://yourdomain.com/promo`) or action identifier (e.g. `open_promo`).
### HTML editor
In sandboxed HTML in-app messages, use the [In-App Message JS API](./in-app-message-api):
* `OneSignalIamApi.openUrl(e)` — opens the URL in the device browser using default URL handling.
* `OneSignalIamApi.addClickName(e)` — sends a custom click name to your [in-app click listener](#handle-deep-links-in-your-app) without opening a browser.
```html theme={null}
```
***
## Send a deep link in an email
By default, OneSignal rewrites every link in an email for click tracking. The rewritten URL uses OneSignal's tracking domain, which does **not** match your app's Associated Domain or Digital Asset Links host — so iOS and Android treat the link as a regular web URL and open the browser instead of your app.
To preserve deep linking in email, disable click tracking for the deep link using one of these options:
| Scope | How |
| --------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Whole email (dashboard)** | Uncheck **Track link clicks** in the email editor. |
| **Whole email (API)** | Set `disable_email_click_tracking: true` in the Create message request. |
| **Single link (HTML)** | Add `disable-tracking=true` to the anchor tag: `…`. Tracking stays enabled on every other link. |
Disabling tracking removes click metrics from [Email Message Reports](./email-message-reports). Use the per-link `disable-tracking=true` attribute to keep analytics on non–deep-link URLs.
### Expected behavior
| Scenario | Result |
| ----------------------------------------------------------------- | --------------------------------------------------------------------- |
| iOS + Safari + Universal Link + tracking disabled | Opens app directly |
| iOS + Safari + Universal Link + tracking enabled | Opens Safari, link doesn't match AASA, stays in browser |
| iOS + non-Safari mail client + Universal Link + app not installed | Falls back to web; can redirect to App Store |
| Android + App Link + tracking disabled | Opens app directly |
| Android + App Link + tracking enabled | Opens browser, link doesn't match `assetlinks.json`, stays in browser |
***
## Send a deep link in SMS or RCS
SMS and RCS messages support only `https://` links — custom schemes like `myapp://` won't open from the Messages app. Insert the link inline in the message body.
If you use OneSignal's trackable link feature (`{{ "https://..." | track_link }}` or the **Insert Trackable Link** button), the URL is replaced with a `1sgnl.co` short link, which breaks Universal Link / App Link domain matching. Do not wrap deep-link URLs in `track_link` — send them raw so the OS can match them to your app.
See [URLs, links, & deep links → SMS/RCS trackable links](./links#sms%2Frcs-trackable-links) for the tracking-link format.
***
## Handle deep links in your app
The most reliable way to route a OneSignal deep link is to intercept it with a click listener and do the navigation yourself. This works regardless of platform quirks (iOS `openURL`, Android 12+ verification, email tracking rewrites) as long as your listener receives the event.
### Push notification click listener
Register the listener as early as possible — in `Application.onCreate()` on Android or `application(_:didFinishLaunchingWithOptions:)` on iOS — so it is active before the user taps a cold-start notification.
```kotlin Kotlin theme={null}
OneSignal.Notifications.addClickListener { event ->
val url = event.notification.launchURL
val data = event.notification.additionalData
// Route to the correct screen based on url or data
}
```
```swift Swift theme={null}
class AppDelegate: UIResponder, UIApplicationDelegate, OSNotificationClickListener {
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
OneSignal.Notifications.addClickListener(self)
return true
}
func onClick(event: OSNotificationClickEvent) {
let url = event.notification.launchURL
let data = event.notification.additionalData
// Route to the correct screen based on url or data
}
}
```
```javascript React Native theme={null}
OneSignal.Notifications.addEventListener('click', (event) => {
const url = event.notification.launchURL;
const data = event.notification.additionalData;
// Route to the correct screen based on url or data
});
```
```dart Flutter theme={null}
OneSignal.Notifications.addClickListener((event) {
final url = event.notification.launchUrl;
final data = event.notification.additionalData;
// Route to the correct screen based on url or data
});
```
```csharp Unity (C#) theme={null}
OneSignal.Notifications.Clicked += (sender, e) => {
var url = e.Notification.LaunchURL;
var data = e.Notification.AdditionalData;
// Route to the correct screen based on url or data
};
```
For the full API including Java, Objective-C, and Cordova/Ionic, see [`addClickListener()` Push](./mobile-sdk-reference#addclicklistener-push).
### In-app message click listener
Fires when a user taps a button configured with a **Custom action ID** (or when `OneSignalIamApi.addClickName` is called in HTML).
```kotlin Kotlin theme={null}
OneSignal.InAppMessages.addClickListener { event ->
val actionId = event.result.actionId
// Route based on the action ID (your deep link or internal route name)
}
```
```swift Swift theme={null}
func onClick(event: OSInAppMessageClickEvent) {
let actionId = event.result.actionId
// Route based on the action ID (your deep link or internal route name)
}
```
```javascript React Native theme={null}
OneSignal.InAppMessages.addEventListener('click', (event) => {
const actionId = event.result.actionId;
// Route based on the action ID (your deep link or internal route name)
});
```
```dart Flutter theme={null}
OneSignal.InAppMessages.addClickListener((event) {
final actionId = event.result.actionId;
// Route based on the action ID (your deep link or internal route name)
});
```
For the full API, see [`addClickListener()` In-App](./mobile-sdk-reference#addclicklistener-in-app).
***
## Personalize deep links per user
Every URL field in OneSignal — Launch URL, Additional Data values, in-app URL actions, email body links — accepts [Liquid syntax](./using-liquid-syntax). Use it to inject the user's `external_id`, tags, or `custom_data` into the path or query string.
```text theme={null}
https://yourdomain.com/profile/{{subscription.external_id}}
https://yourdomain.com/orders/{{message.custom_data.order_id}}
https://yourdomain.com/topic/{{ interest | default: 'home' }}
```
See [Dynamic URLs](./links#dynamic-urls) for the full list of data sources and fallback filters.
***
## Test deep links from OneSignal
1. Send a test message from the OneSignal dashboard with your deep link as the Launch URL, Additional Data value, or in-app click action.
2. Tap the notification or in-app button on a real device (Universal Links do not activate in the iOS Simulator from all sources).
3. Confirm the app opens directly to the right screen and your click listener logs the expected URL or action ID.
For platform-level verification (before involving OneSignal), Apple provides the [Associated Domains validation tool](https://search.developer.apple.com/appsearch-validation-tool/) and Android offers `adb shell pm verify-app-links --re-verify `.
On iOS, Universal Links do not activate from the Safari address bar. Test from Notes, Mail, Messages, or a OneSignal push/in-app message.
***
## Troubleshooting
### iOS push opens Safari for a second before my app
OneSignal's SDK uses `openURL` for the Launch URL. Fix with one of the [two approaches above](#ios-launch-url-behavior): send the link in Additional Data and route from the click listener, or set `OneSignal_suppress_launch_urls=YES` in `Info.plist`.
### Android push opens the browser instead of the app
The `https://` link is not being recognized as a verified App Link. Check that your intent filter uses `android:autoVerify="true"`, that `assetlinks.json` is reachable at `https://yourdomain.com/.well-known/assetlinks.json` (HTTP 200, `application/json`, no redirects), and that its SHA-256 fingerprint matches the certificate you actually shipped (Play App Signing uses a different fingerprint than your local keystore). See Google's [Troubleshoot App Links](https://developer.android.com/training/app-links/troubleshoot).
### Deep link works in push but not in email
Email click tracking rewrites the URL and breaks domain matching. [Disable tracking](#send-a-deep-link-in-an-email) for that link or that email.
### Deep link is received but the app doesn't navigate
* The click listener was registered **after** the click event fired. Register in `Application.onCreate()` (Android) or `application(_:didFinishLaunchingWithOptions:)` (iOS), before any other async work.
* The `launchURL` or `actionId` doesn't match what your router expects. Log the raw value.
* On iOS, the Launch URL (`url`) is being used without suppressing it — the browser may consume the link before your listener fires. Switch to Additional Data.
### I updated my AASA file and iOS still doesn't open the app
Starting in iOS 14 / macOS 11, Apple's CDN fetches AASA files, not the device. The CDN requests a refresh within 24 hours, and installed devices re-check roughly once per week. Reinstall the app to force an immediate fetch during testing.
***
## FAQ
### Should I use Launch URL or Additional Data?
Additional Data is recommended for mobile deep links. It bypasses iOS's `openURL` browser flash and gives your click listener structured data instead of a string to parse. Launch URL is simpler and fine for web push or when you accept the iOS behavior.
### Do deep links work with OneSignal Journeys?
Yes. Set the Launch URL or Additional Data on any push or in-app step the same way you would for a one-off message. Email steps follow the same click-tracking rules — disable tracking on the deep link.
### Can I deep link into another app (not mine)?
Yes, for push and in-app only. Set a custom URL scheme that the other app registers (e.g. `whatsapp://send?phone=15551234567`). Custom schemes do not work in email or SMS clients.
### What happens if the user doesn't have the app installed?
With Universal Links (iOS) or App Links (Android), the `https://` URL loads as a normal web page — you can host a landing page that redirects to the App Store / Play Store. With a custom URI scheme (`myapp://`), the tap fails silently or shows an error; there is no fallback.
### Can I use a trackable SMS link as a deep link?
No. OneSignal's SMS trackable links rewrite the URL to `1sgnl.co`, which isn't associated with your app. Send the original `https://` URL so the OS can match it to your Associated Domain or Digital Asset Links entry.
### Do I need the OneSignal SDK to handle a deep link?
No, but it's recommended. Universal Links / App Links are handled by the OS regardless of which SDK sent the message. Using OneSignal's click listener gives you a consistent entry point across channels and avoids platform-specific quirks like the iOS `openURL` flash.
***
Launch URLs, UTM parameters, dynamic URLs, and click tracking across every channel.
Full API reference for push and in-app click listeners and notification events.
Configure URL, custom action ID, and other click actions for in-app buttons.
Platform-specific push notification setup for Android and iOS.
# Design emails with drag-and-drop
Source: https://documentation.onesignal.com/docs/en/design-emails-with-drag-and-drop
Build responsive emails visually with OneSignal’s Drag and Drop Email Builder — settings, rows, content blocks, personalization, and compliance links.
## Overview
The OneSignal Drag and Drop Email Builder lets you visually design responsive emails exactly as they appear in recipients’ inboxes — without writing full HTML.
You build emails using three core components:
* **Settings** – global styles and layout applied across the entire email
* **Rows** – horizontal layout containers that control structure and responsiveness
* **Content** – individual blocks such as text, images, buttons, and HTML
The sections below cover each component and the recommended order for building an email.
Use the Drag and Drop Email Builder if you want to:
* Design emails visually without managing full HTML (HTML blocks are available)
* Reuse rows or templates across campaigns
* Allow non-technical teammates to safely edit content
If you need full HTML control, custom fonts everywhere, or advanced dark mode logic, use the [HTML Editor](./design-emails-with-html) instead.
### Recommended build flow
Follow this order for the best results and fewer rendering issues:
1. Configure global styles in **Settings**
2. Add layout structure using **Rows**
3. Insert **Content** blocks
4. Add personalization and links
5. Add an unsubscribe link (for marketing emails)
6. Save as a template or send
When finished, your email should:
* Be no wider than 600px
* Render cleanly on mobile and desktop
* Include required compliance links
**Common mistakes that cause rendering or deliverability issues:**
* **Missing unsubscribe link** — required for marketing emails. Without one, inbox providers are more likely to flag your email as spam.
* **Email wider than 600px** — causes horizontal scrolling on mobile and breaks layouts in many clients.
* **Background images in rows** — Outlook and several mobile clients do not render them. Use Image blocks instead.
* **No fallback fonts** — custom web fonts only load in a few clients. Always declare system font fallbacks.
* **Oversized images** — emails over 1 MB load slowly and may be blocked. Individual images should be under 200 KB.
***
## Settings
Settings define the foundational layout and styles for your email. These values cascade down to rows and content blocks unless explicitly overridden.
| Design Setting | Description |
| ----------------------------- | ---------------------------------------------------------------------- |
| Content area width | Width of the email in pixels. **Recommended:** `600px`. |
| Content area alignment | Align content left or center. |
| Background color | Color behind the content area. |
| Content area background color | Color inside the content area. |
| Default font | Applied to all text unless overridden. Custom fonts require HTML. |
| Link color | Default color for all links. |
| Language | Sets the HTML `lang` attribute for accessibility. Defaults to English. |
**Recommended default:** Configure as much styling as possible in Settings to ensure consistency and reduce per-block overrides.
***
## Rows
Rows define the horizontal layout of your email. Each row can contain one or more columns, and each column can contain content blocks.
Drag and drop rows into the editor to build your structure.
Use rows to control layout and spacing. Avoid relying on individual content blocks for major layout decisions.
### Saved rows
Saved rows let you reuse headers, footers, or repeated sections across emails and templates.
Click the **save icon** on a row to save it.
Access saved rows from **Rows > Saved rows**.
Saved row categories:
* **Empty** – blank row templates
* **My Saved Rows** – rows created by you or your team
* **Sample Rows** – OneSignal examples
### Row properties
Click the outer edge of a row to edit row-level settings.
If you see **Content** instead of **Row** when hovering, you’ve selected a content block. Click the outer container edge until the label reads **Row**.
| Row Property | Description |
| ---------------- | ------------------------------------------------------------------------------------------------- |
| Backgrounds | Color or image behind the row. **Recommended:** set background color in Settings for consistency. |
| Borders | Border color, width, and style. |
| Layout | Show or hide rows on mobile or desktop. |
| Columns | Add, remove, or resize columns and adjust column padding. |
| Delete/Duplicate | Select a row and use the **delete** or **duplicate** icons to remove or copy it. |
Avoid background images in rows. Email client support is inconsistent. If the row contains only an image, use an **Image block** instead.
***
## Content
Content blocks are the elements recipients see — text, images, buttons, dividers, and HTML.
Drag a content block into a row column. It automatically adapts to the column width.
**Decision rules for content blocks:**
* Use **Text blocks** for most copy.
* Use **Image blocks** for visuals or custom typography.
* Use **HTML blocks** only when you need advanced styling or behavior.
### Custom fonts
The Drag and Drop Editor supports system fonts by default. To use custom fonts, you must use an HTML block.
```html HTML block theme={null}
Welcome!
Thanks for subscribing.
```
Many email clients (including Gmail and Outlook for Windows) do not load web fonts. Use system fonts for body text and reserve custom fonts for headlines only. Always include fallback fonts, or use images for guaranteed typography.
### Images & video
Upload images directly to the OneSignal dashboard so your team can reuse them. The editor supports cropping, filtering, and other effects. You can also reference externally hosted images and videos by URL — make sure each URL is publicly accessible.
#### Recommended image sizes for email
* Header/Banner images: `3:1` or `4:1` (e.g., `600×200` or `600×150`)
* Hero images: `16:9` or `2:1` (e.g., `600×338` or `600×300`)
* Square images: `1:1` (e.g., `300×300`) — good for product grids
* Thumbnails: `1:1` or `4:3`
* Max width: `600–700px` is standard (most email clients)
* Design at `2x` for retina displays (e.g., `1200px` wide, displayed at `600px`)
* Keep individual images under `100–200 KB`
* Total email size (including images) under `1 MB`
* Smaller images mean faster load times and better deliverability
* **JPG** — best for photos
* **PNG** — best for graphics, logos, and images with transparency
* **GIF** — for simple animations (keep file size small)
* **WebP** — not widely supported in email yet; avoid
* Always include alt text for accessibility and for when images do not load
* Use inline CSS for styling (many clients strip `
```
Replace any content blocks that do not render correctly in dark mode with HTML blocks that use the CSS classes defined above.
```html email-header theme={null}
```
```html email-body theme={null}
Hi {{ first_name | default: 'there' }}!
Ready to craft a stunning email? Here\u2019s how to get started:
Drag & drop simplicity: Add text, images, and buttons to your email.
Build smarter with Saved Rows: Create reusable headers and footers for consistent branding.
Test & preview: Verify your email on every device before sending.
```
Return to preview mode to verify the dark mode styles are applied correctly.
***
## Save your work
Save your email design as a [**template**](./templates) for future use.
***
## FAQ
### How do I add a custom unsubscribe link?
Replace the default `[unsubscribe_url]` with your own URL. You are responsible for updating the email Subscription status in OneSignal when a recipient unsubscribes. See [Create a custom unsubscribe page](./create-custom-unsubscribe-page) for setup details.
### Why does my email look different in Outlook?
Outlook on Windows uses the Microsoft Word rendering engine, which does not support modern CSS. Common issues include ignored `max-width`, collapsed background images, and missing web fonts. Test in Outlook specifically and use inline CSS with explicit `width` and `height` attributes on images.
### What is the maximum email size?
Keep total email size (HTML + images) under 1 MB. Emails over 102 KB of HTML are clipped by Gmail, which hides content below the fold behind a "View entire message" link. Optimize images and remove unused code to stay under this threshold.
### Can I reuse parts of an email across multiple campaigns?
Yes. Save any row as a **Saved Row** by clicking the save icon on the row. Saved rows appear under **Rows > My Saved Rows** and can be dragged into any email. For full email reuse, save the entire design as a [template](./templates).
### How do I preview my email before sending?
Use the **Preview** button in the top toolbar to see how your email renders on desktop and mobile. To send a live test, click **Send Test Email** and enter a recipient address. Test emails are delivered to the inbox so you can verify rendering, links, and personalization in actual email clients.
### Do emojis work in emails?
Yes, but they render differently across email clients. Outlook on Windows is the most inconsistent — emojis may appear as black-and-white outlines or boxes. If emojis are part of your subject line or CTA text, test across clients before sending.
### How do I import an existing HTML email template?
You can bring existing HTML templates into OneSignal three ways:
1. Forward the email to OneSignal using [Email Template forwarding](./email-template-forwarding)
2. Use the [Create Template API](/reference/create-template) to upload HTML programmatically
3. Copy-paste your HTML directly into the [HTML Editor](./design-emails-with-html)
## Related pages
End-to-end guide to sending email with OneSignal.
Full HTML control, dark mode overrides, and advanced styling.
Save and reuse email designs across campaigns.
Add default or custom unsubscribe links for compliance.
Personalize emails with tags, Liquid syntax, and dynamic content.
Test subject lines, content, and send times to optimize engagement.
# Design emails with HTML
Source: https://documentation.onesignal.com/docs/en/design-emails-with-html
Send fully custom branded emails using OneSignal's HTML Editor. Covers supported HTML rules, inline CSS, dark mode, unsubscribe handling, and client limitations.
## Overview
The HTML Editor lets you send fully custom, branded emails using your own HTML.
You should use the HTML Editor when you:
* Need complete control over layout, spacing, and styling
* Already have production-ready HTML email templates
* Are comfortable working within email client limitations
HTML emails are not the same as web pages. Many HTML and CSS features are unsupported or inconsistently rendered across email clients.
### Prerequisites
Before using the HTML Editor, make sure you:
* Have experience building responsive HTML emails
* Host all images on publicly accessible URLs (your site, CDN, S3, etc.)
#### Expected outcome
After setup, your email:
* Renders consistently across major clients (Gmail, Outlook, Apple Mail)
* Tracks link clicks correctly
* Includes a working unsubscribe mechanism
* Passes spam and deliverability checks
### Import your own templates
If you already have HTML email templates, you can add them to OneSignal in any of the following ways:
1. Use [Email Template forwarding](./email-template-forwarding)
2. Create templates programmatically using the [Create Template API](/reference/create-template)
3. Copy-paste your HTML into the HTML Editor
Start with a proven template rather than writing HTML from scratch.
### Design in Figma and export to OneSignal
If your email designs live in Figma, there are two ways to get them into OneSignal as HTML:
* **[Email Love](https://emaillove.com/figma-plugin)**: A Figma plugin that converts your design into production-ready HTML email and uploads it directly to your OneSignal templates. Enter your OneSignal App ID and API key in the plugin's export panel and click upload.
* **[Figma MCP server](https://help.figma.com/hc/en-us/articles/32132100833559-Guide-to-the-Figma-MCP-server)**: Connect Figma to an AI coding tool via the MCP server. Point it at your email design frame and ask it to generate HTML email code, then paste the output into the HTML Editor.
**Common mistakes that cause rendering or deliverability issues:**
* **Missing unsubscribe link**: Required for marketing emails. Without one, inbox providers are more likely to flag your email as spam.
* **Using `