Notification Feeds

Notification feeds let you notify users about relevant interactions, for example

  • someone started to follow them
  • someone liked their post
  • someone left a comment on their post

Creating notification feeds

The built-in notification feed comes with the necessary configurations, but it's also possible to create your own notification feed group:

const myNotificationGrpup = await serverClient.feeds.createFeedGroup({
  id: "myid",
  // Group by activity type and day
  aggregation: { format: '{{ type }}-{{ time.strftime("%Y-%m-%d") }}' },
  // Enable notification tracking
  notification: {
    track_read: true,
    track_seen: true,
  },
});

Notification configuration

The notification config includes several settings that control how notifications are tracked and deduplicated:

  • TrackSeen: When enabled, tracks which notifications have been seen by the user
  • TrackRead: When enabled, tracks which notifications have been read by the user
  • DeduplicationWindow: Controls how duplicate notifications are handled (only available for the built-in notification feed group)
    • Empty string ("") = always deduplicate (default behavior)
    • Duration string (e.g., "24h", "7d") = time-based deduplication window

Note: Comments are not deduplicated. Each comment will create a separate notification activity, regardless of the deduplication window setting.

The built-in notification feed group has deduplication enabled by default (always deduplicate). To change the deduplication window, you need to update the notification feed group.

Aggregation format

The built-in notification feed uses the following aggregation format: "{{ target_id }}_{ type }}_{{ time.strftime('%Y-%m-%d') }}". You can change this syntax by updating the notification feed group.

You can see all supported fields and syntax for aggregation in the Aggregation guide.

It's possible to turn off aggregation, but still enable notification tracking. In that case every new activity will increase unread/unseen count. When you have aggregation turned on, unread/unseen will refer to the number of aggregated groups.

Adding notification activities

The built-in notification groups can automatically create notifications for the most common interactions (see Built-in notification feed section).

If you want to extend that, or create your own notification feed, you can add notification activities using server-side integration. You can add webhook handlers for the relevant events to create notifications without API calls from your client-side application to your server-side application.

It's important to note that target_id is only defined if a notification is created by Stream API. If you want to extend or replace this behavior by adding notification activities from your own application, you most likely need to extend the default aggregation format. You can see all supported fields and syntax for aggregation in the Aggregation guide.

Manual Activity Addition to Notification Feeds

You can directly add activities to notification feeds for complete control over notifications (only available server-side):

// Add a custom notification directly to a user's notification feed
await serverClient.feeds.addActivity({
  feeds: ["notification:user-123"], // Target user's notification feed
  type: "milestone", // Custom activity type
  text: "You've reached 1000 followers!",
  user_id: "user_id",
  extra_data: {
    milestone_type: "followers",
    count: 1000,
  },
});

// Add activity to custom notification feed group
await serverClient.feeds.addActivity({
  feeds: ["alerts:user-123"], // Custom notification feed group
  type: "system_alert",
  text: "Your subscription expires in 3 days",
  user_id: "user_id",
});

Important: When adding activities directly to notification feeds, ensure the activity type is included in the feed group's push_types configuration to trigger push notifications.

Built-in notification feed

Creating notification activities

The built-in notification feed allows you to automatically create notification activities. The following actions are supported and will automatically create notification activities for the target user depending on the action.

ActionTrigger UserTarget User (Recipient)Notification TypeNotification TextDeduplicated?Notes
React to ActivityUser who reacts (e.g., Bob)Activity author (e.g., Alice)reaction{user} reacted to your activity✅ YesMultiple reactions from the same user on the same activity are deduplicated within the deduplication window
React to CommentUser who reacts (e.g., Charlie)Comment author (e.g., Bob)comment_reaction{user} reacted to your comment✅ YesMultiple reactions from the same user on the same comment are deduplicated within the deduplication window. The activity author does NOT receive a notification for reactions
Comment on ActivityUser who comments (e.g., Bob)Activity author (e.g., Alice)comment{user} commented on your activity❌ NoEach comment creates a new notification; not deduplicated
Reply to CommentUser who replies (e.g., Charlie)Comment author (e.g., Bob)comment_reply{user} replied to your comment❌ NoEach reply creates a new notification; not deduplicated. The activity author does NOT receive a notification for replies
Follow UserUser who follows (e.g., Bob)User being followed (e.g., Alice)follow{user} started following you✅ YesMultiple follows/unfollows from the same user are deduplicated within the deduplication window
Mention in ActivityUser who creates activity with mention (e.g., Alice)Mentioned user (e.g., Bob)mention{user} mentioned you in an activity✅ YesMultiple mentions from the same user on the same activity are deduplicated within the deduplication window
Mention in CommentUser who creates comment with mention (e.g., Charlie)Mentioned user (e.g., Bob)comment_mention{user} mentioned you in a comment✅ YesMultiple mentions from the same user on the same comment are deduplicated within the deduplication window. The activity author does NOT receive a notification for mentions (they already get a comment notification)
Update Activity (add mentions)User who updates activity (e.g., Alice)Mentioned user (e.g., Bob)mention{user} mentioned you in an activity✅ YesWhen mentions are added via UpdateActivity or UpdateActivityPartial with handle_mention_notifications=true, mention notifications are automatically created for newly mentioned users. Multiple mentions from the same user on the same activity are deduplicated within the deduplication window
Update Comment (add mentions)User who updates comment (e.g., Charlie)Mentioned user (e.g., Bob)comment_mention{user} mentioned you in a comment✅ YesWhen mentions are added via UpdateComment with handle_mention_notifications=true, comment_mention notifications are automatically created for newly mentioned users. Multiple mentions from the same user on the same comment are deduplicated within the deduplication window. The activity author does NOT receive a notification for mentions (they already get a comment notification)

