Matrix Private Support Chat Plan
Summary
Add a lightweight landing-page support chat widget for apps/webdevelop-pro that uses Matrix as the staff-side communication channel. The website must not embed a full Matrix client and must not expose Matrix bot, admin, or appservice credentials in the browser.
The chosen architecture is private support rooms:
- The visitor chats with a small website widget.
- The widget talks only to a backend support-chat API.
- The backend creates one private Matrix room per website conversation.
- Staff reply from Matrix; replies are relayed back to the visitor through the backend.
This replaces the earlier public-channel direction. The independent critique was correct: a public Matrix room is a poor support-chat default because it leaks support conversations into shared room history, complicates privacy expectations, and makes abuse control harder.
Research basis:
- Matrix Client-Server API supports room creation, message sending, sync, membership, and room state.
- Matrix rooms can be private, invite-only, and non-federated.
- Synapse open registration without verification is a spam and abuse risk.
- Hosted Matrix clients such as Element are not a good website widget fit; a backend relay is the safer integration boundary.
Useful references:
- Matrix Client-Server API: https://spec.matrix.org/latest/client-server-api/
- Synapse configuration manual: https://matrix-org.github.io/synapse/latest/usage/configuration/config_documentation.html
- matrix-bot-sdk intro: https://matrix.org/docs/older/matrix-bot-sdk-intro/
Key Changes
Frontend
Add a new minimal Vue 3 widget in apps/webdevelop-pro.
Default placement:
- Floating bottom-right widget.
- Landing page
/only. - Wrapped in
ClientOnly. - Lazy-loaded after the page is interactive or after first user intent.
Feature flags:
VITE_MATRIX_CHAT_ENABLED=trueVITE_MATRIX_CHAT_API_URL=https://matrix-chat.webdevelop.pro
Do not reuse the existing @webdevelop-pro/ui-kit/chat/ChatWidget transport for this feature. That widget is tied to VITE_CHATBOT_URL and a streaming AI chatbot API. Reusing visual pieces is acceptable only if it does not keep the old chatbot contract.
Backend
Add a new backend service, recommended path/name:
apps/matrix-support-api
Recommended stack:
- Node.js + TypeScript
- Fastify
matrix-bot-sdk- Postgres for session/message persistence
The backend owns all Matrix access tokens and room-management logic.
DevOps
Add a new Ansible deployment role instead of mixing product-specific relay logic into the existing Matrix homeserver role:
ansible-devops/roles/matrix-support-chat
Keep ansible-devops/roles/matrix responsible for Synapse, nginx, LiveKit/MatrixRTC, and homeserver-level configuration.
Required Matrix hardening:
- Disable public registration by default.
- Disable guest access by default.
- Move Matrix secrets out of role defaults into inventory/vault.
- Pin Matrix container image versions instead of using
latest. - Add explicit Synapse rate limits for registration and messaging.
- Keep staff account provisioning separate from anonymous website traffic.
Backend Contract
Create Session
POST /v1/support-chat/sessions
Request:
{
"initialMessage": "Hello, I need help with tokenization infrastructure.",
"sourceUrl": "https://www.webdevelop.biz/",
"pageTitle": "Tokenize Real-World Assets with Enterprise-Grade Infrastructure",
"visitorName": "Optional visitor name",
"visitorEmail": "optional@example.com"
}Response:
{
"sessionId": "support_session_id",
"token": "opaque_session_token",
"messages": []
}Behavior:
- Validate message length and reject empty messages.
- Create a private Matrix room.
- Invite configured support staff.
- Post the visitor's initial message into the Matrix room.
- Store the session, token hash, Matrix room ID, and first message.
Send Visitor Message
POST /v1/support-chat/sessions/:sessionId/messages
Headers:
Authorization: Bearer <token>Request:
{
"body": "Can we talk to someone about launch timelines?"
}Response:
{
"messageId": "message_id",
"createdAt": "2026-05-23T00:00:00.000Z"
}Behavior:
- Validate the bearer token.
- Validate message length.
- Rate-limit by session and IP.
- Send the message into the Matrix room as the support bot.
- Store the message in Postgres.
Poll Messages
GET /v1/support-chat/sessions/:sessionId/messages?after=<messageId>
Headers:
Authorization: Bearer <token>Response:
{
"messages": [
{
"id": "message_id",
"sender": "staff",
"body": "Yes. What timeline are you targeting?",
"createdAt": "2026-05-23T00:00:00.000Z"
}
]
}Behavior:
- Use short polling every 2 seconds while the widget is open.
- Return only messages for that visitor session.
- Do not expose Matrix room IDs, Matrix event IDs, access tokens, or staff internal metadata unless explicitly needed.
Close Session
POST /v1/support-chat/sessions/:sessionId/close
Headers:
Authorization: Bearer <token>Behavior:
- Mark the website session closed.
- Keep the Matrix room for staff history and audit.
- Stop relaying new messages to the visitor unless the session is reopened by a future version.
Matrix Room Behavior
Create one Matrix room per website conversation.
Bot account:
@supportbot:matrix.webdevelop.pro
Staff configuration:
MATRIX_SUPPORT_STAFF_MXIDS=@alice:matrix.webdevelop.pro,@bob:matrix.webdevelop.pro
Room creation defaults:
{
"preset": "private_chat",
"creation_content": {
"m.federate": false
},
"initial_state": [
{
"type": "m.room.history_visibility",
"state_key": "",
"content": {
"history_visibility": "joined"
}
}
]
}Operational behavior:
- Invite all configured staff users to the room.
- Set room name to a useful support label, for example
Website chat - 2026-05-23 - webdevelop.biz. - Set room topic to:
Messages sent here are visible to the website visitor. - Relay non-bot
m.room.messagetext events back to the website. - Ignore bot-origin messages when reading Matrix events to avoid echo loops.
- Keep v1 unencrypted so the bot can read staff replies reliably.
- Prevent normal staff users from enabling room encryption accidentally through room power-level policy where practical.
Out of scope for v1:
- File uploads.
- Voice/video calls.
- MatrixRTC.
- Public room aliases.
- Matrix guest accounts in the browser.
- E2EE.
- Push notifications.
- Multi-agent assignment workflow.
Frontend Behavior
The widget should be deliberately small and support-focused.
Core states:
- Closed launcher.
- Open empty state.
- Sending first message.
- Active session.
- Operator reply received.
- Network error with retry.
- Offline state.
- Session closed.
Data handling:
- Create the backend session only after the first visitor message.
- Store
{ sessionId, token }insessionStorage. - Do not store support-chat tokens in
localStorage. - Render text as text, not HTML.
- Escape all user and Matrix-originated message content.
- Keep transcript limited to the current browser session in v1.
Polling:
- Poll every 2 seconds while the widget is open.
- Pause polling while closed after a short grace period.
- Resume polling when reopened.
- Stop polling on unmount.
Vue implementation requirements:
- Use Vue 3 Composition API.
- Use
<script setup lang="ts">. - Use typed props/emits.
- Use
ref()for primitive state. - Use
computed()for derived UI state. - Put API/session logic in a composable if it is used by more than one component.
- Use Pinia only if the state needs to outlive the widget component or be shared outside it.
Security And Abuse Controls
Frontend:
- No Matrix tokens in browser code.
- No Matrix room IDs exposed to the browser unless a later feature explicitly needs them.
- No HTML rendering in chat messages.
- Disable send while request is in flight.
- Enforce client-side length limits, but do not trust them.
Backend:
- Hash session tokens at rest.
- Rate-limit by IP and session.
- Enforce message length and session lifetime.
- Add basic spam throttles before creating Matrix rooms.
- Log request IDs and Matrix room IDs server-side for debugging.
- Do not log raw bearer tokens.
- Optionally add Cloudflare Turnstile before the first message if spam appears in production.
Matrix/Synapse:
- Disable open public registration unless there is a separate verified onboarding flow.
- Keep support rooms non-federated for v1.
- Keep support rooms invite-only.
- Reduce media upload limits from the current broad Matrix defaults before enabling attachments.
- Add moderation tooling separately for public community rooms; it is not required for private support rooms v1.
Test Plan
Frontend
Run:
pnpm --dir apps/webdevelop-pro exec vue-tsc --noEmit
pnpm --dir apps/webdevelop-pro run test:unit:cov
pnpm --dir apps/webdevelop-pro run buildScenarios:
- Widget is hidden when
VITE_MATRIX_CHAT_ENABLEDis nottrue. - Widget appears on
/and not on unrelated pages. - First visitor message creates a session.
- Later visitor messages use the bearer token.
- Polling appends staff replies.
- Polling stops on unmount.
- Message content is escaped and never rendered as HTML.
- Network failure shows retry state without losing the typed draft.
- Mobile layout does not overlap the page footer or primary CTA.
Backend
Use mocked Matrix client tests for:
- Private, non-federated room creation.
- Staff invitations.
- Initial visitor message relay.
- Later visitor message relay.
- Staff message ingestion from Matrix sync.
- Bot-origin event ignored to prevent echo.
- Invalid token rejected.
- Missing session rejected.
- Expired/closed session rejected.
- Rate limit response.
- Matrix API failure response and retry/log behavior.
DevOps
Run:
ansible-playbook --syntax-check <matrix-support-chat-playbook>
nginx -tStaging smoke test:
- Deploy Synapse and support-chat API.
- Confirm support bot can log in.
- Send first message from website.
- Confirm a private Matrix room is created.
- Confirm staff users are invited.
- Reply from Matrix.
- Confirm the website widget receives the reply.
Assumptions
- Staff accounts already exist or will be provisioned separately on
matrix.webdevelop.pro. - The v1 support bot is
@supportbot:matrix.webdevelop.pro. - The v1 relay API domain is
matrix-chat.webdevelop.pro. - Support rooms are intentionally unencrypted in v1 because the bot must read staff replies.
- Private support rooms are not public community chat. If a community channel is desired later, add a separate
matrix.toCTA and a moderated public Matrix room. - The implementation can add backend and Ansible changes outside
apps/webdevelop-pro.