> ## 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.

# Social activity

> Send push, email, and SMS notifications for social actions including likes, follows, direct messages, and competitive gaming events. Uses custom_data for personalized, backend-triggered alerts.

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.

<Note>
  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.
</Note>

<Columns cols={3}>
  <Card title="Social activity" icon="heart" href="#social-activity-notifications">
    Notify users when someone likes, comments, mentions, tags, or follows them.
  </Card>

  <Card title="Direct messages" icon="message" href="#direct-user-to-user-messages">
    Alert users to new incoming messages with debouncing and conversation deep links.
  </Card>

  <Card title="Gaming alerts" icon="gamepad" href="./social-activity#gaming%3A-competitive-and-social-alerts">
    Send time-sensitive competitive events like base attacks, challenges, and guild activity.
  </Card>
</Columns>

## Prerequisites

Before you begin, make sure you have:

* The OneSignal SDK installed on your app. See [Mobile SDK setup](./mobile-sdk-setup) or [Web SDK setup](./web-push-setup).
* An `external_id` set for every user so you can target them by your own identifier. See [Users & Aliases](./users).
* A backend that can detect social actions and call the OneSignal API. See [REST API overview](/reference/rest-api-overview).
* [Templates](./templates) created in the dashboard if you plan to use `custom_data` for personalization. A `template_id` is required to use `custom_data`.

<Warning>
  **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](./personalization-api-custom-data) for the size limit details and array-iteration patterns.
</Warning>

## 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

| Action  | Example 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

