Skip to content

Behavior & limits

How the SDK behaves at runtime — worth understanding before you ship.

Message delivery (polling)

There is no WebSocket. The SDK polls the backend (default every 4s, pollingInterval). When a poll fails (network/timeout/server unavailable) the interval backs off exponentially (4s → 8s → 16s → max 30s) and resets to the base on the next success. Tickets are polled less often (~every 20s) since they change rarely; webhooks cover changes in between.

Message statuses

User messages show delivery ticks:

TickMeaning
🕓sending
sent / delivered to the backend
✓✓ (tinted)seen — an operator replied after this message
⚠ (red)failed — the send didn't go through (tap to retry)

"Seen" and "typing" caveat

Intercom's API and webhooks expose no real read-receipt or "operator typing" signal, and the architecture forbids WebSocket. So:

  • sent / delivered are real (the backend accepted the message).
  • seen is inferred honestly: a message is "seen" once an operator message follows it.
  • A live typing indicator is not available. (Mock mode simulates one for the showcase only.)

Error states

The SDK never shows a blank screen. It renders a localized panel with a recovery action for:

  • Session expired (401) → calls onSessionExpired, offers re-login.
  • Offline (web: navigator.onLine) → auto-recovers on reconnect.
  • Support unavailable (backend/Intercom down) → retry.

Resilience (backend)

  • Circuit breaker around Intercom: after 3 consecutive outages it opens for 30s and fails fast (502), then probes with one trial request.
  • Rate limit: per-user limit on /api/* (429 + Retry-After).
  • Read coalescing cache: concurrent polls (and a user's multiple devices) share one Intercom call.

Scale

Intercom allows ~10k API calls/min per app. With 4s polling that's ~15 calls/min per active user — roughly a 660 concurrent-user ceiling before the caching and ticket-poll decoupling matter. Beyond a single backend instance, move the read cache and rate-limit store to Redis (see Deployment).

Request lifecycle

The user sees one continuous thread, but under the hood each support request is its own Intercom conversation with a state (open / closed / snoozed).

  1. No open request → the user first picks a topic (admin-managed main → sub "reasons"). Picking a leaf topic starts the request immediately — no typed message — and the thread opens with the operator acknowledgement.
  2. Open request → the normal composer; messages reply into that conversation.
  3. Closed (by the operator, or by the user via the close button → POST /api/conversations/:id/close) → a "request closed" line appears with an inline CSAT rating. Submitting tags the conversation CSAT, posts a note, and stores the stars — which persist and show on reopen.
  4. A new request after a close appends below the previous (closed) messages — the history stays; it's still one thread.

Live state (no reload)

The message poll also returns the conversation's current state / rated, so when an operator closes or reopens a request, the UI reflects it within one poll interval (~4s) without a reload or WebSocket.

Single thread, many tickets

There is one ongoing support thread per user (made of sequential requests), and any number of tickets. Replying to a resolved ticket reopens it automatically.

AW Chat SDK — internal integration docs.