# Notifications

**URL:** https://heroiclabs.com/docs/nakama/concepts/notifications/
**Summary:** In-app notifications make it easy to broadcast a message to one or more users. They are great for sending announcements, alerts, or notices of in-game rewards and gifts. Notifications can be stored until read or pushed so only a connected user will see it.
**Keywords:** in-app notifications, send notifications, receive notifications, list notifications, delete notifications, notifications codes
**Categories:** nakama, notifications, concepts

---


# In-app Notifications

In-app notifications make it easy to broadcast a message to one or more users. They are great for sending announcements, alerts, or notices of in-game rewards and gifts.

A notification can be stored until read when the app is next opened or it can be pushed so only a connected user will see it. You can also use notifications to trigger custom actions within your game and change client behavior.

These notifications are viewed within the app which makes them a great companion to push notifications viewed outside the app.

## Send notifications

You can send a notification to one or more users with server-side code. It can be sent to any user in the game - there's no need to be friends to be able to exchange messages. A number of notifications are also sent by the server implicitly on certain events.

Each notification has a code which is used to categorize it. The code you choose for your notifications must start at `0` and increase upwards. See [below](#notification-codes) for reserved message codes.

A notification has a content object which will be encoded as JSON.

Notifications can be marked as persistent when sent. A non-persistent message will only be received by a client which is currently connected to the server (i.e. a user who is online). If you want to make sure a notification is never lost before it's read, it should be marked as persistent when sent.

{{< code type="server" >}}
```lua
local nk = require("nakama")

local user_id = "user id to send to"
local sender_id = nil -- "nil" for server sent.
local content = {
  item_id = "192308394345345",
  item_icon = "storm_bringer_sword.png"
}
local subject = "You earned a secret item!"
local code = 1
local persistent = true

nk.notification_send(user_id, subject, content, code, sender_id, persistent)
```
{{< / code >}}

{{< code type="server" >}}
```go
subject := "You earned a secret item!"
content := map[string]interface{}{
    "item_id": "192308394345345",
    "item_icon": "storm_bringer_sword.png"
}
userID := "user id to send to"
senderID := "" // Empty string for server sent.
code := 1
persistent := true

nk.NotificationSend(ctx, userID, subject, content, code, senderID, persistent)
```
{{< / code >}}

{{< code type="server" >}}
```typescript
let receiverId = '4c2ae592-b2a7-445e-98ec-697694478b1c';
let subject = "You've unlocked level 100!";
let content = {
  rewardCoins: 1000,
}
let code = 101;
let senderId = 'dcb891ea-a311-4681-9213-6741351c9994' // who the message if from
let persistent = true;

nk.notificationSend(receiverId, subject, content, code, senderId, persistent);
```
{{< / code >}}

## Receive notifications

A callback can be registered for notifications received when a client is connected. The handler will be called whenever a notification is received as long as the client socket remains connected. When multiple messages are returned (batched for performance) the handler will be called once for each notification.

{{< code type="client" >}}
```javascript
socket.onnotification = (notification) => {
  console.log("Received %o", notification);
  console.log("Notification content %s", notification.content);
}
```
{{< / code >}}

{{< code type="client" >}}
```csharp
socket.ReceivedNotification += notification =>
{
    Console.WriteLine("Received: {0}", notification);
    Console.WriteLine("Notification content: '{0}'", notification.Content);
};
```
{{< / code >}}

{{< code type="client" >}}
```swift
socket.onNotifications = { notification in
    print("Received: \(notification)")
    print("Notification content: \(notification.content)")
}
```
{{< / code >}}

{{< code type="client" >}}
```dart
socket.onNotifications.listen((notification) {
  print("Received: $notification");
  print("Notification content: ${notification.content}");
});
```
{{< / code >}}

{{< code type="client" >}}
```cpp
rtListener->setNotificationsCallback([](const NNotificationList& notifications)
{
    for (auto& notification : notifications.notifications)
    {
        std::cout << "Notification content " << notification.content << std::cout;
    }
});
```
{{< / code >}}

{{< code type="client" >}}
```java
SocketListener listener = new AbstractSocketListener() {
    @Override
    public void onNotifications(final NotificationList notifications) {
        System.out.println("Received notifications");
        for (Notification notification : notifications.getNotificationsList()) {
            System.out.format("Notification content: %s", notification.getContent());
        }
    }
};
```
{{< / code >}}

{{< code type="client" framework="godot3" >}}
```gdscript
func _ready():
    # First, setup the socket as explained in the authentication section.
    socket.connect("received_notification", self, "_on_notification")

func _on_notification(p_notification : NakamaAPI.ApiNotification):
    print(p_notification)
    print(p_notification.content)
```
{{< / code >}}

{{< code type="client" framework="godot4" >}}
```gdscript
func _ready():
    # First, setup the socket as explained in the authentication section.
    socket.received_notification.connect(self._on_notification)

func _on_notification(p_notification : NakamaAPI.ApiNotification):
    print(p_notification)
    print(p_notification.content)
```
{{< / code >}}

{{< code type="client" framework="defold" >}}
```lua
local group_id = "<group id>"
socket.on_notification(function(message)
  print("Received notification!")
  pprint(message)
end)
```
{{< / code >}}

{{< missing type="client" lang="shell" />}}
{{< missing type="client" lang="bash" />}}

## List notifications

You can list notifications which were received when the user was offline. These notifications are ones which were marked "persistent" when sent. The exact logic depends on your game or app but we suggest you retrieve notifications after a client reconnects. You can then display a UI within your game or app with the list.

{{< code type="client" >}}
```bash
curl -X GET "http://127.0.0.1:7350/v2/notification?limit=10" \
  -H 'Authorization: Bearer <session token>'
```
{{< / code >}}

{{< code type="client" >}}
```javascript
const result = await client.listNotifications(session, 10);
result.notifications.forEach(notification => {
  console.info("Notification code %o and subject %o.", notification.code, notification.subject);
});
console.info("Fetch more results with cursor:", result.cacheable_cursor);
```
{{< / code >}}

{{< code type="client" >}}
```csharp
var result = await client.ListNotificationsAsync(session, 10);
foreach (var n in result.Notifications)
{
    Console.WriteLine("Subject '{0}' content '{1}'", n.Subject, n.Content);
}
```
{{< / code >}}

{{< code type="client" >}}
```swift
let result = try await client.listNotifications(session: session, limit: 10)

for notification in result.notifications {
    print("Notification content: \(notification.content)")
}
```
{{< / code >}}

{{< code type="client" >}}
```dart
final result = await client.listNotifications(
  session: session,
  limit: 10,
);

for (final notification in result.notifications) {
  print('Notification content: ${notification.content}');
}
```
{{< / code >}}

{{< code type="client" >}}
```cpp
auto successCallback = [](NNotificationListPtr list)
{
    for (auto& notification : list->notifications)
    {
        std::cout << "Notification content " << notification.content << std::endl;
    }
};

client->listNotifications(session, 10, opt::nullopt, successCallback);
```
{{< / code >}}

{{< code type="client" >}}
```java
NotificationList notifications = client.listNotifications(session, 10).get();
for (Notification notification : notifications.getNotificationsList()) {
    System.out.format("Notification content: %s", notification.getContent());
}
```
{{< / code >}}

{{< code type="client" framework="godot3" >}}
```gdscript
var result : NakamaAPI.ApiNotificationList = yield(client.list_notifications_async(session, 10), "completed")
if result.is_exception():
    print("An error occurred: %s" % result)
    return
for n in result.notifications:
    print("Subject '%s' content '%s'" % [n.subject, n.content])
```
{{< / code >}}

{{< code type="client" framework="godot4" >}}
```gdscript
var result : NakamaAPI.ApiNotificationList = await client.list_notifications_async(session, 10)
if result.is_exception():
    print("An error occurred: %s" % result)
    return
for n in result.notifications:
    print("Subject '%s' content '%s'" % [n.subject, n.content])
```
{{< / code >}}

{{< code type="client" >}}
```shell
GET /v2/notification?limit=10
Host: 127.0.0.1:7350
Accept: application/json
Content-Type: application/json
Authorization: Bearer <session token>
```
{{< / code >}}

{{< code type="client" framework="defold" >}}
```lua
local result = client.list_notifications(10)
if result.error then
  print(result.message)
  return
end
for _,notification in ipairs(result.notifications) do
  pprint(notification)
end
```
{{< / code >}}

A list of notifications can be retrieved in batches of up to 100 at a time. To retrieve all messages you should accumulate them with the cacheable cursor. You can keep this cursor on the client and use it when the user reconnects to catch up on any notifications they may have missed while offline.

You will usually want to list only 100 notifications at a time, otherwise you might cause user fatigue. A good solution could be to have the UI fetch the next 100 notifications when the user scrolls to the bottom of your UI panel.

{{< code type="client" >}}
```bash
curl -X GET "http://127.0.0.1:7350/v2/notification?limit=100&cacheable_cursor=<cacheableCursor>" \
  -H 'Authorization: Bearer <session token>'
```
{{< / code >}}

{{< code type="client" >}}
```javascript
var allNotifications = [];

var accumulateNotifications = (cursor) => {
  var result = await client.listNotifications(session, 100, cursor);
  if (result.notifications.length == 0) {
    return;
  }
  allNotifications.concat(result.notifications.notifications);
  accumulateNotifications(result.cacheable_cursor);
}
accumulateNotifications("");
```
{{< / code >}}

{{< code type="client" >}}
```csharp
var result = await client.ListNotificationsAsync(session, 100);
if (!string.IsNullOrEmpty(result.CacheableCursor))
{
    result = await client.ListNotificationsAsync(session, 100, result.CacheableCursor);
    foreach (var n in result.Notifications)
    {
        Console.WriteLine("Subject '{0}' content '{1}'", n.Subject, n.Content);
    }
}
```
{{< / code >}}

{{< code type="client" >}}
```swift
var allNotifications = [ApiNotification]()

func accumulateNotifications(cursor: String) async {
    do {
        let result = try await client.listNotifications(session: session, limit: 100, cacheableCursor: cursor)
        allNotifications.append(contentsOf: result.notifications)
        if !result.cacheableCursor.isEmpty {
            await accumulateNotifications(cursor: result.cacheableCursor)
        }
    } catch {
        print("Failed to fetch notifications: \(error.localizedDescription)")
    }
}

await accumulateNotifications(cursor: "")
```
{{< / code >}}

{{< code type="client" >}}
```dart
final allNotifications = [];

Future accumulateNotifications(String cursor) async {
  final result = await client.listNotifications(
    session: session,
    limit: 100,
    cursor: cursor,
  );
  allNotifications.addAll(result.notifications);
  if (result.cursor != null && result.cursor!.isNotEmpty) {
    await accumulateNotifications(result.cursor!);
  }
}

await accumulateNotifications('');
```
{{< / code >}}

{{< code type="client" >}}
```cpp
// add to your class: std::vector<NNotification> allNotifications;

void YourClass::accumulateNotifications(const string& cursor)
{
    auto successCallback = [this](NNotificationListPtr list)
    {
        allNotifications.insert(allNotifications.end(), list->notifications.begin(), list->notifications.end());

        if (!list->cacheableCursor.empty())
        {
            accumulateNotifications(list->cacheableCursor);
        }
    };

    client->listNotifications(session, 100, cursor, successCallback);
}

accumulateNotifications("");
```
{{< / code >}}

{{< code type="client" >}}
```java
NotificationList notifications = client.listNotifications(session, 10).get();
if (notifications.getCacheableCursor() != null) {
    notifications = client.listNotifications(session, 10, notifications.getCacheableCursor()).get();
    for (Notification notification : notifications.getNotificationsList()) {
        System.out.format("Notification content: %s", notification.getContent());
    }
}
```
{{< / code >}}

{{< code type="client" framework="godot3" >}}
```gdscript
var result : NakamaAPI.ApiNotificationList = yield(client.list_notifications_async(session, 1), "completed")
if result.is_exception():
    print("An error occurred: %s" % result)
    return
for n in result.notifications:
    print("Subject '%s' content '%s'" % [n.subject, n.content])
while result.cacheable_cursor and result.notifications:
    result = yield(client.list_notifications_async(session, 10, result.cacheable_cursor), "completed")
    if result.is_exception():
          print("An error occurred: %s" % result)
          return
    for n in result.notifications:
          print("Subject '%s' content '%s'" % [n.subject, n.content])
print("Done")
```
{{< / code >}}

{{< code type="client" framework="godot4" >}}
```gdscript
var result : NakamaAPI.ApiNotificationList = await client.list_notifications_async(session, 1)
if result.is_exception():
    print("An error occurred: %s" % result)
    return
for n in result.notifications:
    print("Subject '%s' content '%s'" % [n.subject, n.content])
while result.cacheable_cursor and result.notifications:
    result = await client.list_notifications_async(session, 10, result.cacheable_cursor)
    if result.is_exception():
          print("An error occurred: %s" % result)
          return
    for n in result.notifications:
          print("Subject '%s' content '%s'" % [n.subject, n.content])
print("Done")
```
{{< / code >}}

{{< code type="client" >}}
```shell
GET /v2/notification?limit=100&cursor=<cacheableCursor>
Host: 127.0.0.1:7350
Accept: application/json
Content-Type: application/json
Authorization: Bearer <session token>
```
{{< / code >}}

{{< code type="client" framework="defold" >}}
```lua
local cursor = nil
repeat
  local result = client.list_notifications(10, cursor)
  if result.error then
      print(result.message)
      break
  end

  cursor = result.cursor
  for _,notification in ipairs(result.notifications) do
      pprint(notification)
  end
until not cursor
```
{{< / code >}}

It can be useful to retrieve only notifications which have been added since the list was last retrieved by a client. This can be done with the cacheable cursor returned with each list message. Sending the cursor through a new list operation will retrieve only notifications newer than those seen.

The cacheable cursor marks the position of the most recent notification retrieved. We recommend you store the cacheable cursor in device storage and use it when the client makes its next request for recent notifications.

{{< code type="client" >}}
```bash
curl -X GET "http://127.0.0.1:7350/v2/notification?limit=10&cacheable_cursor=<cacheableCursor>" \
  -H 'Authorization: Bearer <session token>'
```
{{< / code >}}

{{< code type="client" >}}
```javascript
const cacheableCursor = "<cacheableCursor>";
const result = await client.listNotifications(session, 10, cacheableCursor);
result.notifications.forEach(notification => {
  console.info("Notification code %o and subject %o.", notification.code, notification.subject);
});
```
{{< / code >}}

{{< code type="client" >}}
```csharp
const string cacheableCursor = "<cacheableCursor>";
var result = await client.ListNotificationsAsync(session, 10, cacheableCursor);
foreach (var n in result.Notifications)
{
    Console.WriteLine("Subject '{0}' content '{1}'", n.Subject, n.Content);
}
```
{{< / code >}}

{{< code type="client" >}}
```swift
let cacheableCursor = "<cacheableCursor>"

var result = try await client.listNotifications(session: session, limit: 10, cacheableCursor: cacheableCursor)

for notification in result.notifications {
    print("Notification content: \(notification.content)")
}
```
{{< / code >}}

{{< code type="client" >}}
```dart
const cacheableCursor = '<cacheableCursor>';

final result = await client.listNotifications(
  session: session,
  limit: 10,
  cursor: cacheableCursor,
);

for (final notification in result.notifications) {
  print('Notification content: ${notification.content}');
}
```
{{< / code >}}

{{< code type="client" >}}
```cpp
auto successCallback = [](NNotificationListPtr list)
{
    for (auto& notification : list->notifications)
    {
        std::cout << "Notification content " << notification.content << std::endl;
    }
};

string cacheableCursor = "<cacheableCursor>";
client->listNotifications(session, 10, cacheableCursor, successCallback);
```
{{< / code >}}

{{< code type="client" >}}
```java
String cacheableCursor = "<cacheableCursor>";
NotificationList notifications = client.listNotifications(session, 10, cacheableCursor).get();
for (Notification notification : notifications.getNotificationsList()) {
    System.out.format("Notification content: %s", notification.getContent());
}
```
{{< / code >}}

{{< code type="client" framework="godot3" >}}
```gdscript
var cursor = "<cacheable-cursor>";
var result : NakamaAPI.ApiNotificationList = yield(client.list_notifications_async(session, 10, cursor), "completed")
if result.is_exception():
    print("An error occurred: %s" % result)
    return
for n in result.notifications:
    print("Subject '%s' content '%s'" % [n.subject, n.content])
```
{{< / code >}}

{{< code type="client" framework="godot4" >}}
```gdscript
var cursor = "<cacheable-cursor>";
var result : NakamaAPI.ApiNotificationList = await client.list_notifications_async(session, 10, cursor)
if result.is_exception():
    print("An error occurred: %s" % result)
    return
for n in result.notifications:
    print("Subject '%s' content '%s'" % [n.subject, n.content])
```
{{< / code >}}

{{< code type="client" >}}
```shell
GET /v2/notification?limit=10&cursor=<cacheableCursor>
Host: 127.0.0.1:7350
Accept: application/json
Content-Type: application/json
Authorization: Bearer <session token>
```
{{< / code >}}

{{< code type="client" framework="defold" >}}
```lua
local cursor = "<cacheable-cursor>"
local result = client.list_notifications(10, cursor)
if result.error then
  print(result.message)
  return
end

for _,notification in ipairs(result.notifications) do
  pprint(notification)
end
```
{{< / code >}}

## Delete notifications

You can delete one or more notifications from the client. This is useful to purge notifications which have been read or consumed by the user and prevent a build up of old messages. When a notification is deleted all record of it is removed from the system and it cannot be restored.

{{< code type="client" >}}
```bash
curl -X DELETE "http://127.0.0.1:7350/v2/notification?ids=<notificationId>&ids=<notificationId>" \
  -H 'Authorization: Bearer <session token>'
```
{{< / code >}}

{{< code type="client" >}}
```javascript
const notificationIds = ["<notificationId>"];
await client.deleteNotifications(session, notificationIds);
```
{{< / code >}}

{{< code type="client" >}}
```csharp
var notificationIds = new[] {"<notificationId>"};
await client.DeleteNotificationsAsync(session, notificationIds);
```
{{< / code >}}

{{< code type="client" >}}
```swift
var notificationIds = ["<notificationId>"]

try await client.deleteNotifications(session: session, ids: notificationIds)
```
{{< / code >}}

{{< code type="client" >}}
```dart
final notificationIds = ['<notificationId>'];

await client.deleteNotifications(
  session: session,
  notificationIds: notificationIds,
);
```
{{< / code >}}

{{< code type="client" >}}
```cpp
client->deleteNotifications(session, { "<notificationId>" });
```
{{< / code >}}

{{< code type="client" >}}
```java
String[] notificationIds = new String[] {"<notificationId>"};
client.deleteNotifications(session, notificationIds).get();
```
{{< / code >}}

{{< code type="client" framework="godot3" >}}
```gdscript
var notification_ids = ["<notification-id>"]
var delete : NakamaAsyncResult = yield(client.delete_notifications_async(session, notification_ids), "completed")
if delete.is_exception():
    print("An error occurred: %s" % delete)
    return
print("Deleted")
```
{{< / code >}}

{{< code type="client" framework="godot4" >}}
```gdscript
var notification_ids = ["<notification-id>"]
var delete : NakamaAsyncResult = await client.delete_notifications_async(session, notification_ids)
if delete.is_exception():
    print("An error occurred: %s" % delete)
    return
print("Deleted")
```
{{< / code >}}

{{< code type="client" >}}
```shell
DELETE /v2/notification?ids=<notificationId>&ids=<notificationId>
Host: 127.0.0.1:7350
Accept: application/json
Content-Type: application/json
Authorization: Bearer <session token>
```
{{< / code >}}

{{< code type="client" framework="defold" >}}
```lua
local ids = { "<notification-id>" }
local result = client.delete_notifications(ids)
if result.error then
  print(result.message)
  return
end
```
{{< / code >}}

## Notification codes

The server reserves all codes that are less than or equal to 0 for messages sent implicitly on certain events. You can define your own notification codes by simply using values greater than 0.

The code is useful to decide how to display the notification in your UI.

| Code | Purpose |
|
------| ------- |
| 0    | Reserved |
| -1   | Message received from user X while offline or not in channel. |
| -2   | User X wants to add you as a friend.  |
| -3   | User X accepted your friend invite. |
| -4   | You've been accepted to X group. |
| -5   | User X wants to join your group. |
| -6   | Your friend X has just joined the game. |
| -7   | Final notifications to sockets closed via the [`single_socket` configuration](../../getting-started/configuration/#session). |
| -8   | You've been banned. |
