> ## Documentation Index
> Fetch the complete documentation index at: https://docs.masivo.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Integrate in-app messages

> Step-by-step guide to integrating in-app messages in your mobile app

In-app messages appear inside your app UI. This guide covers fetching eligible messages, rendering them, and reporting impressions to Masivo.

## Prerequisites

* An active Masivo account with at least one **ACTIVE** in-app template
* A **SERVER** or **CLIENT** API key
* Customers registered with `external_id` values matching your app user ids
* For **real-time journey delivery**: a registered FCM device token and push consent on the customer profile (same setup as [push notifications](/guides/create-push)). Without them, messages still materialize in Masivo but the app only sees them on the next `GET /inapp-messages` fetch (for example on app open).

## Check capabilities

Before wiring the SDK, confirm in-app is enabled and read journey event types that support real-time refresh:

```bash theme={null}
GET /api/storefront/v1/inapp/capabilities
Authorization: Bearer <API_KEY>
x-account-id: <ACCOUNT_ID>
```

Example response:

```json theme={null}
{
  "data": {
    "inappEnabled": true,
    "eventTypes": ["PURCHASE", "APP_OPEN"]
  }
}
```

When `inappEnabled` is `false`, skip in-app fetch calls until templates are published.

## Fetch eligible messages

Call this endpoint after app launch and whenever a trigger event from `eventTypes` fires:

```bash theme={null}
GET /api/storefront/v1/customers/{customer_id}/inapp-messages?limit=5
Authorization: Bearer <API_KEY>
x-account-id: <ACCOUNT_ID>
```

Optional query parameters:

| Parameter  | Description                                                            |
| ---------- | ---------------------------------------------------------------------- |
| `brand_id` | Filter by brand                                                        |
| `trigger`  | Event type active on screen (returns untriggered or matching messages) |
| `types`    | Comma-separated types, for example `modal,banner`                      |
| `limit`    | Max messages (1–50, default 10)                                        |

Example response:

```json theme={null}
{
  "data": {
    "messages": [
      {
        "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
        "template_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
        "type": "modal",
        "priority": 10,
        "expires_at": "2026-08-01T00:00:00Z",
        "content": {
          "title": "Welcome back",
          "body": "You have 500 points ready to redeem.",
          "image_url": "https://cdn.example.com/hero.png",
          "buttons": [
            {
              "id": "primary",
              "text": "Redeem now",
              "action": "deeplink",
              "value": "myapp://rewards"
            },
            {
              "id": "dismiss",
              "text": "Not now",
              "action": "dismiss"
            }
          ]
        },
        "display_rules": {
          "max_impressions": 3,
          "priority": 10
        },
        "trigger": {
          "event": "APP_OPEN"
        }
      }
    ]
  }
}
```

## Render messages

<Steps>
  <Step title="Sort by priority">
    Messages are pre-sorted by Masivo, but you can re-sort using
    `display_rules.priority` when showing multiple surfaces.
  </Step>

  <Step title="Map type to UI">
    Render `modal`, `banner`, `slideup`, or `fullscreen` using your design
    system. Use `content.buttons` for actions.
  </Step>

  <Step title="Respect display rules">
    Honor `max_impressions` client-side as a fallback, but always log events so
    Masivo enforces caps server-side.
  </Step>
</Steps>

## Log impression events

After displaying or interacting with a message, report the action:

```bash theme={null}
POST /api/storefront/v1/customers/{customer_id}/inapp-messages/{message_id}/events
Authorization: Bearer <API_KEY>
x-account-id: <ACCOUNT_ID>
Content-Type: application/json

{
  "action": "shown"
}
```

Supported actions: `shown`, `clicked`, `dismissed`.

For button clicks, include metadata:

```json theme={null}
{
  "action": "clicked",
  "metadata": {
    "button_id": "primary"
  }
}
```

Masivo returns `{ "data": { "success": true } }` and emits `INAPP_SHOWN`, `INAPP_CLICKED`, or `INAPP_DISMISSED` tracking events.

## Common patterns

### Refresh on app open

```typescript theme={null}
const refreshInApp = async (customerId: string) => {
  const res = await fetch(
    `https://app.masivo.ai/api/storefront/v1/customers/${customerId}/inapp-messages`,
    {
      headers: { Authorization: `Bearer ${apiKey}`, "x-account-id": accountId }
    }
  );
  const { data } = await res.json();
  return data.messages;
};
```

### Refresh after a journey trigger event

```typescript theme={null}
const onEvent = async (customerId: string, eventType: string) => {
  const caps = await fetchCapabilities();
  if (!caps.eventTypes.includes(eventType)) return;
  const query = new URLSearchParams({ trigger: eventType });
  const res = await fetch(
    `https://app.masivo.ai/api/storefront/v1/customers/${customerId}/inapp-messages?${query}`,
    {
      headers: { Authorization: `Bearer ${apiKey}`, "x-account-id": accountId }
    }
  );
  const { data } = await res.json();
  showNextMessage(data.messages[0]);
};
```

### Log shown before render

Always send `shown` after the message is visible. Masivo assigns a `tracking_id` on `shown` and reuses it for subsequent `clicked` or `dismissed` events in the same impression.

## Frequently asked questions

<AccordionGroup>
  <Accordion title="Do in-app messages require push notification permissions?">
    **Displaying** messages does not — your app fetches eligible messages over HTTPS and renders them in your UI.

    **Real-time journey delivery** does. When a journey materializes an in-app message, Masivo sends a silent push so the app refreshes its queue. That path requires:

    * A registered FCM **device token** on the customer (`PATCH /customers/{id}/devices`)
    * **Push consent** on the customer profile (`consent.purposes.push_notifications` not disabled)
    * Firebase configured in [Push settings](/guides/settings/push)

    If either is missing, Masivo skips the silent push. The message remains in the database and appears on the next fetch (for example when the user opens the app).
  </Accordion>

  <Accordion title="What happens when max_impressions is reached?">
    The message is excluded from `GET /inapp-messages` results. A final `shown` event moves the row out of `pending` when the cap is hit.
  </Accordion>

  <Accordion title="Can the same template show multiple times?">
    Yes, when `display_rules.max_impressions` is greater than 1 and `min_interval_seconds` has elapsed since the last `shown` event.
  </Accordion>

  <Accordion title="How do journey-triggered messages arrive in real time?">
    When a qualifying event is processed, Masivo materializes the in-app message and sends a **silent push** to devices with a valid FCM token and push consent. Your app handles that payload, then calls `GET /inapp-messages` (optionally with the `trigger` query param from the push data). Check `eventTypes` from `/inapp/capabilities` for supported journey events.
  </Accordion>
</AccordionGroup>

## Related documentation

* [In-app messages](/guides/create-in-app-templates)
* [In-app messages concept](/concepts/in-app-messages)
* [Integrate in-app messages](/api-reference/guides/integrate-in-app-messages)
