---
source: crewkit documentation
url: https://crewkit.io/docs/feedback.md
title: "Feedback"
description: "How a Host app collects feedback with the crewkit panel, what a report carries, what the Visitor is told, and what crewkit keeps."
---


**Feedback** lets the people who use a Host app, your app's development and pre-production
deployments, send your team a message without leaving the page. The person who sends one is a
**Visitor**. A Visitor opens the crewkit panel, reads a short notice, and sends a message with their
name and email. The report arrives in your organization's **Inbox** as pending. Nothing reaches your
agents until someone on your team reads it and confirms it.

## Turning feedback on

Feedback is **off by default**. An organization owner or admin turns it on in
**Settings → Organization → Feedback**, from an account whose email address is proven. Once crewkit
publishes its data processing agreement, turning feedback on accepts it for your organization.

Turning feedback off closes nothing and revokes no key. While it is off, every key answers as if it
did not exist, and the keys work again when feedback is turned back on.

## The panel

The panel is the `@crewkit/feedback` package, a React component with no dependencies besides React.
Mount it once in your app's shell:

```tsx
import { Feedback } from "@crewkit/feedback";
import "@crewkit/feedback/styles.css";

export function AppShell({ children }: { children: React.ReactNode }) {
  return (
    <>
      {children}
      <Feedback
        feedbackKey={process.env.NEXT_PUBLIC_CREWKIT_FEEDBACK_KEY}
        release={process.env.NEXT_PUBLIC_COMMIT_SHA}
      />
    </>
  );
}
```

The panel renders nothing when `feedbackKey` is absent or empty, so an environment without the
variable shows no panel. Put the key only in your development and pre-production environments:
crewkit cannot tell a production origin from a staging one, so nothing enforces that for you.

The panel always sends to `https://api.crewkit.io`. Add that origin to your Content Security
Policy's `connect-src`. Its requests never carry cookies. The package's README lists every prop,
including `visitor` to prefill the name and email, `route` to send a route pattern instead of the
path, and `openFeedback()` to open the panel from your own button.

## Feedback keys

A **Feedback key** is a project's credential for one environment of a Host app. A developer or above
mints one on the project's **Settings** page, under **Feedback keys**. crewkit shows the key once,
when it is minted, and stores only its SHA-256 digest, so a lost key cannot be recovered: mint
another and revoke the old one. A key never expires. Revoking it ends it, and a manager or above, or
the person who minted it, can revoke it.

Each key has these settings:

- **Environment.** A short lower-case label, such as `preview` or `staging`. It is shown on every
  report that comes through the key.
- **Allowed origins.** The origins the panel may send from, each exactly `scheme://host[:port]`.
  `http:` is accepted only for `localhost`. One `*` is allowed, within the leftmost label of an
  `https:` origin, and it matches part of that label but never a dot. A pattern that would match
  every host on a public suffix, such as `https://*.vercel.app`, is refused. A pattern whose fixed
  part ends in a public suffix, such as `https://myapp-*-myteam.vercel.app`, is accepted with a
  warning, because any account on that provider can register a host it matches. A request from an
  origin off the list is refused. The origin check is not authentication: rate limits and the
  project's daily cap are the controls.
- **Query parameters to keep.** At most 10 parameter names. By default none is kept.
- **Repository.** Optional. When a report's release is a commit SHA, crewkit links that commit in
  this repository.
- **Sentry organization slug.** Optional. When it is set, the panel may send the Visitor's Sentry
  replay and event IDs, and crewkit builds the links to them from this slug.
