Tab Closed Mid-Stream: Billing for Live AI Replies
§1 Minute 1: Separate delivered tokens from billable usage
A user starts a live AI reply, reads a few sentences, and closes the tab. Does billing stop immediately? Not necessarily. Closing the browser connection does not automatically cancel generation upstream, and the text displayed on screen is not a reliable measure of billable tokens.
There are three separate events: the browser disconnects, your backend detects that disconnect, and the upstream API receives a cancellation. Tokens may be generated between those events. Your first job is to connect them; your second is to record enough information to explain the resulting charge.
Treat cancellation as a request to stop future work, not a refund for completed work. Input processing and output already generated may remain billable under the provider's rules. Exact treatment depends on the service and model, so do not promise that closing a tab makes a request free.
§2 Minute 2: Put the token stream behind your backend
Use a simple path: browser, application backend, streaming AI API. Keep the API key on the backend. Give each generation an application request ID before opening the upstream stream, and associate it with the authenticated user.
Fluxio fits this setup when you want streaming-first, pay-per-token access through one API key. OpenAI-style clients use https://api.fluxioapi.com/v1 as the base URL. You can select a supported model such as Claude Haiku 4.5 and forward incoming text to the browser as SSE events.
For this quick start, keep one upstream request per live reply. Avoid automatic retries after a disconnect: a retry can create another billable generation after the user has already left. If your product intentionally supports background generation, make that a separate, explicit behavior.
§3 Minute 3: Wire browser disconnects to upstream cancellation
Create an AbortController for each upstream request and pass its signal through your HTTP client or SDK's request options. On a confirmed downstream disconnect, call abort on that controller. Also stop your forwarding loop and release stream resources.
- – Connect the chat interface's Stop button to cancellation, rather than merely hiding incoming text.
- – For a Node HTTP backend, monitor the response close event and check whether normal response completion has already occurred before classifying it as abandonment.
- – Do not treat the incoming request body's close event alone as proof that the browser abandoned a streamed response.
- – Verify that your SDK actually forwards cancellation to its underlying network request.
A tab-close notification is not dependable cleanup. Browser lifecycle callbacks can be skipped, and network failures may take time to detect. Use server-side connection signals plus a maximum generation duration and output-token limit as backstops. Check proxy buffering and disconnect propagation, too.
§4 Minute 4: Record usage without inventing missing totals
Request streaming usage information where the API and selected model support it. Some streaming APIs provide totals near the end, but an interrupted connection may never deliver that final event. Missing usage does not mean zero usage.
Store a small request ledger with these fields:
- – The application request ID, upstream request ID when available, user ID, and selected model.
- – The start time, disconnect time, cancellation time, and final lifecycle status.
- – The reported input and output token counts, with an explicit unknown value when unavailable.
- – A reconciliation status for checking later usage records or billing exports, if available.
Do not calculate authoritative charges from SSE event counts, text chunks, or character lengths. A chunk is not necessarily a token. If you show a live estimate, label it as an estimate and keep it distinct from settled usage.
§5 Minute 5: Test abandoned SSE replies and explain charges
Start a long response, close the tab halfway through, and inspect your logs. Confirm that the backend detects the disconnect, sends cancellation, exits the forwarding loop, and does not retry. Repeat with a dropped network connection and the Stop button. Record cancellation latency, not just whether cancellation happened.
Use clear billing copy: “Closing a reply requests cancellation. Usage incurred before cancellation takes effect may still be charged.” If final usage is unavailable, show a pending state rather than a fabricated total. This keeps your real-time chat experience honest while your ledger catches up.
Sign up for Fluxio to build your streaming workflow.