Adding notifications with the create notification activity flag only works if the target user (the one who should receive the notification) has a feed with group notification, and id <user id>.

// Eric follows Jane
ericFeed.follow(
    targetFid = janeFeed.fid,
    createNotificationActivity = true // When true Jane's notification feed will be updated with follow activity
)

// Eric comments on Jane's activity
ericFeed.addComment(
    ActivityAddCommentRequest(
        comment = "Agree!",
        activityId = janeActivity.activityId,
        createNotificationActivity = true // When true Jane's notification feed will be updated with comment activity
    )
)

// Eric reacts to Jane's activity
ericFeed.addReaction(
    activityId = janeActivity.activityId,
    request = AddReactionRequest(
        type = "like",
        createNotificationActivity = true // When true Jane's notification feed will be updated with reaction activity
    ),
)

// Eric reacts to a comment posted to Jane's activity by Sara
ericFeed.addCommentReaction(
    commentId = saraComment.id,
    request = AddCommentReactionRequest(
        type = "like",
        createNotificationActivity = true // When true Sara's notification feed will be updated with comment reaction activity
    ),
)

Updating mentions in activities and comments

You can also create or remove mention notifications when updating activities or comments. This flag defaults to false for all endpoints it's supported in.

// Alice updates her activity to mention Bob
ericFeed.updateActivity(
    activityId = activityId,
    text = "Hey @Bob check this out!",
    mentionedUserIds = listOf("bob"),
    handleMentionNotifications = true // When true, Bob will receive a mention notification
)

// Alice updates her comment to mention Charlie
ericFeed.updateComment(
    commentId = commentId,
    comment = "Hey @Charlie!",
    mentionedUserIds = listOf("charlie"),
    handleMentionNotifications = true // When true, Charlie will receive a comment_mention notification
)

// Alice removes mentions from her activity
ericFeed.updateActivity(
    activityId = activityId,
    text = "Updated text without mentions",
    mentionedUserIds = emptyList(),
    handleMentionNotifications = true // When true, mention notifications for removed users are deleted
)

Commenting and reacting to your own activities and comments will not create notification activities.

Deleting notification activities

When you add notification activities with create_notification_activity you can also have the API automatically remove these when the corresponding trigger entity is deleted. This is done by using the flag delete_notification_activity which defaults to false for all endpoints it's supported in.

ActionNotification Type DeletedNotes
Remove Activity ReactionreactionOnly the notification activity created by the user removing the reaction is deleted. Other users' reaction notifications remain unaffected.
Remove Comment Reactioncomment_reactionOnly the notification activity created by the user removing the reaction is deleted. Other users' comment reaction notifications remain unaffected.
Delete CommentcommentDeletes the comment notification for the activity author.
Delete Commentcomment_mentionWhen a comment with mentions is deleted, comment_mention notifications are deleted for all mentioned users if delete_notification_activity=true.
Unfollow UserfollowOnly the notification activity created by the user performing the unfollow is deleted.
Delete ActivitymentionWhen an activity with mentions is deleted, mention notifications are deleted for all mentioned users if delete_notification_activity=true.
Delete Activities (batch)mentionWhen multiple activities with mentions are deleted via DeleteActivities with delete_notification_activity=true, mention notifications are deleted for all mentioned users across all deleted activities.
Update Activity (remove mentions)mentionWhen mentions are removed via UpdateActivity or UpdateActivityPartial with handle_mention_notifications=true, mention notifications are automatically removed for users no longer mentioned.
Update Comment (remove mentions)comment_mentionWhen mentions are removed via UpdateComment with handle_mention_notifications=true, comment_mention notifications are automatically removed for users no longer mentioned.

