From c6e4fd563886c28e6aea39fb76a4c78d2d2d1be3 Mon Sep 17 00:00:00 2001 From: Bufo Date: Fri, 21 Aug 2026 17:05:13 +0200 Subject: [PATCH] docs: document streaming client methods [AMB-3018] --- AGENTS.md | 4 ++-- docs/INTEGRATION.md | 40 ++++++++++++++++++++++++++++++++++++- packages/payments/README.md | 37 ++++++++++++++++++++++++++++++++++ 3 files changed, 78 insertions(+), 3 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index e51ffde..2712ec2 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -70,8 +70,8 @@ Resource getters are lazy and call `requireServiceApiKey`: | Getter | Class | Operations | | --------------- | -------------- | ------------------------------------------------------------- | | `.environments` | `Environments` | `list()`, `get(id)`, `create(input)`, `delete(id)` | -| `.wallets` | `Wallets` | `list({ environmentId })`, `get(id)`, `create(input)`, `delete(id)` | -| `.transactions` | `Transactions` | `createReceive(input)`, `send(params)`, `prepareSend(params)`, `isSendReady(walletId)`, `forgetSend(walletId)` | +| `.wallets` | `Wallets` | `list({ environmentId })`, `get(id)`, `create(input)`, `delete(id)`, `watchEvents(id)` | +| `.transactions` | `Transactions` | `createReceive(input)`, `send(params)`, `prepareSend(params)`, `isSendReady(walletId)`, `forgetSend(walletId)`, `watch(id)` | | `.webhooks` | `Webhooks` | `verify(input)` — does NOT require any API key | `Payments.webhooks` is also a static reference to `Webhooks` for stateless use. diff --git a/docs/INTEGRATION.md b/docs/INTEGRATION.md index 9670b58..f44eb71 100644 --- a/docs/INTEGRATION.md +++ b/docs/INTEGRATION.md @@ -11,6 +11,9 @@ this single file to an AI coding agent as the spec for your integration. - **Send** — pay BOLT11 invoices or Lightning addresses from your wallets. - **Webhooks** — signed `payment.pending` / `payment.completed` / `payment.failed` events pushed to your endpoint. +- **Streaming (SSE)** — a live third option when you can't expose a public + endpoint: `transactions.watch` / `wallets.watchEvents` stream the same + events over a plain `fetch`-based connection. - **Sandbox environments** — test the full flow with no real money and no Lightning node. @@ -98,7 +101,7 @@ transaction.payment_request; // BOLT11 invoice — show this to the payer (QR/li transaction.payment_hash; // correlate with the webhook event later ``` -Do not poll for settlement — consume the `payment.completed` webhook (Step 5). +Do not poll for settlement — consume the `payment.completed` webhook (Step 5) or stream live status (Step 6). ## Step 4 — Send a payment @@ -293,6 +296,41 @@ Verification failures throw `WebhookVerificationError` with a typed `code` [package README](../packages/payments/README.md#webhook-error-codes) for the full table. +## Step 6 — Or stream live status (SSE) + +Webhooks require a publicly reachable endpoint. If you don't want to stand one +up — or don't want to poll for settlement either — `transactions.watch` and +`wallets.watchEvents` give you a live third option: a plain `fetch`-based SSE +connection that yields the same `PaymentEvent`s a webhook would push, opened +directly from your process. + +```ts +for await (const event of payments.transactions.watch(transaction.id)) { + if (event.event_type === 'payment.completed') { + // fulfill the order; correlate via event.data.payment_details.payment_hash + } +} +``` + +The transaction stream ends on its own once the transaction reaches a +terminal status (`COMPLETED` / `FAILED` / `EXPIRED`), or after a 30-minute max +stream lifetime — whichever comes first. `wallets.watchEvents(walletId)` is +the wallet-scoped counterpart: since a wallet has no terminal state, that +stream stays open for the full 30 minutes (or until the connection drops), +observing every transaction on the wallet. Reconnect by calling either method +again — it mints a fresh stream token internally, no manual auth handling +required. + +```ts +for await (const event of payments.wallets.watchEvents(walletId)) { + console.log(event.event_type, event.data.status); +} +``` + +Pass `{ signal }` to abort early. Errors follow the same pattern as every +other call: `ApiError` before the stream opens (bad/expired token, unknown +id), `NetworkError` for a transport failure or an aborted signal. + ## Error handling Every API call can throw one of three typed errors: diff --git a/packages/payments/README.md b/packages/payments/README.md index 5c8d1c0..f55edd0 100644 --- a/packages/payments/README.md +++ b/packages/payments/README.md @@ -282,6 +282,43 @@ Notes: - Sandbox wallets prepare too (no password, nothing to decrypt) — it just caches the fact that no node payment is needed. +### Streaming + +`transactions.watch(id)` and `wallets.watchEvents(id)` stream live `PaymentEvent`s +over SSE — a live alternative to webhooks when polling for status or exposing a +public endpoint isn't an option. Both mint a short-lived stream token via +GraphQL, then read `GET /payments/stream/{transactions,wallets}/:id` as +`text/event-stream` using plain `fetch` (not `EventSource`), so they work in +Node and browsers alike. + +```ts +for await (const event of payments.transactions.watch(transactionId)) { + console.log(event.event_type, event.data.status); +} +``` + +Each yielded `event` is a `PaymentEvent` — the same shape delivered to a +verified webhook (see [Event shape](../../docs/INTEGRATION.md#event-shape)). + +`transactions.watch` ends (the loop exits normally) once the transaction +reaches a terminal status (`COMPLETED` / `FAILED` / `EXPIRED`) or the stream's +30-minute max lifetime elapses. `wallets.watchEvents` has no terminal +state — it stays open until the 30-minute max lifetime elapses or the +connection drops (the stream token itself only gates the initial handshake, +not how long the connection stays open): + +```ts +for await (const event of payments.wallets.watchEvents(walletId)) { + console.log(event.wallet_id, event.event_type); +} +``` + +Call either method again (it mints a fresh token) to reconnect. Both accept an +optional `{ signal }` to abort the connection. Rejects with `ApiError` before +the stream opens (401 missing/invalid token, 404 unknown id) or `NetworkError` +for a transport failure or an aborted signal — the same error types every +other resource method throws. + ## Examples Runnable scripts live in [`examples/`](./examples). They run against a live API