Technical docs
Message Reactions
Overview
Support reference for the current message feedback and reaction paths.
What the backend actually supports
- Feedback state now lives on the `messages` table itself:
- `reaction`
- `reaction_at`
- `feedback_comment`
- `feedback_comment_at`
- `feedback_state_updated_at`
- `reaction_by_*`
- Accepted reactions are:
- `like`
- `dislike`
- `null` to clear feedback
- `feedback_comment` is only retained for `dislike`.
- Clearing the reaction clears the entire feedback state.
Eligibility and validation
- Backend feedback is rejected for:
- `operator` messages
- `contact` messages
- That means backend eligibility currently allows feedback on assistant-side `ai` and `bot` messages.
- `feedback_comment` is capped at `500` characters.
Public webchat API
- Public webchat feedback route is:
- `PUT /api/public/webchat/messages/{message_id}/feedback`
- Accepted body:
- `{ "reaction": "like" | "dislike" | null, "feedback_comment"?: string | null }`
- The processor verifies that:
- the public API key resolves an organization
- the `webchat-token` resolves a webchat session/contact
- the message belongs to a conversation accessible through that contact channel
- The route returns the full updated webchat message object, not just the reaction value.
Current widget mismatch
- The widget client still calls:
- `PUT /api/public/webchat/messages/{message_id}/reaction`
- Widget body:
- `{ "reaction": "like" | "dislike" | null }`
- There is no matching `/reaction` route in the current public webchat router.
- If contact-side reactions are failing in the shipped widget, this path mismatch is the first thing to check.
Widget-side behavior
- The widget only shows reaction buttons when:
- `message.role === 'ai'`
- It does not show them for:
- `bot`
- `operator`
- Clicking the same reaction twice toggles it back to `null`.
- The widget updates reaction state optimistically in local component state.
Propagation to Omni and the widget
- Public webchat feedback updates emit:
- `conversation.message_updated` to organization websocket clients
- Omni consumes that event and reads reaction fields from the updated message payload.
- Omni renders feedback as read-only footer state on outgoing messages.
- The widget listens for:
- `webchat.message_updated`
- But its websocket handler currently only patches:
- `content`
- `updated_at`
- It does not reconcile websocket-delivered reaction, comment, or actor fields.
Internal chat feedback path
- Internal chat uses the organization route:
- `PUT /api/organizations/{orgId}/conversations/{conversationId}/messages/{messageId}/feedback`
- That flow emits:
- `internal.message_updated`
- Internal chat feedback also adds rate limiting for repeated changes on the same message.
Reporting API
- Aggregate webchat AI feedback stats are available from:
- `GET /api/organizations/{orgId}/messages/stats`
- Supported `types` query values are:
- `total`
- `likes`
- `dislikes`
- Those counts are scoped to:
- `role = ai`
- `channel = web_chat`