---
title: "Feedback"
description: "Wire answer ratings through the verified handler seam and read the shipped hosted summary response."
---

Set `feedback` on `<ChatWidget>` to show thumbs up/down under completed assistant
messages. The client always fires its local `onFeedback` callback and makes a
best-effort POST to `${apiBase}/v1/feedback`.

```tsx
<ChatWidget
  apiBase="/api/chat"
  config={{
    schemaVersion: 1,
    runtime: { model: 'anthropic/claude-sonnet-4-5' },
    client: { feedback: true },
  }}
  onFeedback={(event) => analytics.track('chat_feedback', event)}
/>
```

## Persist on the server

The mounted handler validates the payload, resolves the user again through
`getUserId`, and calls its server-side `onFeedback` seam. Browser identity is
never authoritative.

```ts
import { createHostedFeedback }
  from '@mordn/chat-widget/server/hosted';

createChatHandler({
  getUserId: getChatUserId,
  model,
  store,
  onFeedback: createHostedFeedback({
    apiKey: process.env.MORDN_CHAT_KEY!,
  }),
});
```

Without a server `onFeedback`, the handler acknowledges valid submissions but
does not persist them. Recording failures are logged and still return `{ ok:
true }`; feedback must not break chat.

The submitted shape is:

```ts
interface FeedbackEvent {
  userId: string; // added by the verified server handler
  conversationId?: string;
  messageId: string;
  rating: 'up' | 'down';
  reason?: string;
}
```

## Hosted summary

```bash
curl "https://api.mordn.com/v1/feedback/summary?from=2026-07-01" \
  -H "Authorization: Bearer $MORDN_CHAT_KEY"
```

The current response is the aggregate count only:

```json
{
  "up": 1601,
  "down": 239,
  "total": 1840
}
```

`from` and `to` are optional. The endpoint does not currently return `upRate` or
recent reason text; compute the rate client-side and use a separate authorized
surface if reason inspection is required.
