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

# Reward notifications

> How to integrate the Almedia SDK — synced from the source repository.

Once the player is linked, the SDK polls the Almedia backend for reward notifications.

## Polling

Polling starts automatically once the player's status reaches `Linked`. The native layer drives the loop; no manual start from host code is required. The loop runs at the interval the backend sets. When the backend sets none, it runs at your configured interval (`AlmediaConfig.NotificationsPollingIntervalSec` at runtime, or `AlmediaLinkSettings.NotificationPollIntervalSeconds` on the asset; default 30s, minimum 5s). It runs on the foreground only. The native plugin pauses polling when the app backgrounds and resumes when it returns to foreground.

Host code only interacts with polling for opt-in pause and resume:

`StopNotificationPolling()` halts the loop. Common use: stop while showing a cinematic or full-screen ad, restart after.

`StartNotificationPolling()` resumes the loop after a `StopNotificationPolling()` call.

`FetchNotifications()` performs a one-shot fetch outside the loop. Useful from a "Refresh" button or after a known reward-granting event. The backend can hold the request for up to a minute until a message arrives, so the result can take that long.

> `OnNotificationsReceived` fires only when at least one notification comes back. An empty result (the player has none) fires nothing, so "fetched and got zero" is indistinguishable from "the fetch has not returned yet" without your own state tracking. In particular, a "clear the badge on refresh" pattern will not fire on an empty result - clear host-side state when you *issue* the fetch, not from the callback.

### Composition rules

The three methods compose as follows. The native plugins enforce these rules; no guards are needed on the host side.

| Call | If polling is running | If polling is stopped |
| - | - | - |
| `StartNotificationPolling()` | No-op. The schedule is unchanged - the loop is not double-scheduled, the interval is not shortened, no immediate poll is triggered. | Starts the loop. The first tick fires after one interval. |
| `StopNotificationPolling()` | Stops the loop. Any in-flight request is allowed to complete; its result (if any) still arrives on `OnNotificationsReceived`. | No-op. |
| `FetchNotifications()` | Fires the request immediately **and** resets the polling clock so the next scheduled tick happens at `now + interval`, not at the originally planned time. This prevents a near-duplicate poll moments after a host-driven refresh. | Fires the request immediately. Does not start the loop. |

Practical consequence: calling `FetchNotifications()` on a user action (e.g. opening a notifications drawer) is safe and free of duplicates. The host doesn't need to `Stop`, `Fetch`, `Start` defensively.

## Default UI

When `AlmediaLinkSettings.EnableDefaultNotificationUI` is on (the default), the SDK creates a screen-space-overlay Canvas at the top of the sorting order and renders incoming notifications using two prefabs:

* **NotificationCard** - a single card that slides up from the bottom and auto-dismisses after about 4 seconds. The dismiss delay is a serialized field on the prefab (`_dismissDelay`), so you can adjust the timing directly in the Inspector - the same way as Bottom Padding, and without needing a Prefab Variant. When more than one notification arrives in the same batch, the card shows the latest and displays a stacked indicator.
* **ActivityOverlay** - tapping the stacked indicator opens a full list of the most recent notifications (up to 3).

The row icon is the sprite authored on the bundled `NotificationRow` prefab. Change it with a Prefab Variant - see [Customize the UI](https://docs.almedia.dev/technical/integration-guide/customize-the-ui.md).

## Custom UI

Disable the default UI in **Almedia → Settings → SDK Configuration → Enable Default Notification UI**, then handle notifications directly:

```csharp theme={"theme":{"light":"github-light","dark":"night-owl"}}
Almedia.OnNotificationsReceived += notifications => {
    foreach (var n in notifications)
        MyToastSystem.Show(n.Title, n.Message);
};
```

`AlmediaNotification` is a plain data class with `Id`, `Title`, `Message`, `Timestamp` (raw ISO 8601 string from the backend), `ReceivedAt` (parsed UTC `DateTimeOffset?`, ready for "5 minutes ago" math), `Display` (the presentation hint, `"popup"` or `"tray"`), and `IconUrl` (absolute icon URL, or `null` when the backend sent none).

For relative-time labels in your own UI, use `ReceivedAt` directly:

```csharp theme={"theme":{"light":"github-light","dark":"night-owl"}}
if (n.ReceivedAt.HasValue)
{
    var elapsed = System.DateTimeOffset.UtcNow - n.ReceivedAt.Value;
    label.text = elapsed.TotalMinutes < 1 ? "now" : $"{(int)elapsed.TotalMinutes}min ago";
}
```

***


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.