Deletion Scope: The deletion behavior differs depending on the action:

  • Reactions and Follows: Only the notification activity created by the specific user performing the deletion is removed. Other users' notifications remain unaffected.
  • Activities and Comments with Mentions: When an activity or comment containing mentions is deleted, notification activities are removed for all mentioned users if delete_notification_activity=true.
// Eric unfollows Jane
ericFeed.unfollow(
    targetFid = janeFeed.fid,
    // When true the corresponding notification activity will be removed from Jane's notification feed
    deleteNotificationActivity = true
)

// Eric removes his comment
ericFeed.deleteComment(
    commentId = commentId,
    // When true the corresponding notification activity will be removed from Jane's notification feed
    deleteNotificationActivity = true
)

// Eric removes his activity reaction
ericFeed.deleteReaction(
    activityId = janeActivity.activityId,
    type = "like",
    // When true the corresponding notification activity will be removed from Jane's notification feed
    deleteNotificationActivity = true
)

// Eric removes his comment reaction
ericFeed.deleteCommentReaction(
    commentId = saraComment.id,
    type = "like",
    // When true the corresponding notification activity will be removed from Jane's notification feed
    deleteNotificationActivity = true
)

// Eric deletes his activity with mentions
ericFeed.deleteActivity(
    activityId = activityId,
    // When true, mention notifications for all mentioned users will be removed
    deleteNotificationActivity = true
)

// Eric deletes multiple activities with mentions (batch operation)
ericFeed.deleteActivities(
    activityIds = listOf(activityId1, activityId2),
    // When true, mention notifications for all mentioned users will be removed
    deleteNotificationActivity = true
)

Trigger and Target

The trigger is the entity that triggered the creation of the notification activity. This can be a follow, a reaction or a comment. When the trigger is a comment the comment data is available on the trigger object, this allows for deep linking back to the comment that triggered the notification.

