Activity Feeds V3 is in closed alpha — do not use it in production (just yet).

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:

myNotificationGroup, err := client.Feeds().CreateFeedGroup(context.Background(), &getstream.CreateFeedGroupRequest{
    ID: "myid2",
    // Group by activity type and day
    Aggregation: &getstream.AggregationConfig{
        Format: getstream.PtrTo("{{ type }}-{{ time.strftime(\"%Y-%m-%d\") }}"),
    },
    // Enable notification tracking
    Notification: &getstream.NotificationConfig{
        TrackRead: getstream.PtrTo(true),
        TrackSeen: getstream.PtrTo(true),
    },
})
if err != nil {
    log.Fatal("Error creating feed group:", err)
}

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):

ctx := context.Background()

// Add a custom notification directly to a user's notification feed
_, err = client.Feeds().AddActivity(ctx, &getstream.AddActivityRequest{
  Feeds:  []string{"notification:john"}, // Target user's notification feed
  Type:   "milestone",                       // Custom activity type
  Text:   getstream.PtrTo("You've reached 1000 followers!"),
  UserID: getstream.PtrTo("<user id>"),
  Custom: map[string]any{
    "milestone_type": "followers",
    "count":          1000,
  },
})
if err != nil {
  log.Fatal("Error adding milestone activity:", err)
}

// Add activity to custom notification feed group
_, err = client.Feeds().AddActivity(ctx, &getstream.AddActivityRequest{
  Feeds:  []string{"alerts:john"}, // Custom notification feed group
  Type:   "system_alert",
  Text:   getstream.PtrTo("Your subscription expires in 3 days"),
  UserID: getstream.PtrTo("<user id>"),
})
if err != nil {
  log.Fatal("Error adding system alert activity:", err)
}

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.

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

ctx := context.Background()

// Eric follows Jane
_, err = client.Feeds().Follow(ctx, &getstream.FollowRequest{
  Source:                     "user:eric",
  Target:                     "user:jane",
  CreateNotificationActivity: getstream.PtrTo(true), // When true Jane's notification feed will be updated with follow activity
})
if err != nil {
  log.Fatal("Error following user:", err)
}

// Eric comments on Jane's activity
_, err = client.Feeds().AddComment(ctx, &getstream.AddCommentRequest{
  Comment:                    "Agree!",
  ObjectID:                   "janeActivity.id", // This would be the actual activity ID
  ObjectType:                 "activity",
  CreateNotificationActivity: getstream.PtrTo(true), // When true Jane's notification feed will be updated with comment activity
  UserID:                     getstream.PtrTo("eric"),
})
if err != nil {
  log.Fatal("Error adding comment:", err)
}

// Eric reacts to Jane's activity
_, err = client.Feeds().AddReaction(ctx, "janeActivity.id", &getstream.AddReactionRequest{ // This would be the actual activity ID
  Type:                       "like",
  CreateNotificationActivity: getstream.PtrTo(true), // When true Jane's notification feed will be updated with reaction activity
  UserID:                     getstream.PtrTo("eric"),
})
if err != nil {
  log.Fatal("Error adding reaction:", err)
}

// Eric reacts to a comment posted to Jane's activity by Sara
_, err = client.Feeds().AddCommentReaction(ctx, "saraComment.id", &getstream.AddCommentReactionRequest{ // This would be the actual comment ID
  Type:                       "like",
  CreateNotificationActivity: getstream.PtrTo(true), // When true Sara's notification feed will be updated with comment reaction activity
  UserID:                     getstream.PtrTo("eric"),
})
if err != nil {
  log.Fatal("Error adding comment reaction:", err)
}

Reading notification activities

notificationFeed := client.Feeds().Feed("notification", "john")
// Read notifications
response, err := notificationFeed.GetOrCreate(context.Background(), &getstream.GetOrCreateFeedRequest{
  Limit:  getstream.PtrTo(20),
  UserID: getstream.PtrTo("john"),
})
if err != nil {
  log.Fatal(err)
}
log.Printf("Response: %+v\n", response.Data.AggregatedActivities)

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",
            },
          },
        },
      ],
    },
  ];
}

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:

{
  NotificationStatus: {
    // Number of unread notifications
    Unread: 12,
    // Number of unseen notifications
    Unseen: 0,
    LastReadAt: // Timestamp object with last seen timestamp,,
    SeenActivities: [],
    LastSeenAt: // Timestamp object with last read timestamp,,
    ReadActivities: ['activity123-reaction-2025-08-04'],
  }
}

import (
	// Other imports...
	"slices"
)

// Check if a group is read/seen
group := response.Data.AggregatedActivities[0]
lastSeenAt := response.Data.NotificationStatus.LastSeenAt
seenActivities := response.Data.NotificationStatus.SeenActivities
lastReadAt := response.Data.NotificationStatus.LastReadAt
readActivities := response.Data.NotificationStatus.ReadActivities

isRead := (lastReadAt != nil && lastReadAt.Time != nil && group.UpdatedAt.Time != nil && group.UpdatedAt.Time.Before(*lastReadAt.Time)) || slices.Contains(readActivities, group.Group)

isSeen := (lastSeenAt != nil && lastSeenAt.Time != nil && group.UpdatedAt.Time != nil && group.UpdatedAt.Time.Before(*lastSeenAt.Time)) || slices.Contains(seenActivities, group.Group)

Marking notifications as seen

notificationFeed := client.Feeds().Feed("notification", "john")

_, err = notificationFeed.MarkActivity(context.Background(), &getstream.MarkActivityRequest{
  // Mark all notifications as seen...
  MarkAllSeen: getstream.PtrTo(true),
  // ...or only selected ones
  MarkSeen: []string{
    // group names to mark as seen
  },
  UserID:      getstream.PtrTo("john"),
})
if err != nil {
  log.Fatal("Error:", err)
}

Marking notifications as read

notificationFeed := client.Feeds().Feed("notification", "john")

_, err = notificationFeed.MarkActivity(context.Background(), &getstream.MarkActivityRequest{
  // Mark all notifications as read...
  MarkAllRead: getstream.PtrTo(true),
  // ...or only selected ones
  MarkRead: []string{
    // group names to mark as read
  },
  UserID:      getstream.PtrTo("john"),
})
if err != nil {
  log.Fatal("Error:", err)
}

Pagination

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

feed := client.Feeds().Feed("user", "john")

// First page
firstPage, err := feed.GetOrCreate(context.Background(), &getstream.GetOrCreateFeedRequest{
  Limit:  getstream.PtrTo(10),
  UserID: getstream.PtrTo("john"),
})
if err != nil {
  log.Fatal("Error getting first page:", err)
}

// Second page request using next cursor
nextPage, err := feed.GetOrCreate(context.Background(), &getstream.GetOrCreateFeedRequest{
  Next:   firstPage.Data.Next,
  Limit:  getstream.PtrTo(10),
  UserID: getstream.PtrTo("john"),
})
if err != nil {
  log.Fatal("Error getting next page:", err)
}

log.Printf("First page activities count: %d", len(firstPage.Data.Activities))
log.Printf("Next page activities count: %d", len(nextPage.Data.Activities))
© Getstream.io, Inc. All Rights Reserved.