GET that receives every
event as it happens, with guaranteed replay if the connection drops.
Use the stream to keep a dashboard live (balances, payins landing, payouts
settling, card authorizations, KYT alerts for admins). Use
webhooks for anything that must survive your browser being
closed — the two channels carry the same events with the same payloads and
the same event_id, so you can consume both without writing two mappings.
Open the stream
The endpoint requires the sameAuthorization: Bearer credential as the rest
of the API, so the browser’s native EventSource (which cannot send headers)
is not an option. Use fetch with a streaming reader:
Response
Reconnect without gaps
If the connection drops, reconnect sending the last cursor you processed. The server replays everything you missed from the log before switching back to live, so a flaky network never loses an event.The replay is capped at 1,000 events. If you were offline long enough to
miss more, the stream emits a
replay_truncated control event and you should
reconcile with ?snapshot=true or with
GET /v1/events/history instead of assuming continuity.Initial snapshot
Opening with?snapshot=true sends the current state first, then the
deltas. It removes the classic race of “read the REST endpoints, then
subscribe, and lose whatever happened in between”: the cursor is taken after
the subscription is open, so nothing falls through the crack.
balances for an account credential (same fields as
GET /v1/balances) and, for an organization admin,
the operational health counters.
Filter by event type
?types= narrows what you receive. It can only restrict what your
credential already sees, never widen it. An unknown type is rejected with
400 invalid_event_type instead of silently returning nothing.
Scope: account vs organization admin
An account never sees another account’s events, and never sees the org-wide
compliance surface. The stream never delivers a field that the same credential
could not already read over REST.
Control events
Besides your business events, the stream emits protocol events. They carry noid: (except snapshot), so they never move your replay cursor.
A
: ping comment arrives every 20 seconds to keep proxies from closing an
idle connection — ignore lines starting with :.
Limits
Exceeding the concurrency limit returns
429 too_many_streams. Exceeding the
openings quota returns 429 rate_limited — that one counts attempts, so a
client reconnecting in a tight loop burns it even with no stream open. Always
honour the retry: hint (3 s) plus exponential backoff.
Queryable history
The same log that feeds the stream is readable over REST — useful for auditing, for a “what did I miss” view, or when the replay was truncated.Errors
Full list in Errors.
FAQ
Should I replace webhooks with the stream?
Should I replace webhooks with the stream?
No. The stream lives as long as the browser tab; webhooks reach your
backend even when nobody is looking. Use the stream for the UI and
webhooks for anything that triggers business logic (reconciliation,
accounting, notifications).
Can I get the same event twice?
Can I get the same event twice?
Yes — after a reconnect the replay may re-deliver the boundary event, and
both channels (webhook and stream) share the same
event_id. Deduplicate
by event_id and treat every payload as absolute state.Why does my connection close after 30 minutes?
Why does my connection close after 30 minutes?
By design. A stream with no lifetime cap hides leaked connections. The
server sends a
reconnect control event first, and reconnecting with
Last-Event-ID continues exactly where you were.Do I need a subscription like with webhooks?
Do I need a subscription like with webhooks?
No. The stream needs no configuration: it delivers everything your
credential is allowed to see. Webhook subscriptions only control HTTP
deliveries to your server.
Nothing arrives, not even the heartbeat.
Nothing arrives, not even the heartbeat.
Check that your HTTP client is not buffering the response (in
fetch,
read res.body as a stream instead of awaiting res.text()), and that no
proxy of yours is buffering text/event-stream. CBPay already disables
buffering on its side.Can an organization admin stream a single account?
Can an organization admin stream a single account?
Yes, with
?account_id=. The filter only works inside your own
organization; anything else returns 404.