<Steps>
  <Step title="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 JSON theme={null}
    {
      "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"
    }
    ```
  </Step>

  <Step title="Create a push template">
    In the dashboard, go to **Messages > Templates > New Push Template**. Use Liquid syntax to reference `custom_data` fields:

    **Heading:**

    ```liquid Liquid theme={null}
    {{ message.custom_data.sender_name | default: "Someone" }}
    ```

    **Message:**

    ```liquid Liquid theme={null}
    {{ 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 Liquid theme={null}
    {{ message.custom_data.sender_avatar }}
    ```

    Save the template and note its `template_id`.
  </Step>

  <Step title="Call the Create Message API">
    From your backend, send the notification to the recipient:

    ```json JSON theme={null}
    {
      "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.
  </Step>

  <Step title="Optional: add email and SMS fallbacks">
    To reach users who have push disabled or whose notification went undelivered, see [Email and SMS fallbacks](#email-and-sms-fallbacks) below.
  </Step>
</Steps>

<Tip>
  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](./using-liquid-syntax) for more filters.
</Tip>

<Tip>
  **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` header** — `image/jpeg`, `image/png`, etc.

  Hosting from a CDN with cached headers is the safest setup.
</Tip>

### 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](./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.

<Note>
  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.
</Note>

### Setup

<Steps>
  <Step title="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.
  </Step>

  <Step title="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.
  </Step>

  <Step title="Send the push notification">
    Send a push to User B with a deep link to the conversation:

    ```json JSON theme={null}
    {
      "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](./deep-linking) for platform-specific setup.
  </Step>

  <Step title="Optional: add email and SMS fallbacks">
    To reach users who have push disabled or whose notification went undelivered, see [Email and SMS fallbacks](#email-and-sms-fallbacks) below.
  </Step>
</Steps>

<Tip>
  **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](./push#notification-grouping).
</Tip>

<Tip>
  **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](./push#badges).
</Tip>

<Warning>
  **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.
</Warning>

## 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

| Event          | Example 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

<Steps>
  <Step title="Detect the game event on your backend">
    When a competitive event occurs, your game backend identifies the affected player and captures relevant context:

    ```json JSON theme={null}
    {
      "event": "base_attack",
      "attacker_id": "user_orca",
      "attacker_name": "Orca",
      "recipient_id": "user_dragon",
      "resources_at_risk": {
        "gold": 12400,
        "elixir": 8200
      }
    }
    ```
  </Step>

  <Step title="Create a push template">
    In the dashboard, create a Push Template with Liquid references:

    **Heading:**

    ```liquid Liquid theme={null}
    ⚔️ Your village is under attack!
    ```

    **Message:**

    ```liquid Liquid theme={null}
    {{ 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`.
  </Step>

  <Step title="Send the notification">
    Call the Create Message API from your game backend:

    ```json JSON theme={null}
    {
      "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.
  </Step>

  <Step title="Optional: add email and SMS fallbacks">
    To reach players who have push disabled or whose notification went undelivered, see [Email and SMS fallbacks](#email-and-sms-fallbacks) below.
  </Step>
</Steps>

<Tip>
  **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](./push#delivery-schedule-and-optimization) 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.
</Tip>

<Tip>
  **Consider Live Activities for in-progress events.** For ongoing matches, raids, or live events on iOS 16.1+, a [Live Activity](./live-activities) 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.
</Tip>

### More gaming alert examples

<Tabs>
  <Tab title="Leaderboard overtake">
    **Template message:**

    ```liquid Liquid theme={null}
    {{ 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 JSON theme={null}
    {
      "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"
    }
    ```
  </Tab>

  <Tab title="Guild event">
    Send to all guild members simultaneously by passing multiple `external_id` values. Each receives the same message.

    **Template message:**

    ```liquid Liquid theme={null}
    Guild war alert! {{ message.custom_data.guild_name | default: "Your guild" }} clashes in {{ message.custom_data.minutes_until | default: "30" }} minutes. Get in position.
    ```

    **API request:**

    ```json JSON theme={null}
    {
      "app_id": "YOUR_APP_ID",
      "template_id": "YOUR_TEMPLATE_ID",
      "include_aliases": {
        "external_id": ["user_dragon", "user_orca", "user_shadow"]
      },
      "custom_data": {
        "guild_name": "BladeClan",
        "minutes_until": "30"
      },
      "url": "yourgame://guild/war"
    }
    ```
  </Tab>

  <Tab title="Challenge invite">
    **Template message:**

    ```liquid Liquid theme={null}
    {{ message.custom_data.challenger_name | default: "A player" }} challenged you to a {{ message.custom_data.game_mode | default: "duel" }}. You have {{ message.custom_data.hours_to_respond | default: "24" }} hours to accept.
    ```

    **API request:**

    ```json JSON theme={null}
    {
      "app_id": "YOUR_APP_ID",
      "template_id": "YOUR_TEMPLATE_ID",
      "include_aliases": {
        "external_id": ["user_dragon"]
      },
      "custom_data": {
        "challenger_name": "DragonSlayer99",
        "game_mode": "ranked duel",
        "hours_to_respond": "24"
      },
      "url": "yourgame://challenges/incoming",
      "data": {
        "challenge_id": "chal_9876",
        "challenger_id": "user_dragonslayer99"
      }
    }
    ```
  </Tab>
</Tabs>

## 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](/reference/view-message) to check for a [confirmed receipt](./confirmed-delivery) 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.

<Tabs>
  <Tab title="Email">
    **Social activity**

    Best for high-value actions like mentions and direct replies.

    ```json JSON theme={null}
    {
      "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 Liquid theme={null}
    {{ message.custom_data.sender_name | default: "Someone" }} {{ message.custom_data.action | default: "interacted with your post" }}
    ```

    **Direct messages**

    Best as a daily digest of unread conversations rather than per-message alerts.

    ```json JSON theme={null}
    {
      "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 Liquid theme={null}
    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 Liquid theme={null}
    <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](./personalization-api-custom-data) for the full array-iteration reference, including nested objects and conditional rendering.

    **Gaming**

    Best for non-urgent recaps like weekly leaderboard summaries, guild war results, or milestone unlocks.

    ```json JSON theme={null}
    {
      "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 Liquid theme={null}
    Your weekly rankings: #{{ message.custom_data.rank }} ({{ message.custom_data.rank_change }} this week)
    ```
  </Tab>

  <Tab title="SMS">
    **Social activity**

    Best for high-importance actions like mentions and direct replies, not routine likes or follows.

    ```json JSON theme={null}
    {
      "app_id": "YOUR_APP_ID",
      "template_id": "YOUR_SMS_TEMPLATE_ID",
      "include_aliases": {
        "external_id": ["user_a"]
      },
      "custom_data": {
        "sender_name": "Anna",
        "post_title": "My Hawaii trip",
        "post_url": "https://yourapp.com/posts/xyz789"
      }
    }
    ```

    **SMS template example:**

    ```liquid Liquid theme={null}
    {{ message.custom_data.sender_name | default: "Someone" }} mentioned you in "{{ message.custom_data.post_title | default: "a post" }}". Tap to reply: {{ message.custom_data.post_url }}
    ```

    **Direct messages**

    Best for individual missed messages rather than bulk message sequences.

    ```json JSON theme={null}
    {
      "app_id": "YOUR_APP_ID",
      "template_id": "YOUR_SMS_TEMPLATE_ID",
      "include_aliases": {
        "external_id": ["user_b_id"]
      },
      "custom_data": {
        "sender_name": "Anna",
        "message_preview": "Hey, you around?",
        "conversation_url": "https://yourapp.com/chat/chat_1234"
      }
    }
    ```

    **SMS template example:**

    ```liquid Liquid theme={null}
    New message from {{ message.custom_data.sender_name | default: "a user" }}: "{{ message.custom_data.message_preview }}". Reply in the app: {{ message.custom_data.conversation_url }}
    ```

    **Gaming**

    Best for time-critical events like base attacks where the player needs to act immediately.

    ```json JSON theme={null}
    {
      "app_id": "YOUR_APP_ID",
      "template_id": "YOUR_SMS_TEMPLATE_ID",
      "include_aliases": {
        "external_id": ["user_dragon"]
      },
      "custom_data": {
        "attacker_name": "Orca",
        "gold": "12400",
        "game_url": "https://yourgame.com/base/defend"
      }
    }
    ```

    **SMS template example:**

    ```liquid Liquid theme={null}
    ⚔️ ATTACK! {{ message.custom_data.attacker_name | default: "An enemy" }} is raiding your base. {{ message.custom_data.gold | default: "0" }} gold at risk. Defend now: {{ message.custom_data.game_url }}
    ```
  </Tab>
</Tabs>

<Tip>
  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.
</Tip>

## 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](./message-personalization).

### 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](./personalization-api-custom-data) for the full approach. The exact per-call recipient cap and rate limits are documented on the [Create Message API reference](/reference/create-message) — 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](./multi-language-messaging) for the full reference, including fallback language behavior.

## Related pages

<Columns cols={3}>
  <Card title="Personalize messages with API custom_data" icon="sparkles" href="./personalization-api-custom-data">
    Inject dynamic, message-specific data into templates using custom\_data and Liquid syntax.
  </Card>

  <Card title="Message personalization" icon="user" href="./message-personalization">
    Overview of all personalization options in OneSignal, including Tags, user attributes, and segmentation.
  </Card>

  <Card title="Deep linking" icon="link" href="./deep-linking">
    Route users to a specific screen in your app when they tap a notification.
  </Card>

  <Card title="Create an Activity Feed" icon="list" href="./create-an-activity-feed">
    Display a history of social alerts inside your app using OneSignal's notification inbox.
  </Card>

  <Card title="Templates" icon="file" href="./templates">
    Create and manage reusable message templates for push, email, and SMS.
  </Card>

  <Card title="Create Message API" icon="code" href="/reference/create-message">
    Complete API reference for sending messages with custom\_data, targeting, and all available fields.
  </Card>
</Columns>
