# User feedback > https://docs.errora.org/en/products/user-feedback User feedback closes the loop between "something broke" and "here is what the person was actually trying to do". Errora accepts it from **three places** and funnels all of them into **one inbox**, linked to the error the user saw. | Source | Stored `source` | Who sends it | | --- | --- | --- | | Feedback widget | `widget` / `new_feedback_envelope` | The browser SDK's `feedbackIntegration()` | | Crash-report modal | `crash_report_embed_form` | `Sentry.showReportDialog({ eventId })` | | User report API | `user_report_envelope` | Your backend, or the legacy `user_report` envelope item | Pick whichever fits. Most teams run the **widget** everywhere and the **crash-report modal** on their error page. ## Option 1 — the feedback widget The widget is a floating button that opens a form. It is part of the official Sentry browser SDK, so there is nothing extra to install. ### Setup ```javascript import * as Sentry from "@sentry/browser"; Sentry.init({ dsn: "https://@/", environment: "production", release: "web@1.4.2", integrations: [ Sentry.feedbackIntegration({ colorScheme: "system", // "light" | "dark" | "system" showBranding: false, autoInject: true, // render the floating button showName: true, showEmail: true, isNameRequired: false, isEmailRequired: false, // Localized copy — the widget ships English defaults. triggerLabel: "Send feedback", formTitle: "Send feedback", messagePlaceholder: "What happened?", submitButtonLabel: "Send", successMessageText: "Thank you!", }), ], }); ``` That is the whole setup. Submissions arrive in **Feedback** in the dashboard within a second or two. ### Attaching it to your own button Turn off auto-injection and open the form yourself: ```javascript const feedback = Sentry.feedbackIntegration({ autoInject: false }); document.querySelector("#feedback-button").addEventListener("click", async () => { const form = await feedback.createForm(); form.appendToDom(); form.open(); }); ``` Or attach the widget to an existing element: ```javascript const unsubscribe = feedback.attachTo(document.querySelector("#feedback-button"), { formTitle: "Report a problem", }); ``` ### Sending feedback without any UI If you already have your own form, send its contents straight through the SDK: ```javascript Sentry.captureFeedback({ name: "Nima", email: "nima@example.com", message: "The invoice PDF is blank on Safari.", associatedEventId: lastEventId, // optional — links the feedback to that error }); ``` ### What Errora stores The widget sends a `feedback` envelope item whose payload sits in `contexts.feedback`. Errora keeps: | Field | Notes | | --- | --- | | `message` | The feedback text (max 4096 chars) | | `name`, `contact_email` | Whatever the form collected | | `url` | The page the user was on | | `associated_event_id` | The error the user saw; used to link the feedback to its issue | | `replay_id` | Links to the [session replay](/en/products/session-replay), when replay is on | | `environment`, `release`, `platform` | From the SDK's scope | | `user_id`, `user_ip`, `user_agent` | Only when the SDK sends them | | `source`, `status`, `created_at` | Set by Errora | Feedback that names an `associated_event_id` is **attached to that event's issue**, so the issue page shows what the user said about the exact error. ## Option 2 — the crash-report modal The crash-report modal is the classic "It looks like we're having issues" dialog you show *after* catching an error. ```javascript Sentry.init({ dsn: "https://@/", beforeSend(event, hint) { // Only prompt for real, user-visible failures. if (event.exception && event.event_id) { Sentry.showReportDialog({ eventId: event.event_id }); } return event; }, }); ``` Or explicitly, wherever you handle a failure: ```javascript const eventId = Sentry.captureException(err); Sentry.showReportDialog({ eventId, title: "مشکلی پیش آمد", subtitle: "تیم ما مطلع شد.", subtitle2: "اگر مایلید کمک کنید، بنویسید چه اتفاقی افتاد.", labelName: "نام", labelEmail: "ایمیل", labelComments: "چه اتفاقی افتاد؟", labelSubmit: "ارسال گزارش", labelClose: "بستن", user: { name: "Nima", email: "nima@example.com" }, }); ``` ### How it works The SDK injects a ` ``` ## Option 3 — the server-side report API When your **backend** already captured the exception and you collect the comment yourself (a support form, a Telegram bot, a phone call), post it to the project's feedback endpoint: ```bash curl -X POST "$ERRORA/api/v1/projects/$PROJECT_ID/feedback" \ -H "Authorization: Bearer $ACCESS_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "event_id": "9a1f4c2e7b8d4a1fa0c3e5d6b7a8c9d0", "name": "Nima", "email": "nima@example.com", "comments": "Checkout froze after I pressed pay." }' ``` - Requires a user token with the **`issue:write`** capability on the project (a JWT access token or a [personal access token](/en/account/tokens)). - `event_id` accepts both the 32-hex and dashed UUID forms. - `comments` is required (max 4096 chars); empty feedback is rejected with `400`. Legacy SDKs that send a `user_report` envelope item land here too, with no extra work. ## Working the inbox **Feedback** in the sidebar is the inbox. Each row shows the message, who sent it (or *anonymous*), the source, the page URL, the linked issue and the time. You can: - **Search** the message text (`q`), filter by **status**, **environment**, and *only feedback linked to an issue* (`has_issue`), over a time period. - **Change status** — `unresolved` → `resolved`, or mark `spam`. - **Open the linked issue** to see the stack trace behind the complaint. - **Open the session replay**, when the feedback carries a `replay_id`. - **Delete** a submission. Headline counters (total, unresolved, linked to an issue) sit at the top of the page. ### API ```text GET /api/v1/projects/{project_id}/feedback?q=&status=&environment=&has_issue=&stats_period=24h&limit=25&offset=0 POST /api/v1/projects/{project_id}/feedback # the report API above GET /api/v1/projects/{project_id}/feedback/summary GET /api/v1/projects/{project_id}/feedback/{id} PATCH /api/v1/projects/{project_id}/feedback/{id} # {"status": "resolved" | "unresolved" | "spam"} DELETE /api/v1/projects/{project_id}/feedback/{id} GET /api/v1/issues/{issue_id}/feedback # everything said about one issue ``` ## Getting notified Feedback fires the **`feedback.received`** event, so you can route it to any channel — Telegram, Bale, Mattermost, email, SMS or a webhook: 1. **Settings → Webhooks** → create a channel (see [Alerts](/en/products/alerts)). 2. Add an alert rule for **New user feedback**, scoped to the project. A useful template for a chat channel: ```text 📣 {{ title }} {{ body }} {{ url }} ``` ## Troubleshooting | Symptom | Cause / fix | | --- | --- | | Widget button never appears | `feedbackIntegration()` missing from `integrations`, or `autoInject: false` with no `attachTo`/`createForm` call | | Widget submits but nothing arrives | The DSN is wrong, or an ad blocker ate the request — check the browser network tab for the `envelope` call, and consider the SDK's `tunnel` option | | Modal shows "unknown error" on submit | You are past the 10-per-minute-per-IP limit, or the `eventId` is not a valid event id | | Modal never opens | `showReportDialog` was called before `Sentry.init`, or with no `eventId` on a page where no event was captured | | Feedback arrives but is not linked to an issue | No `associated_event_id`/`event_id` was sent, or the event it names was sampled away | | `403` from the report API | The token lacks `issue:write` on that project | | Everything is `anonymous` | The SDK sends no user; set `Sentry.setUser({...})` or turn on `showEmail`/`showName` in the widget |