Skip to main content

Documentation Index

Fetch the complete documentation index at: https://documentation.onesignal.com/llms.txt

Use this file to discover all available pages before exploring further.

Notify users when something happens that involves them: a like, a reply, a follow, an incoming message, or a competitive event in a game. These notifications drive re-engagement even when users aren’t currently active in your app.
OneSignal is not designed for real-time communication. Push notifications are best used as a fallback when users are not actively in the app. For real-time in-app messaging, use your app’s existing messaging layer and trigger OneSignal notifications only when the recipient is offline or inactive.

Prerequisites

Before you begin, make sure you have:
  • The OneSignal SDK installed on your app. See Mobile SDK setup or Web SDK setup.
  • An external_id set for every user so you can target them by your own identifier. See Users & Aliases.
  • A backend that can detect social actions and call the OneSignal API. See REST API overview.
  • Templates created in the dashboard if you plan to use custom_data for personalization. A template_id is required to use custom_data.
Keep custom_data payloads under 2KB. The custom_data field has a hard size limit. Sending large payloads — full conversation lists, leaderboard arrays, base64 images, or HTML — risks truncation or rejection. For rich content, pass an identifier (e.g., digest_id or summary_url) and have the recipient’s device fetch the full payload from your backend on tap. See Personalize messages with API custom_data for the size limit details and array-iteration patterns.

Social activity notifications

Send a push notification when a user is involved in a social action. Use custom_data to inject the sender’s name, avatar, and relevant context into the message at send time. No data is stored in OneSignal.

Common social actions

ActionExample notification
Like”Anna liked your photo.”
Comment”Leo replied: ‘Looks awesome!’”
Mention”Sara mentioned you in a comment.”
Tag”James tagged you in a post.”
Follow”Maya started following you.”
Invite”Ben invited you to the event.”
Share”Alex shared ‘Hawaii Album’ with you.”

Setup

1

Detect the action on your backend

When a social action occurs, your backend identifies the sender and recipient, plus any relevant context like post ID or content:
JSON
{
  "action": "like",
  "sender_id": "user_b",
  "sender_name": "Anna",
  "sender_avatar": "https://cdn.yourapp.com/avatars/anna.jpg",
  "recipient_id": "user_a",
  "post_id": "xyz789",
  "post_title": "My Hawaii trip"
}
2

Create a push template

In the dashboard, go to Messages > Templates > New Push Template. Use Liquid syntax to reference custom_data fields:Heading:
Liquid
{{ message.custom_data.sender_name | default: "Someone" }}
Message:
Liquid
{{ message.custom_data.sender_name | default: "Someone" }} liked your post "{{ message.custom_data.post_title | default: "your post" }}".
Image (optional, displays the sender’s avatar):
Liquid
{{ message.custom_data.sender_avatar }}
Save the template and note its template_id.
3

Call the Create Message API

From your backend, send the notification to the recipient:
JSON
{
  "app_id": "YOUR_APP_ID",
  "template_id": "YOUR_TEMPLATE_ID",
  "include_aliases": {
    "external_id": ["user_a"]
  },
  "custom_data": {
    "sender_name": "Anna",
    "sender_avatar": "https://cdn.yourapp.com/avatars/anna.jpg",
    "post_title": "My Hawaii trip",
    "post_id": "xyz789"
  },
  "url": "yourapp://posts/xyz789"
}
OneSignal renders the template at send time using the custom_data values. The sender’s name and avatar appear in the notification without being stored in OneSignal.
4

Optional: add email and SMS fallbacks

To reach users who have push disabled or whose notification went undelivered, see Email and SMS fallbacks below.
Use | default: filters in every Liquid placeholder so the message still reads naturally if a field is missing. For example: {{ message.custom_data.sender_name | default: "Someone" }}. See Using Liquid syntax for more filters.
Avatar and image URL requirements. The sender_avatar URL (and any other notification image) must be:
  • HTTPS — iOS rejects HTTP URLs.
  • Publicly accessible — APNs and FCM cannot send authenticated requests.
  • Under ~1 MB — iOS limit is 10 MB but practical delivery windows favor smaller assets.
  • Served with the correct Content-Type headerimage/jpeg, image/png, etc.
