# Live feed

`mayarin.feed.stream()` is an async sequence of a merchant's payments **as they finish**\: money that arrived (`payment.completed`) and payments that did not make it (`payment.failed`). It is what a donation alert, a live sales counter, or a kiosk screen listens to. A payment that is still on its way is not announced; only the outcome is.

It runs on both clients\:

* On the **browser** client with an `alertKey` (`ak_…`), for an overlay page. An alert key is read-only and reaches only this feed.
* On the **server** client with an `sk_` key that can read payments, for a server that relays the feed to its own clients.

```ts
import { createMayarinBrowser } from "@mayarin/sdk/browser";

const mayarin = createMayarinBrowser({
  baseUrl: "https://api.mayarin.xyz",
  alertKey: "ak_…",
});

const controller = new AbortController();

for await (const event of mayarin.feed.stream({ signal: controller.signal })) {
  if (event.type !== "payment.completed") continue;
  showAlert({
    who: event.data.metadata.donor ?? "Someone",
    message: event.data.metadata.message,
    amount: event.data.amount.display,
  });
}
```

## What an event holds

| Field                      | Meaning                                                                          |
| -------------------------- | -------------------------------------------------------------------------------- |
| `id`                       | `<payment intent id>:<status>`. Stable, so a repeat can be dropped.              |
| `type`                     | `payment.completed` or `payment.failed`.                                         |
| `occurredAt`               | When the payment finished. Also the resume point.                                |
| `data.amount`              | What it was priced at, in the currency the merchant quoted. Has a `display` form. |
| `data.settlement`          | What reached the merchant\: `amount`, `fee` and `net`. `null` when nothing settled. |
| `data.metadata`            | What your server attached when it created the payment.                           |
| `data.payment`             | The asset and network the payer used, when they chose one.                        |
| `data.merchantReference`   | Your own order id, if you set one.                                               |

A webhook carries no amount; the feed does, because showing a payment means showing what it was.

## It keeps itself connected

The sequence holds one connection and keeps it open. When it drops, it reconnects with a doubling wait (`reconnectMs`, up to `maxReconnectMs`) and **resumes from the last payment it saw**, so a blip does not lose a donation. A payment can arrive twice at that seam, and the sequence drops the repeat by `id`, so you see each once.

Pass `since` to start from a point you saved, for a page that was closed while payments arrived\:

```ts
mayarin.feed.stream({ since: lastSeenOccurredAt });
```

`onStatus` tells you `open` once the connection is live and `reconnecting` after a drop, so an overlay can show that it is waiting.

The sequence ends when you abort the signal. It **throws** a `MayarinApiError` for a failure a retry cannot fix, such as a revoked key (`401`) or a key that may not read the feed (`403`). A server that is down, a network failure and a `429` are retried.

## Limits

* A merchant may hold **5 streams open** at once. A sixth is refused with a `429`, and the SDK keeps retrying it until a slot frees.
* Only an alert key may be sent in the URL (`?key=`). Use that when you hand a plain URL to a tool that cannot set a header; use the SDK everywhere else.
* Treat `data.metadata` as text a stranger typed\: escape it before it goes in a page, and do not let it decide anything about money.

The wire format is in the [API reference](/api-reference/payments), and a walk-through of an overlay is in [Livestream alerts](/guides/livestream-alerts).
