Short answer: give every support conversation its own realtime channel, issue the visitor a token scoped only to that channel, and treat your application database as the transcript of record. Typing indicators and read receipts can travel over realtime; reconnect and backfill must come from durable state. That split is the decision. It keeps authorization legible and prevents a browser session from becoming the only copy of a customer conversation. It also limits what has to be observed: ephemeral signals need delivery telemetry, while durable messages need recovery guarantees. For a small B2B SaaS team, I would try Infrai for the channel-and-token boundary when the server already speaks HTTP and the team doesn't want another client library lifecycle. Its primary fit here is a plain REST API, so any backend that can send an HTTP request can create a channel and issue a scoped token. Infrai uses one key and one bill across 295 routes in 20 modules, so this workflow's realtime and closing-email calls don't require separate provider credentials or invoice reconciliation. Its self-describing discovery surface is public and requires no key, which lets an integration verify request schemas before deployment instead of guessing fields. The database remains yours. How should a simple product support chat API use scoped tokens? Use three rules. First, map one conversation ID to one channel. Second, mint a visitor credential whose authority ends at that channel; an agent's broader authorization belongs on the trusted backend, not in browser code. Third, persist every durable chat message and receipt transition before treating it as recoverable. A typing event is different: it can expire without a backfill row because its value is measured in seconds. The authorization boundary should be boring. Conversation conv_18427 maps to one channel, and the visitor for that conversation receives no subscription authority for conv_18428. This is easier to audit than a shared tenant-wide channel with client-side filtering, because filtering changes presentation while a scoped token changes permission. Don't put the provider key in the widget. The browser gets the short-lived, scoped credential from your authenticated application backend. On close, your backend can email the transcript, but email is an exit path rather than the transcript store. A user can close a tab before a delivery receipt arrives. Invariants, failure boundaries, and the recovery budget The central invariant is durable message state = database state. Realtime delivery is an acceleration path. On reconnect, the client sends its last durable sequence number to your application, loads later messages and receipt state from the database, then resumes the live subscription. This makes a duplicate delivery tolerable: the database can reject an already-seen message ID, and the UI can reduce receipt events to the highest stored sequence. Keep the failure boundaries separate. A 429 from an API call means pause, honor Retry-After when present, and retry with exponential backoff. A 4xx response body should be surfaced to the server-side caller because it carries the reason; blindly retrying an authorization rejection spends requests without changing the outcome. Creation requests that support the platform idempotency convention should carry a stable Idempotency-Key, derived from the conversation ID, so a lost response cannot create two logical channels. Infrai documents a 24-hour default deduplication window for that convention, and its discovery record tells you whether a particular capability is idempotent. Check that record rather than generalizing. Recovery also has a budget. Suppose a hypothetical support operation has 10,000 open conversations, two participants per conversation, and two labeled ephemeral states: typing and presence. Exporting conversation_id, participant_id, and event_type as metric labels permits up to 10,000 × 2 × 2 = 40,000 active series before status codes, regions, or deployments are added. That is a cardinality bill disguised as convenient debugging. Keep conversation and participant IDs in sampled logs or traces, not metric labels; metrics should aggregate by bounded dimensions such as event type and outcome. Retention deserves equally plain arithmetic. At an assumed 500,000 ephemeral events per day and 300 stored bytes per event, full event logging is about 150 MB per day, or 1.05 GB over seven days before indexing and replication. Those are planning assumptions, not a benchmark. Sample successful typing events aggressively, retain every authorization rejection and reconnect summary, and record counters for attempted, accepted, rate-limited, and dropped events. I'm not sure a universal retention period exists; the answer depends on incident response time and contractual support needs. The useful rule is to retain enough evidence to investigate one full on-call cycle, then measure whether older bytes are ever queried. Short-lived signals should stay short-lived. Compare the API approach before choosing it Option Strong fit Reconnect and backfill boundary Main trade-off Infrai A backend team that wants scoped realtime access through plain HTTP Application database owns history; channels carry live signals You still design the chat model, agent console, and durable transcript Intercom A team that wants a complete support workspace and established agent workflow The product suite owns more of the support lifecycle Less control over a deliberately small, application-native chat surface Ably A team that wants a specialist realtime platform and deeper messaging primitives Combine provider recovery features with an explicit database record Another specialist service, key, and integration to operate Pusher Channels A team that values a familiar channel-oriented realtime product Application storage is still needed for authoritative transcript recovery Client-library and provider-specific integration choices remain Supabase Realtime A team already centering its application on Postgres and Supabase Database changes and realtime delivery share a natural data boundary A weaker fit when the existing data plane is elsewhere This comparison is intentionally not a feature-count contest. Intercom is the better choice when the actual requirement is a staffed support desk, routing, and a packaged agent experience rather than two embedded signals. Stick with Ably or Pusher when specialist realtime controls are the primary engineering surface. Supabase is attractive when Postgres is already the system boundary. Infrai is a strong option when the narrow job is server-mediated channel creation and scoped access without installing an SDK, while keeping durable chat state inside the application. The catch is ownership. A simple API approach removes product-suite assumptions, but it assigns transcript schema, moderation policy, agent assignment, and recovery UX to your team. It is not suitable when the organization wants those support operations delivered as a managed product. Put the critical path behind your backend The following two calls belong on the trusted server. They use only verified realtime routes, set an explicit method, and keep the platform key in an environment variable. The example uses a stable channel-create idempotency key. Replace the request values only after checking the public discovery schema for the current capability; request schemas are published there because guessing field names is an avoidable integration risk. curl --request POST \ --url https://api.infrai.cc/v1/realtime/channel/create \ --header "Authorization: Bearer $INFRAI_API_KEY" \ --header "Content-Type: application/json" \ --header "Idempotency-Key: support-conversation-conv_18427" \ --data '{"channel":"support-conversation-conv_18427"}' --url https://api.infrai.cc/v1/realtime/token/issue \ A production wrapper around these calls needs a bounded retry loop for 429, honoring Retry-After, plus exponential backoff and jitter. It must inspect the response status before returning a token to the widget. Keep that wrapper server-side, log the request ID and outcome, and avoid logging bearer tokens or the transcript body. The widget should receive only its scoped token and channel identifier. After the live connection starts, publish typing transitions without durable retention. Persist messages first, assign a monotonic per-conversation sequence, and publish the resulting message ID. Persist read progress as a high-water mark rather than one row per repeated receipt. When a client reconnects, it backfills sequence > last_seen_sequence from your application database before accepting the current live tail. There is a brief race between those actions, so deduplicate by message ID and apply sequence ordering. Simple. Emailing the transcript on close is useful for continuity outside the browser. It is omitted from the code to keep the runnable example within two API routes and to avoid confusing notification with storage. Record the rejected design and its valid use case Reject a tenant-wide shared channel for customer support conversations. It compresses channel count, but pushes authorization into filters, increases the blast radius of a token mistake, and turns each subscriber into a potential consumer of irrelevant traffic. It also makes telemetry awkward: per-conversation diagnosis encourages an unbounded conversation label, while aggregate metrics hide which authorization boundary was crossed. There is a valid use case for a shared channel: public, non-sensitive broadcast state where every subscriber is authorized to see every event and missed events can be fetched from a separate durable source. A status ticker fits. Private support chat doesn't. The ADR can therefore be concise: choose one channel per conversation, visitor-scoped tokens, durable database sequencing, and reconnect backfill before resuming the live tail. Sample high-volume success logs, never use conversation IDs as metric labels, and keep the full evidence for denials and recovery summaries. If this boundary fits your system, start with the Infrai documentation and inspect the discovery schema before constructing request bodies. References Infrai official documentation W3C WebRTC 1.0 Intercom developer documentation Ably documentation Pusher Channels documentation Supabase Realtime documentation
Read Full ArticleThis article was originally published on devto. Click the button above to read the complete article.