Hosting from a CDN with cached headers is the safest setup.

Throttle high-volume actions

A viral post can generate thousands of like events per second. Don’t send a push for each one — that floods the recipient and gets your app muted or uninstalled. The pattern:
  1. Accumulate counts on your backend (e.g., a Redis counter keyed by recipient + post).
  2. After a quiet window (10 minutes is a reasonable default), send a single digest push: “12 people liked your post.”
  3. If more likes arrive after the digest, start a fresh window — don’t immediately push again.
The same logic applies to comments, follows, and reactions. See Throttling for OneSignal-side rate limits if your backend can’t debounce.

Direct (user-to-user) messages

Notify a user when they receive a new direct message, and deep link them directly into the conversation.
Only send a push when the recipient is not actively in the chat. Notifying someone who is already reading the conversation creates a poor experience. Use your app’s own logic to check whether the recipient is currently active before triggering a notification. OneSignal does not track whether a user is currently using your app.

Setup

1

Detect when a message is sent and check activity

When User A sends a message to User B, check whether User B is currently active in that conversation. If User B is offline or not in the conversation, proceed to send a push.
2

Avoid sending one push per message

If User A sends several messages in a row, wait a short period after the last message before triggering a notification. Here is how to do this in your backend:
  1. When the first message arrives, start a timer (for example, 60 seconds).
  2. If another message arrives before the timer runs out, reset it.
  3. When the timer runs out with no new messages, send a single push summarizing the unread count.
OneSignal does not consolidate multiple API calls automatically, so if you call the API five times, five notifications are sent.
3

Send the push notification

Send a push to User B with a deep link to the conversation:
JSON
{
  "app_id": "YOUR_APP_ID",
  "headings": { "en": "New message" },
  "contents": { "en": "Anna: 'Hey, you around?'" },
  "include_aliases": {
    "external_id": ["user_b_id"]
  },
  "url": "yourapp://chat/chat_1234",
  "data": {
    "click_action": "open_chat",
    "conversation_id": "chat_1234",
    "sender_id": "user_a_id"
  }
}
Your app reads data.conversation_id on notification open and navigates to the correct screen. See Deep linking for platform-specific setup.
4

Optional: add email and SMS fallbacks

To reach users who have push disabled or whose notification went undelivered, see Email and SMS fallbacks below.
Group notifications by conversation natively. Backend debouncing reduces how many notifications fire, but iOS and Android can also visually collapse multiple notifications into a single thread. Set a thread or collapse identifier (e.g., the conversation_id) so the OS groups messages from the same chat. See Notification grouping.
Update the badge count on each new message. Most chat apps want the iOS/Android badge to reflect total unread messages across all conversations. Pass the unread count via the API on each push so the badge stays accurate even when a user clears one notification but has others outstanding. See Badges.
Lock Screen privacy. iOS shows notification content on the Lock Screen by default — including the message preview (“Anna: ‘Hey, you around?’”). For messaging apps with sensitive content (health, finance, dating, professional), consider sending a generic preview (“New message from Anna”) and let users opt into full previews via your in-app settings.

Gaming: competitive and social alerts

Competitive games benefit from time-sensitive alerts that create urgency. Use custom_data to make these notifications feel specific and personal. A notification that names the attacker or shows exact resource counts is far more compelling than a generic alert.

Common competitive events

EventExample notification
Base attack”Your village is under attack! Orca is raiding your base.”
Challenge”DragonSlayer99 challenged you to a ranked duel. You have 24h to respond.”
Leaderboard”You’ve been knocked out of the top 10. Orca just passed you at #8.”
Guild event”Guild war starts in 30 minutes. BladeClan is counting on you!”
Clan invite”DragonSlayer99 invited you to join BladeClan.”
Resource ready”Your troops are trained and ready to deploy.”

Setup

1

Detect the game event on your backend

When a competitive event occurs, your game backend identifies the affected player and captures relevant context:
JSON
{
  "event": "base_attack",
  "attacker_id": "user_orca",
  "attacker_name": "Orca",
  "recipient_id": "user_dragon",
  "resources_at_risk": {
    "gold": 12400,
    "elixir": 8200
  }
}
2

