Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
40 changes: 39 additions & 1 deletion docs/INTEGRATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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:
Expand Down
37 changes: 37 additions & 0 deletions packages/payments/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading