# Web / API Access (WebChat) Guide MateClaw's WebChat channel lets external websites reach the conversation engine over plain HTTP / SSE, with no JWT. Visitor identity is isolated under a shared API Key via `visitorId + visitorToken` (HMAC-signed). There are two integration paths: - **Embeddable widget** — drop in one JS file, call `init(...)` once, and a chat bubble appears in the corner. Fastest to ship; ideal for marketing sites / landing-page support. - **Custom HTTP / SSE integration** — call the REST + SSE endpoints below and render your own UI. For deeply customized experiences. ## Embeddable widget (mateclaw-webchat) The widget is a zero-dependency browser library shipped in both UMD (` ``` **Option 2: npm (ESM)** ```bash npm install @mateclaw/webchat ``` ```ts import { init } from '@mateclaw/webchat' init({ apiKey: 'your-channel-api-key', server: 'https://' }) ``` **Config options** | Field | Required | Default | Notes | |---|---|---|---| | `apiKey` | yes | — | Channel API Key | | `server` | yes | — | MateClaw server URL (no trailing slash) | | `position` | no | `bottom-right` | Bubble position: `bottom-right` / `bottom-left` | | `primaryColor` | no | `#D97757` | Primary color (any CSS color) | | `title` | no | `MateClaw` | Panel title | | `placeholder` | no | `Type a message...` | Input placeholder | **Behavior** - The visitor ID is generated on first open and persisted in `localStorage` (key `mc-webchat-visitor`), then reused — you don't manage it yourself. - The panel is themed entirely through CSS variables (`--mc-primary` / `--mc-bg-elevated` / ...); the host page can override them under `:root`. - The widget consumes the `/stream` SSE protocol described below. For richer interactions (session list, attachments, revocation), call the HTTP endpoints directly and build your own UI. ## Custom integration: basics - **Base URL**: `https:///api/v1/channels/webchat` - **Auth**: every endpoint requires the header `X-MC-Key: ` (from the channel edit page). - **Session-management endpoints** additionally require `X-MC-Visitor-Token: ` (issued by the server and returned on the first `/stream` call). - **Response envelope**: `R` → `{"code": 200, "msg": "...", "data": T}`; anything other than 200 is an error. - **Charset**: UTF-8. The SSE stream uses `text/event-stream; charset=UTF-8`. ## Endpoint list | Method | Path | Auth | Purpose | |---|---|---|---| | POST | `/stream` | API Key | SSE streaming chat (issues visitorToken) | | GET | `/config` | API Key | Get channel config (title/placeholder/...) | | POST | `/sessions` | API Key | Explicitly create an empty session thread | | GET | `/sessions` | + visitorToken | List sessions (excludes archived by default) | | GET | `/sessions/page` | + visitorToken | Paginated + keyword search | | PUT | `/sessions/title` | + visitorToken | Rename | | PUT | `/sessions/pinned` | + visitorToken | Pin / unpin | | PUT | `/sessions/archive` | + visitorToken | Archive / unarchive | | DELETE | `/sessions` | + visitorToken | Delete | | POST | `/sessions/stop` | + visitorToken | Stop an in-flight stream | | POST | `/sessions/regenerate` | + visitorToken | Regenerate the last assistant reply | | POST | `/sessions/approve` | + visitorToken | Approve a pending tool approval and replay (SSE) | | POST | `/sessions/deny` | + visitorToken | Deny a pending tool approval (synchronous JSON) | | GET | `/sessions/messages` | + visitorToken | Message list (paginated) | | POST | `/upload` | + visitorToken | Upload an attachment (returns fileId) | | GET | `/files` | + visitorToken | Download a file (uploaded or agent-generated) | Admin-level (require a MateClaw JWT, outside the permitAll set above): | Method | Path | Purpose | |---|---|---| | POST | `/api/v1/admin/webchat/revoked-visitor` | Revoke a visitor's management token | | DELETE | `/api/v1/admin/webchat/revoked-visitor` | Un-revoke | > In the admin console, the "Conversations" list hides WebChat visitor sessions from regular admins by default — only a global admin sees them. This is a cross-workspace isolation and visitor-privacy guard. ## Auth flow ```text ┌──────────┐ POST /stream {visitorId:"v1", message:"hi"} │ Client │ ─────────────────────────────────────────────► ┌──────────┐ └──────────┘ │ MateClaw │ ▲ └──────────┘ │ SSE meta event: {sessionId, conversationId, visitorToken} │ SSE content_delta events: {text} │ SSE done event └───────────────────────────────────────────────────────── │ ┌──────────┐ GET /sessions X-MC-Visitor-Token: │ │ Client │ ─────────────────────────────────────────────► │ └──────────┘ ◄──── 200 {code:200, data:[...]} │ ``` `visitorToken` is valid for 7 days by default; re-issue it through any `/stream` call once it expires. Every `/stream` call (even with a still-valid token) returns a fresh token in the meta event — the client should keep updating its stored copy. ## Error codes | HTTP | When | |---|---| | 400 | Invalid parameter (visitorId / sessionId charset, title length, etc.) | | 401 | Invalid API Key / missing, expired, or revoked visitorToken | | 404 | The given sessionId does not exist or does not belong to the visitor | | 409 | More than 5 inactive empty sessions | The error message is in `R.msg` and can be shown directly to the user. ## SSE event protocol `/stream` and `/sessions/regenerate` return `text/event-stream`: ``` event: meta data: {"sessionId":"s1","conversationId":"webchat:abc123:v1:s1","visitorToken":"xxx.yyy"} event: phase data: {"phase":"planning","timestamp":1716700000000} event: tool_start data: {"tool":"web_search"} event: tool_end data: {"tool":"web_search","success":true} event: plan data: {"steps":["search the web","summarize"]} event: content_delta data: {"text":"He"} event: content_delta data: {"text":"llo"} event: thinking_delta data: {"text":"..."} (optional, reasoning trace) event: done data: {"status":"completed"} event: error data: {"message":"..."} (on failure) ``` > The SSE spec requires clients to ignore unknown event types. The server may emit internal events prefixed with an underscore (e.g. `_usage_final`); these carry no contract for visitors and can be safely ignored. ### Optional real-time progress events `phase` / `tool_start` / `tool_end` / `plan` are **optional** events — used to show an "AI is typing…" bubble, tool-execution badges ("Searching…"), or a Plan-and-Execute step checklist in your SDK. The SDK can ignore them all and still render the full reply from `content_delta` alone. | Event | Triggered when | Data fields | |---|---|---| | `phase` | the agent enters a new execution phase (planning / generating / summarizing / ...) | `phase`, `timestamp` | | `tool_start` | the agent calls a tool | `tool` (tool name) | | `tool_end` | a tool call finishes | `tool`, `success` | | `plan` | a Plan-and-Execute agent breaks work into steps | `steps` (string array) | **Note**: `tool_start` / `tool_end` carry **only the tool name**, never the call arguments or results — agent tool calls may involve PII (file paths, user queries, credentials), which would leak if forwarded to a third-party website frontend. The SDK should map tool names to localized labels (`web_search` → "Searching…"). ## File upload / download 1. `POST /upload` (multipart): returns `{fileId, fileName, contentType, size}`. 2. Add the fileId to the `attachmentIds` array in the body of the next `/stream` call. Unknown / expired / foreign fileIds are silently dropped (only the text part is sent, no error). 3. The agent reads server-side files directly; the `fileUrl` in a message is a relative download path (`/api/v1/channels/webchat/files?storedName=...`) — the client appends auth headers to download. 4. Agent-generated files (PDF/DOCX/...) appear in assistant replies as `/api/v1/files/generated/` URLs, downloadable **without auth**, with a 7-day TTL. ## Session lifecycle: pin / archive / delete - **Pin** (`PUT /sessions/pinned`): sorted first in the `/sessions` list. - **Archive** (`PUT /sessions/archive`): a soft close — the thread stays in the DB (history queryable, addressable by sessionId, files downloadable) but is hidden from `/sessions` by default (pass `includeArchived=true` to return it), and no longer counts against the "≤ 5 inactive empty sessions" quota. - **Delete** (`DELETE /sessions`): permanent, unrecoverable. Each session returned by `/sessions` includes: `sessionId`, `title`, `lastActiveTime`, `messageCount`, `pinned`, `archived`, `streamStatus` (`running` / `idle`). ## Tool approval resolve (API-Key channel) When an agent bound to WebChat calls a tool protected by [Tool Guard](./security), that turn **suspends waiting for approval**. The visitor can approve or deny it in-session instead of letting it time out. - **Approve** `POST /sessions/approve` — with `sessionId` + `pendingId`. Auth reuses visitorToken + conversation ownership; the `pendingId` is **strictly validated to belong to this session** (else 404), closing a cross-visitor IDOR. Approving **replays** the suspended tool call and resumes as SSE. - **Deny** `POST /sessions/deny` — with `sessionId` + `pendingId`, returns synchronous JSON, no replay. Both broadcast a `tool_approval_resolved` SSE event (see [Optional realtime progress events](#optional-realtime-progress-events) above) so the SDK / frontend clears the approval banner in real time. > Whether an approval appears depends on whether the agent's bound Tool Guard rules set `require_approval` for some tool. Get `pendingId` from the `tool_approval_requested` event. ```bash # Approve (resumes as SSE) curl -N -X POST "https://mate.example.com/api/v1/channels/webchat/sessions/approve" \ -H "X-MC-Key: " -H "X-Visitor-Token: " \ -H "Content-Type: application/json" \ -d '{"sessionId":"s1","pendingId":""}' # Deny (synchronous JSON) curl -X POST "https://mate.example.com/api/v1/channels/webchat/sessions/deny" \ -H "X-MC-Key: " -H "X-Visitor-Token: " \ -H "Content-Type: application/json" \ -d '{"sessionId":"s1","pendingId":""}' ``` ## visitorToken revocation (admin) A visitor abusing the channel? An admin calls: ```bash curl -X POST https://mate.example.com/api/v1/admin/webchat/revoked-visitor \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{"channelId":123, "visitorId":"v1", "reason":"abuse"}' ``` After revocation, all of that visitor's management endpoints return 401 (`/stream` is unaffected and can re-issue a fresh token). Revocation state is briefly cached, so under a multi-instance deployment it takes up to ~10 minutes to fully propagate. Un-revoke via `DELETE` on the same endpoint. ## curl examples **Step 1: send the first message** ```bash curl -N -X POST https://mate.example.com/api/v1/channels/webchat/stream \ -H "X-MC-Key: your-api-key" \ -H "Content-Type: application/json" \ -d '{"visitorId":"v1","message":"hi"}' ``` Save the `visitorToken` and `sessionId` from the meta event. **Step 2: list sessions** ```bash curl https://mate.example.com/api/v1/channels/webchat/sessions?visitorId=v1 \ -H "X-MC-Key: your-api-key" \ -H "X-MC-Visitor-Token: " ``` **Step 3: upload an attachment and send** ```bash # upload curl -X POST https://mate.example.com/api/v1/channels/webchat/upload \ -H "X-MC-Key: your-api-key" \ -H "X-MC-Visitor-Token: " \ -F "visitorId=v1" \ -F "file=@report.pdf" # returns {"fileId":"abc-uuid", ...} # send a message with the attachment curl -N -X POST https://mate.example.com/api/v1/channels/webchat/stream \ -H "X-MC-Key: your-api-key" \ -H "Content-Type: application/json" \ -d '{"visitorId":"v1","sessionId":"","message":"take a look at this report","attachmentIds":["abc-uuid"]}' ``` ## Limits - Inactive empty sessions per visitor ≤ 5 (creation is rejected past 5 — send a message or delete an old session first) - Upload: single file ≤ configured cap, extension + MIME dual whitelist; ≤ 50 files / 200 MB per session (configurable) - visitorToken expires in 7 days; agent-generated file URLs have a 7-day TTL - Currently a single-instance deployment (staging registry + streamTracker are both in-memory). Multi-instance support is on the roadmap. ## Related - Upstream epic issue: https://github.com/mateaix/mateclaw/issues/355