The target is the receiver of the trigger action. This can be a feed (e.g., a user's feed), an activity or a comment. When the target is an activity the activity data will be present in the target object. When the target is a comment the parent activity data and comment data will be present in the target object.

Example: A reply to a comment will include the data of the reply comment in the trigger and the parent comment and activity in the target.

{
  "target": {
    "id": "<activity_id>",
    "type": "<activity_type>",
    "user_id": "<activity_user_id>",
    "comment": {
      "id": "<parent_comment_id>",
      "comment": "this is the parent comment",
      "user_id": "<parent_comment_user_id>"
    }
  },
  "trigger": {
    "text": "<comment_user_id> replied to your comment",
    "type": "comment_reply",
    "comment": {
      "id": "<comment_id>",
      "user_id": "<comment_user_id>",
      "comment": "this is the reply comment"
    }
  }
}

Aggregating on comments and activities

By default the built-in notification feed aggregates on activities only with the aggregation format {{ target_id }}-{{ type }}-{{ time.strftime('%Y-%m-%d') }}. If you want to aggregate on comments and activities alike you need to update the aggregation format to

{% if comment_id %}
  {{ comment_id }}_{{ type }}_{{ time.strftime('%Y-%m-%d') }}
{% else %}
  {{ target_id }}_{{ type }}_{{ time.strftime('%Y-%m-%d') }}
{% endif %}

This lets you show notifications like

  • Your comment has 5 new likes or
  • Your comment has 3 new replies

Reading notification activities

let notificationFeed = client.feed(group: "notification", id: "jane")
let notifications = try await notificationFeed.getOrCreate()

This is what Jane's notification feed looks like after the above interactions (only relevant fields shown):

  • Three aggregated activity groups:
    • <activity id>-comment-2025-08-04
    • <activity id>-reaction-2025-08-04
    • <feed id>-follow-2025-08-04
  • notification_context has information about the activity/action that triggered the notification
    • Please note that notification_context field is only defined if you're using the built-in notification feed and create_notification_activity flag

When reading notifications, every aggregated activity group contains at most 100 activities (can be configured with aggregation group limit). The user_count field is also computed from the last n activities, defined by aggregation group limit.

If a group has more activities than the limit, user_count_truncated will be set to true, signaling that user_count may not be accurate. This enables creating notifications like "100+ people commented on your post". The activity_count field is always accurate, even if the group has more activities than the limit.

Example API response:

{
  aggregated_activities: [
    {
      activity_count: 1,
      user_count: 1,
      user_count_truncated: false,
      group: "activity123-comment-2025-08-04",
      activities: [
        {
          type: "comment",
          user: {
            id: "eric",
            name: "Eric",
            // other User fields
          },
          notification_context: {
            trigger: {
              text: "Eric commented on your activity",
              type: "comment",
            },
            target: {
              user_id: "jane",
              type: "post",
              text: "As earnestly shameless elsewhere defective estimable fulfilled of",
              id: "a0668408-0eb9-4906-a1cf-be79f988051d",
              attachments: [
                {
                  type: "image",
                  image_url: "https://...",
                },
              ],
            },
          },
          // Other activity fields
        },
      ],
    },
    {
      activity_count: 1,
      user_count: 1,
      group: "activity123-reaction-2025-08-04",
      activities: [
        {
          type: "reaction",
          user: {
            id: "eric",
            name: "Eric",
          },
          notification_context: {
            target: {
              id: "8966090a-30bf-4fe2-b8bc-b0fe36200e56",
              user_id: "jane",
              type: "post",
              text: "Ask too matter formed county wicket oppose talent",
            },
            trigger: {
              type: "reaction",
              text: "Eric reacted to your activity",
            },
          },
        },
      ],
    },
    {
      activity_count: 1,
      user_count: 1,
      group: "jane-follow-2025-08-04",
      activities: [
        {
          type: "follow",
          user: {
            id: "eric",
            name: "Eric",
          },
          notification_context: {
            target: {
              id: "jane",
              name: "Jane",
            },
            trigger: {
              type: "follow",
              text: "Eric started following you",
            },
          },
        },
      ],
    },
    {
      activity_count: 1,
      user_count: 1,
      group: "comment456-comment_reply-2025-08-04",
      activities: [
        {
          type: "comment_reply",
          user: {
            id: "charlie",
            name: "Charlie"
          },
          notification_context: {
            target: {
              id: "8966090a-30bf-4fe2-b8bc-b0fe36200e56",
              user_id: "alice",
              type: "post",
              text: "Ask too matter formed county wicket oppose talent",
              comment: {
                id: "comment456",
                user_id: "bob",
                comment: "Great post! I totally agree with this."
              }
            },
            trigger: {
              type: "comment_reply",
              text: "Charlie replied to your comment"
            }
          }
        }
      ]
    },
  ];
}

Push Notifications

For information on configuring push notifications, see Feed Group Push Configuration.

Notification status

If notification tracking is turned on for the feed group then you'll receive notification status when reading the feed. Notification status tells

  • How many unread notifications does the feed have?
  • Which notifications are read?
  • How many unseen notifications does the feed have?
  • Which notifications are seen?

Unread/unseen count is computed from the last 1000 activities of the feed, and aggregated into maximum 100 groups. This means that unread/unseen count will never be more than 100.

For example, take the notification system on Facebook. If you click the notification icon, all notifications get marked as seen. However, an individual notification only gets marked as read when you click on it.

Here is an example payload for notificaiton status:

{
  notification_status: {
    // Number of unread notifications
    unread: 12,
    // Number of unseen notifications
    unseen: 0,
    last_seen_at: // Date object with last seen timestamp,
    seen_activities: [],
    last_read_at: // Date object with last read timestamp,
    read_activities: ['activity123-reaction-2025-08-04'],
  }
}

// Check if a group is read/seen
const isRead = (lastReadAt && group.updated_at.getTime() < lastReadAt.getTime()) || readActivities.includes(group.group);
const isSeen = (lastSeenAt && group.updated_at.getTime() < lastSeenAt.getTime()) || seenActivities.includes(group.group);

Marking notifications as seen

notificationFeed.markActivity(
    request = MarkActivityRequest(
        // Mark all notifications as seen...
        markAllSeen = true,
        // ...or only selected ones
        markSeen = listOf(
            /* group names to mark as seen */
        )
    )
)

Marking notifications as read

notificationFeed.markActivity(
    request = MarkActivityRequest(
        // Mark all notifications as read...
        markAllRead = true,
        // ...or only selected ones
        markRead = listOf(
            /* group names to mark as read */
        )
    )
)

Pagination

Pagination for notification (aggregated) feeds work the same way as it works for any other feed:

let feed = client.feed(
    for: .init(
        group: "user",
        id: "john",
        activityLimit: 10
    )
)
// Page 1
try await feed.getOrCreate()
let activities = feed.state.activities // First 10 activities

// Page 2
let page2Activities = try await feed.queryMoreActivities(limit: 10)

let page1And2Activities = feed.state.activities