Create a push template

In the dashboard, create a Push Template with Liquid references:Heading:
Liquid
⚔️ Your village is under attack!
Message:
Liquid
{{ message.custom_data.attacker_name | default: "An enemy" }} is raiding your base. You're at risk of losing {{ message.custom_data.gold | default: "0" }} gold and {{ message.custom_data.elixir | default: "0" }} elixir.
Save the template and note its template_id.
3

Send the notification

Call the Create Message API from your game backend:
JSON
{
  "app_id": "YOUR_APP_ID",
  "template_id": "YOUR_TEMPLATE_ID",
  "include_aliases": {
    "external_id": ["user_dragon"]
  },
  "custom_data": {
    "attacker_name": "Orca",
    "gold": "12400",
    "elixir": "8200"
  },
  "url": "yourgame://base/defend",
  "data": {
    "event": "base_attack",
    "attacker_id": "user_orca"
  }
}
The url deep links the player directly to the defend screen. The data object passes context to your app’s notification handler so it can load the correct battle state.
4

Optional: add email and SMS fallbacks

To reach players who have push disabled or whose notification went undelivered, see Email and SMS fallbacks below.
Respect quiet hours for non-urgent alerts. Base-attack pushes at 3 AM local time are a known opt-out driver. Split your gaming alerts into two tiers:
  • Time-critical (guild war starting in 30 minutes, base under attack right now) — send immediately regardless of local time.
  • Non-time-critical (troops are ready, daily reward available, weekly recap) — use Intelligent Delivery or Custom time per timezone so they land in waking hours in the player’s local timezone.
