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

# News Feed cards

> Create, publish, display, and track persistent in-app cards.

This guide shows how to configure a News Feed template in the dashboard and integrate the public feed and interaction endpoints in your app.

## Prerequisites

Authenticate with the Storefront API using a Bearer access token. See [Integrate with Masivo](/api-reference/guides/integrate-with-masivo).

<Note>
  These endpoints work with **CLIENT** and **SERVER** API keys. Use a SERVER
  key when fetching the feed from your backend; CLIENT keys are suitable for
  embedded apps that call Masivo directly.
</Note>

* A customer registered with the same id your app sends to Masivo
* A native or web feed surface that can render the returned card content
* For journey publication, an active journey with a News Feed action

## Create and publish a card

<Steps>
  <Step title="Create a template">
    In the dashboard, go to **Marketing Automation → News Feed** and select **New template**.
  </Step>

  <Step title="Choose the content shape">
    Select `classic`, `captioned_image`, or `image_only`. Complete the title, message, image, and optional link fields required by that choice.

    <Frame caption="Classic card: title and message with an optional square icon.">
      <img src="https://mintcdn.com/trd/50FryFX_FsOtpLx3/images/news_feeds/edition_news_2.png?fit=max&auto=format&n=50FryFX_FsOtpLx3&q=85&s=b82d1f71fec7cef48810d1c7d0b1ad60" alt="News feed card editor showing the classic card type with a live iOS and Android preview" width="2486" height="1608" data-path="images/news_feeds/edition_news_2.png" />
    </Frame>

    <Frame caption="Captioned image card: prominent image with a title and message below.">
      <img src="https://mintcdn.com/trd/50FryFX_FsOtpLx3/images/news_feeds/edition_news_1.png?fit=max&auto=format&n=50FryFX_FsOtpLx3&q=85&s=1fdcf4aa8116bb9ea15e85cb113c3bbf" alt="News feed card editor showing the captioned image card type with a live iOS and Android preview" width="2474" height="1614" data-path="images/news_feeds/edition_news_1.png" />
    </Frame>
  </Step>

  <Step title="Configure delivery">
    Set the priority, pinned state, and expiration in days. Pinned cards are returned first; priority orders cards within the same pinned group.
  </Step>

  <Step title="Publish to customers">
    Use the template in a journey News Feed action or schedule it for an audience. Audience eligibility is evaluated when each customer fetches the feed.
  </Step>
</Steps>

## Display the feed

Fetch eligible cards from your backend or trusted SDK client:

```bash theme={null}
curl --request GET \
  --url 'https://app.masivo.ai/api/storefront/v1/customers/customer_123/news-feed?brand_id=main&limit=20' \
  --header 'Authorization: Bearer <SERVER_API_KEY>' \
  --header 'x-account-id: <ACCOUNT_ID>'
```

A complete card response has this shape:

```json theme={null}
{
  "data": {
    "messages": [
      {
        "id": "90e3b315-c8b6-4055-835d-93a8300ea52e",
        "type": "news_feed",
        "campaign_id": "85cd7c9a-97bd-4235-ab56-1a9b9d6fd6fd",
        "priority": 10,
        "expires_at": "2026-08-19T15:30:00.000Z",
        "content": {
          "title": "Your weekly offer",
          "body": "Save 20% on your next order.",
          "image_url": "https://cdn.example.com/offers/weekly.png",
          "link": "myapp://offers/weekly",
          "link_text": "View offer",
          "pinned": true
        },
        "display_rules": {
          "priority": 10
        }
      }
    ]
  }
}
```

Fields without values are omitted. `expires_at` and `campaign_id` can be absent when the assignment or template does not define them.

## Track customer interactions

Log each interaction with the template id returned as the card `id`:

```bash theme={null}
curl --request POST \
  --url 'https://app.masivo.ai/api/storefront/v1/customers/customer_123/news-feed/90e3b315-c8b6-4055-835d-93a8300ea52e/events' \
  --header 'Authorization: Bearer <SERVER_API_KEY>' \
  --header 'Content-Type: application/json' \
  --header 'x-account-id: <ACCOUNT_ID>' \
  --data '{"action":"shown"}'
```

Valid actions are `shown`, `clicked`, and `dismissed`. A dismissal removes the card from subsequent responses unless the same template receives a newer assignment.

## React Native SDK

<Note>
  Assignments reference the template, not a content copy. Editing an active
  template updates what every assigned customer sees immediately, with no
  republish step — the next fetch returns the new content. Refetch the feed
  whenever the customer is likely to see stale data instead of relying on a
  single fetch per session.
</Note>

```ts theme={null}
const cards = await masivo.inApp.fetchNewsFeedCards({
  customerId: "customer_123",
  brandId: "main",
  limit: 20
});

for (const card of cards) {
  await masivo.inApp.logNewsFeedCardShown(card, {
    customer_id: "customer_123",
    brand_id: "main"
  });
}
```

When a customer taps or dismisses a card:

```ts theme={null}
const event = { customer_id: "customer_123", brand_id: "main" };

await masivo.inApp.logNewsFeedCardClicked(card, event);
await masivo.inApp.logNewsFeedCardDismissed(card, event);
```

The SDK keeps News Feed fetches separate from the overlay in-app message queue and returns pinned cards before unpinned cards.

## Common patterns

### Refetch when the app opens or returns to the foreground

Since card content is resolved live, fetch on launch and again whenever the app resumes from the background so customers always see the latest template content:

```ts theme={null}
import { AppState } from "react-native";

useEffect(() => {
  const fetchFeed = () => {
    masivo.inApp.fetchNewsFeedCards({
      customerId: "customer_123",
      brandId: "main",
      limit: 20
    });
  };

  fetchFeed();

  const subscription = AppState.addEventListener("change", nextState => {
    if (nextState === "active") fetchFeed();
  });

  return () => subscription.remove();
}, []);
```

### Remove expired cards locally

```ts theme={null}
const visibleCards = cards.filter(card => {
  if (!card.expires_at) return true;
  return new Date(card.expires_at).getTime() > Date.now();
});
```

### Open an optional destination

```ts theme={null}
const destination = card.content.link;
if (destination) {
  await Linking.openURL(destination);
}
```

### Render link text with a fallback

```ts theme={null}
const linkLabel = card.content.link_text ?? "Learn more";
```

## Frequently asked questions

<AccordionGroup>
  <Accordion title="Do template edits update cards already assigned?">
    Yes. Assignments reference the template, and the Storefront endpoint resolves active template content on every request.
  </Accordion>

  <Accordion title="What happens when a customer leaves an audience?">
    Audience membership is checked during feed resolution. The audience assignment stops making the card eligible immediately, unless another direct or global assignment still covers it.
  </Accordion>

  <Accordion title="Can the same template be assigned more than once?">
    Yes. Feed resolution collapses active assignments to one card per template. The latest assignment controls when a previously dismissed card can reappear, while any non-expiring assignment keeps it non-expiring.
  </Accordion>

  <Accordion title="When are expired cards deleted?">
    Expired assignments are excluded immediately. The News Feed cleanup cron later deletes them in batches and removes orphaned interaction state after a seven-day grace period.
  </Accordion>

  <Accordion title="Does clicking remove a card?">
    No. `shown` and `clicked` only update interaction timestamps. Only `dismissed` hides the card.
  </Accordion>

  <Accordion title="How are cards ordered?">
    Pinned cards come first, followed by higher priority and then newer assignment time. The React Native SDK preserves this order while grouping pinned cards first.
  </Accordion>
</AccordionGroup>
