> ## Documentation Index
> Fetch the complete documentation index at: https://docs.stateset.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Widget Authentication and Conversation Recovery

> Separate widget identity from conversation access, reconcile interrupted sends, and control browser transcript retention.

A storefront can share a public widget identity across visitors while keeping every
conversation behind its own capability. The reviewed `response-chat-widget` backend enforces
these independently. Custom backends must implement their own equivalent authorization.

## Credentials and ownership

| Credential | Sent as | Authority |
| - | - | - |
| Signed widget token | `x-widget-token` (`widgetAuthToken`) | Verified org/agent and optional site identity |
| Thread capability | `x-widget-thread-token` | Access to one conversation; required once issued |
| Operator key | `Authorization: Bearer rw_admin_...` | Tenant-scoped administration and native operator access |
| Metrics token | `x-metrics-token` | Metrics scrape, when configured |

Widget signatures use `STATESET_WIDGET_AUTH_SECRET`. Tokens have either
`orgId.agentId.timestamp.signature` or `orgId.agentId.timestamp.siteId.signature`; the site
claim is signed and UUID-shaped. Tenant hints in headers or request metadata must agree with
verified claims; they cannot grant ownership. The [public session issuer](https://docs.stateset.com/api-reference/chat-widget/settings/api-widget-session-create.md)
binds those claims to server configuration and checks an exact allowed Origin.
That identifies the deployment, not the visitor's identity.

When a conversation is created, its response discloses `threadToken`, either in JSON or a
leading SSE `thread` event. Default widget requests store and echo it for chat, history,
mutations and feedback. An org-wide widget token does not grant access to another visitor's
thread. For this server, `STATESET_THREAD_TOKEN_REQUIRED=true` also rejects legacy threads
that never had capabilities; it does not recover missing capabilities. The separate hosted
ResponseCX service uses its own enforcement settings.

A browser storage failure falls back to memory for the current page. It does not authorize a
later page load that lost a protected conversation's token. Custom `buildThreadRequest`
overrides own the full request and must supply required headers.

## Preserve message identity on retries

The current widget creates a stable `clientMessageId` for a send and reuses it on recovery.
With a session ID (or thread-token header as the execution scope), the server caches completed
logical replies for ten minutes, subject to a 256 KiB captured-response limit. JSON and completed SSE can replay with
`X-Idempotent-Replay: true`, without executing the action again. Cached access to an existing
thread is reauthorized; a session ID alone is not a capability. Oversized or incomplete replies
are not reusable cached successes, even if an external action already happened.

For a custom client, preserve both IDs for the same intentional send:

```json theme={null}
{
  "sessionId": "visitor_session_001",
  "clientMessageId": "visitor_message_001",
  "threadId": "thr_existing_conversation",
  "message": "Can you check the shipment status?",
  "stream": false
}
```

| Result | Meaning | Client action |
| - | - | - |
| `409 idempotency_conflict` | Same ID reused with different content or operation | Correct client identity handling; do not blindly resend |
| `409 message_in_progress` | That send is still active | Wait, then reconcile the existing operation |
| `409 thread_busy` | Another thread mutation holds the lease | Wait for the turn and refresh the conversation |
| `409 message_outcome_unknown` | Execution may have occurred without a complete reusable response | Read the conversation and reconcile effects before a new action |
| `403 thread_forbidden` | Missing capability or ownership mismatch | Restore authorized context; do not substitute another visitor's token |
| `429 rate_limited` | Request budget exceeded | Honor `Retry-After` and keep the same logical send identity |

A failed stream is not proof that a commerce action failed. Use **Check for a reply** to refresh
without resending. To inspect the authorized thread directly, populate these environment
variables from the same visitor session and issue a read:

```bash theme={null}
curl --fail-with-body "$WIDGET_API_URL/api/threads/$THREAD_ID" \
  -H "x-widget-token: $WIDGET_TOKEN" \
  -H "x-widget-thread-token: $THREAD_TOKEN"
```

> [!NOTE]
>
> A timeout or interrupted connection does not prove that the server or a commerce provider
> rejected the action. Preserve its identity and reconcile the stored result first.

Stop/cancellation ends the client's wait; it cannot undo accepted server or
external work. For a new intentional action, use a new message ID. The ten-minute replay cache
and distributed leases do not make external provider or commerce systems transactional.

For replay, a stream needs a terminated `data: [DONE]` event and no error events. A quoted
marker, a comment or an unfinished frame cannot establish completion. The UI may accept a
custom stream's natural end; that does not make an incomplete server execution replayable.

## Timeouts and conversation changes

Current browser limits cover response bodies as well as connection setup:

| Operation | Limit |
| - | - |
| JSON chat response | 30 seconds |
| Streaming inactivity | 60 seconds |
| Total stream lifetime | Five minutes |
| Thread create/read/update/delete | 15 seconds |
| Send preparation | 30 seconds |

Partial output remains marked interrupted. Starting or selecting another conversation,
forgetting it, or deleting the current thread cancels obsolete reads and sends so late replies
cannot restore the discarded transcript. Failed deletion preserves the transcript and capability.

## Browser privacy and retention

Set `settings.security.retentionDays` to expire inactive browser transcripts before restoring
them. Omit it to retain existing behavior, or use `0` to disable transcript and capability
persistence. `settings.session.persistThreadTokens: false` keeps capabilities in memory.
Session storage can be selected through `sessionConfig.storage` or the session settings alias.

**Forget this conversation on this device** clears its local transcript, saved session and
capability. It does not erase server reporting, Redis state, helpdesk tickets or backups.
Thread deletion and those systems' retention policies are separate operations. Persisted
attachment labels do not contain file bytes or temporary preview URLs.

## When a send looks lost

Check the request ID, thread ID, reply format and capability headers first. Avoid logging tokens
or attachment contents. Refresh the same thread before creating another conversation. Confirm
readiness on every API replica and shared Redis coordination before treating repeated conflicts
as a browser problem. For a human-controlled thread, check the active operator claim and
customer polling as described in [native live chat](https://docs.stateset.com/stateset-response/response-chat-widget-live-chat.md).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.