Most gaming-alert opt-outs come from the second category sending at the wrong time, not from the first category being too frequent.
Consider Live Activities for in-progress events. For ongoing matches, raids, or live events on iOS 16.1+, a Live Activity on the Lock Screen and Dynamic Island is often a better experience than repeated push notifications updating the same context. Use Live Activities for the live state (“23 minutes remaining, you’re #4”) and reserve push for milestone or completion moments.

More gaming alert examples

Template message:
Liquid
{{ message.custom_data.overtaker_name | default: "Another player" }} just passed you. You dropped from #{{ message.custom_data.previous_rank }} to #{{ message.custom_data.current_rank }} on the leaderboard.
API request:
JSON
{
  "app_id": "YOUR_APP_ID",
  "template_id": "YOUR_TEMPLATE_ID",
  "include_aliases": {
    "external_id": ["user_dragon"]
  },
  "custom_data": {
    "overtaker_name": "Orca",
    "previous_rank": "8",
    "current_rank": "9"
  },
  "url": "yourgame://leaderboard"
}

Email and SMS fallbacks

Add an email or SMS fallback to any notification type to reach users who have push disabled or whose notification was not delivered. Use the View Message API to check for a confirmed receipt or click. If none is recorded within your delay window, send a follow-up using the same custom_data approach with an Email or SMS template.
Social activityBest for high-value actions like mentions and direct replies.
JSON
{
  "app_id": "YOUR_APP_ID",
  "template_id": "YOUR_EMAIL_TEMPLATE_ID",
  "include_aliases": {
    "external_id": ["user_a"]
  },
  "custom_data": {
    "sender_name": "Anna",
    "sender_avatar": "https://cdn.yourapp.com/avatars/anna.jpg",
    "action": "liked your post",
    "post_title": "My Hawaii trip",
    "post_url": "https://yourapp.com/posts/xyz789"
  }
}
Email template example (subject):
Liquid
{{ message.custom_data.sender_name | default: "Someone" }} {{ message.custom_data.action | default: "interacted with your post" }}
Direct messagesBest as a daily digest of unread conversations rather than per-message alerts.
JSON
{
  "app_id": "YOUR_APP_ID",
  "template_id": "YOUR_EMAIL_TEMPLATE_ID",
  "include_aliases": {
    "external_id": ["user_b_id"]
  },
  "custom_data": {
    "unread_count": "3",
    "conversations": [
      { "sender_name": "Anna", "preview": "Hey, you around?", "url": "https://yourapp.com/chat/chat_1234" },
      { "sender_name": "Leo", "preview": "Did you see this?", "url": "https://yourapp.com/chat/chat_5678" }
    ]
  }
}
Email template example (subject):
Liquid
You have {{ message.custom_data.unread_count | default: "new" }} unread message{% if message.custom_data.unread_count != "1" %}s{% endif %}
Email template example (body, iterating over the conversations array):
Liquid
<h2>You have {{ message.custom_data.unread_count }} unread messages</h2>
<ul>
  {% for conversation in message.custom_data.conversations %}
    <li>
      <strong>{{ conversation.sender_name }}:</strong>
      "{{ conversation.preview }}"
      <a href="{{ conversation.url }}">Open</a>
    </li>
  {% endfor %}
</ul>
See Personalize messages with API custom_data for the full array-iteration reference, including nested objects and conditional rendering.GamingBest for non-urgent recaps like weekly leaderboard summaries, guild war results, or milestone unlocks.
JSON
{
  "app_id": "YOUR_APP_ID",
  "template_id": "YOUR_EMAIL_TEMPLATE_ID",
  "include_aliases": {
    "external_id": ["user_dragon"]
  },
  "custom_data": {
    "player_name": "DragonSlayer99",
    "rank": "7",
    "rank_change": "+3",
    "top_players": [
      { "name": "Orca", "score": "48200" },
      { "name": "Shadow", "score": "45100" },
      { "name": "DragonSlayer99", "score": "43800" }
    ],
    "summary_url": "https://yourgame.com/rankings/weekly"
  }
}
Email template example (subject):
Liquid
Your weekly rankings: #{{ message.custom_data.rank }} ({{ message.custom_data.rank_change }} this week)
Give users control over their fallback preferences. An opt-in like “Notify me by SMS if I miss a message” helps prevent unwanted messages for users who intentionally have push disabled.

FAQ

Can OneSignal send notifications in real time, like a chat app?

No. Push notifications are delivered through Apple (APNs) and Google (FCM) infrastructure, which introduces variable delivery times and no delivery guarantees. Use your app’s existing messaging layer for real-time in-app communication and use OneSignal as a fallback when the recipient is not actively in the app.

How do I avoid notifying a user who is already in the app?

OneSignal does not track whether a user is currently active in your app. Your own backend logic must determine whether to trigger the notification. Only call the OneSignal API when you’ve confirmed the recipient is offline or not in the relevant screen.

How do I prevent multiple notifications from rapid message sequences?

Add a short delay in your backend before sending a notification. When the first message arrives, start a timer. If another message comes in before it runs out, reset it. When the timer runs out, send a single push with the unread count. OneSignal does not consolidate multiple API calls automatically, so if you call the API five times, five notifications are sent.

Is custom_data saved to the user’s profile after the message sends?

No. custom_data is ephemeral and exists only during the API request, used to render the template at send time. It is not stored in OneSignal and cannot be reused in future messages or Journeys. For persistent user data, use Tags.

Can I target multiple recipients in one API call?

Yes. Pass multiple external_id values in the include_aliases array. If each recipient needs different personalized content (for example, different attacker names), use the bulk personalization pattern in custom_data. See Personalize messages with API custom_data for the full approach. The exact per-call recipient cap and rate limits are documented on the Create Message API reference — for very large audiences, segment-based targeting is more efficient than passing thousands of external_id values per call.

Do I need to localize messages for international users?

Yes for any audience that spans languages. The headings and contents fields accept multiple language codes (e.g., { "en": "...", "es": "...", "fr": "..." }) and OneSignal selects the right variant based on each subscription’s language. The same pattern applies to template fields. See Multi-language messaging for the full reference, including fallback language behavior.

Personalize messages with API custom_data

Inject dynamic, message-specific data into templates using custom_data and Liquid syntax.

Message personalization

Overview of all personalization options in OneSignal, including Tags, user attributes, and segmentation.

Deep linking

Route users to a specific screen in your app when they tap a notification.

Create an Activity Feed

Display a history of social alerts inside your app using OneSignal’s notification inbox.

Templates

Create and manage reusable message templates for push, email, and SMS.

Create Message API

Complete API reference for sending messages with custom_data, targeting, and all available fields.