- **Host app privacy policy URL.** Optional. The notice links it beside your organization's policy.
- **Screenshots.** Off by default. When it is on, the panel offers the Visitor a screenshot (see
  [Screenshots](#screenshots)). A developer or above turns it on with **Offer screenshots** in the
  key's **Edit** dialog, or through the MCP tool `update_feedback_key` with `screenshots_enabled`.
  Turning it on or off changes the notice Visitors read. The switch is disabled, with its reason,
  when crewkit has turned screenshots off on the platform, when your organization is data-resident,
  or when crewkit cannot tell whether either applies.

Each project accepts at most 200 reports a day by default, counted per UTC day. A manager can set the
cap from 1 to 1,000. One Visitor's network address gets at most 20 reports a project a day, so one
sender cannot use up the cap for everyone else.

## What a report carries

- **The message.** The panel caps it at 4,000 characters. crewkit removes anything in it that looks
  like a secret before it is stored.
- **The Visitor's name and email**, as typed. crewkit never verifies them, and records whether your
  app prefilled them. Both are stored encrypted.
- **The page.** Its path, or the `route` your app passes. The query string and the fragment are cut,
  except the values of the parameters the key keeps. Each kept value has anything that looks like a
  secret removed, and is cut to 200 characters. The origin the request came from is recorded with
  the page.
- **The browser and OS family**, such as Chrome on macOS. crewkit reads them from the User-Agent
  header and never stores the header itself, or any version number.
- **The viewport**, the window's width and height.
- **The locale**, `en` or `fr`.
- **The release** your app passes, such as a commit SHA, cut to 64 characters. With the key's
  repository, a commit-shaped release becomes a link to that commit, and to its pull request when
  crewkit knows one.
- **Sentry links**, when the key has a Sentry slug and your app passes the IDs. crewkit builds the
  links from the slug and the IDs. It never stores a URL the panel sends.
- **The screenshot**, when the key offers one and the Visitor chooses to add it.
- **The notice version**, the notice's settings digest, and the privacy policy URL the notice
  linked.

The report does not store the Visitor's network address. It stores a keyed hash of it, bound to the
project, for 30 days, to limit abuse.

## What the Visitor is told

Before Send, the panel shows a notice that crewkit writes and serves, in English or French. It opens
on a one-line summary: who collects the feedback and for whom, that the AI agents your team works
with read the reports your organization confirms, that the information may be communicated outside
Québec, and that the Visitor can erase what they send. The full notice sits behind
**How your feedback is used**. It says what is recorded, and it names each setting of the key that
changes that: the query parameters kept, screenshots, and Sentry. It also says how long reports are
kept, how to erase one, and who to write to. That contact is your privacy officer when your
organization has set one, and your organization otherwise.

The notice links your organization's privacy policy, and the Host app's when the key has one. While
your organization has not set a privacy policy, the notice says so and links crewkit's instead,
labelled as crewkit's. Your Host app's own privacy policy must name crewkit as a processor.

Every change to the notice's words is a new **notice version**, and each report records the version
and locale the Visitor read. If the notice, the key's settings or your organization's policy change
while a panel is open, the panel's Send is refused, and the panel loads the notice again first.

## The Inbox

Pending reports wait in the **Inbox**, under **Feedback**, newest first and grouped by project.
Developers and above see them. A row shows the message, the environment, the page, the release and
the time. A project also shows there when it reached its daily cap today.

Opening a report shows the full message, its context, its screenshot when it has one, and the
Visitor's name and email, marked as provided by the app or as typed and not verified. From there you
**Confirm** it or **Delete** it, and the next report opens.

- **Confirm** makes the report project context. Outside a data-resident organization it becomes a
  `feedback` [Artifact](/docs/artifacts), which your agents can search. The Artifact holds the
  message, the environment, the page, the release, the viewport and the browser, and says whether a
  screenshot exists. It never holds the Visitor's name, email or locale, their hashed address, the
  Sentry links or the screenshot itself. Confirming cannot be undone, and there is no bulk confirm:
  each report is read by a person before agents can read it.
- **Delete** removes the report for good. To delete many at once, pick them in the list and choose
  **Delete picked**, up to 200 at a time. Each deletion is recorded as a security event.

## The project's Feedback tab

A project's **Feedback** tab lists its confirmed reports under **Open**, **Resolved** and **All**,
each with its count. A pending report never appears there. The tab never shows the Visitor's name
or email.

Beside each report's message, the context column shows where it came from, which build, which
device, and its Sentry session. The page's address is a link only once the report is confirmed,
through an origin without a `*`. Otherwise it is text you can copy.

A developer or above resolves an open report, optionally with the commit or pull request that fixed
it, and can reopen a resolved one. A manager or above can delete a confirmed report, from the
dashboard only. That deletes its Artifact too. Resolving, reopening and deleting are each recorded as
a security event.

Your agents work with confirmed reports through the crewkit [MCP](/docs/mcp) tools `list_feedback`,
`get_feedback`, `resolve_feedback` and `reopen_feedback`. They see what the tab sees, never the
Visitor's name or email, and no tool deletes a report. An agent that resolves a report records the
session it resolved from.

## Screenshots

Screenshots are off for every key until your team turns them on for that key, and crewkit can turn
them off for every key with a platform switch. They are never available to a data-resident
organization.

When the key offers screenshots, the notice says so, and the panel offers **Add a screenshot**.
No screenshot is taken unless the Visitor chooses one. The panel renders the visible page in the
Visitor's browser and shows it before Send, so they can highlight parts, black parts out, or remove
it. Password, card and one-time-code fields are always blank. An element your app marks
`data-ckfb-mask` is blanked, and one marked `data-ckfb-block` is left out.

The screenshot is uploaded once the report is sent, within 15 minutes, as a PNG, JPEG or WebP image
of at most 10 MB. crewkit scans and re-encodes it, and serves only a clean image.

- **Encryption.** Every screenshot object is encrypted before it is first written to storage, with
  its own AES-256-GCM key. That key is wrapped by a Cloud KMS key for the environment, and the
  ciphertext is bound to its image record. If Cloud KMS fails, crewkit stores nothing and the upload
  fails; it never falls back to storing an unencrypted image.
- **Serving.** A screenshot is never at a public or signed URL. The dashboard fetches it from the API
  with your sign-in, as whoever may read the report's text. Each view is recorded as a security
  event first, and when that cannot be recorded, the image is not shown. crewkit's MCP tools return
  no screenshot, and the screenshot route refuses the crewkit CLI.
- **Erasure.** Deleting or erasing a report deletes its screenshot with it, and a daily job deletes
  any stored screenshot whose report is gone, once it is more than an hour old. A screenshot still
  being processed when its report is erased can therefore stay stored for up to about 25 hours,
  until that job deletes it.

## Data-resident organizations

In a data-resident organization, confirming a report creates **no Artifact**: the report becomes
open in the project's Feedback tab, but nothing of it enters project context. Screenshots cannot be
turned on. When an organization becomes data-resident, its keys stop offering screenshots and its
stored screenshots are deleted.

## Retention, erasure and data requests

**Retention.** A daily job deletes:

- a pending report, 30 days after it arrived;
- a confirmed report, after your organization's feedback retention period, set in
  **Settings → Organization → Feedback** from 30 to 365 days, 180 by default, counted from arrival;
- the hashed network address and the Sentry IDs on every report, 30 days after it arrived.

A report always goes with its Artifact and its screenshot. Deleting a project or offboarding an
organization deletes its keys and every report, and the delete does not happen if a report cannot be
erased.

**Visitor erasure.** After Send, the panel offers **Delete what I sent**, which erases the report at
once, and gives the Visitor a private link to see or delete it later. Erasing removes the report, its
Artifact and its screenshot. It cannot remove what an agent already quoted in a team member's
session, and the notice says so.

**Data requests.** An organization owner or admin answers a Visitor's request from
**Settings → Organization → Feedback**, with **Data request**. The lookup is by the exact email
address, across every project and status, and it can export or erase what it finds. Each lookup,
erasure and screenshot view is recorded as a security event that carries a keyed hash of the
address, never the address itself. A screenshot link in an export works only for the admin who ran
the lookup, for 15 minutes.
