Skip to main content

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.

SourceStored sourceWho sends it
Feedback widgetwidget / new_feedback_envelopeThe browser SDK's feedbackIntegration()
Crash-report modalcrash_report_embed_formSentry.showReportDialog({ eventId })
User report APIuser_report_envelopeYour 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​

import * as Sentry from "@sentry/browser";

Sentry.init({
dsn: "https://<public_key>@<your-errora-host>/<project_id>",
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:

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:

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:

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:

FieldNotes
messageThe feedback text (max 4096 chars)
name, contact_emailWhatever the form collected
urlThe page the user was on
associated_event_idThe error the user saw; used to link the feedback to its issue
replay_idLinks to the session replay, when replay is on
environment, release, platformFrom the SDK's scope
user_id, user_ip, user_agentOnly when the SDK sends them
source, status, created_atSet 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.

Sentry.init({
dsn: "https://<public_key>@<your-errora-host>/<project_id>",
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:

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 <script> pointing at Errora's embed endpoint:

GET https://<host>/api/embed/error-page/?dsn=<dsn>&eventId=<event_id> → the dialog's JavaScript
POST https://<host>/api/embed/error-page/?dsn=<dsn>&eventId=<event_id> → stores the report
  • Authentication is the DSN public key, not a session — the dialog runs on your users' pages. It is a public write endpoint by design.
  • Every label is a query parameter. Any key in the dialog's copy (title, subtitle, subtitle2, labelName, labelEmail, labelComments, labelClose, labelSubmit, errorGeneric, errorFormEntry, successMessage) can be overridden — which is how you ship a Persian dialog.
  • Rate limit: 10 submissions per client IP per minute.
  • The report is linked to the event id you passed, and therefore to its issue.

Server-rendered pages​

You don't need the SDK loaded to show the dialog — render the script tag yourself on your error page:

<script
src="https://<host>/api/embed/error-page/?dsn=<dsn>&eventId=<event_id>&labelSubmit=%D8%A7%D8%B1%D8%B3%D8%A7%D9%84">
</script>

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:

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).
  • 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​

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).
  2. Add an alert rule for New user feedback, scoped to the project.

A useful template for a chat channel:

📣 {{ title }}
{{ body }}
{{ url }}

Troubleshooting​

SymptomCause / fix
Widget button never appearsfeedbackIntegration() missing from integrations, or autoInject: false with no attachTo/createForm call
Widget submits but nothing arrivesThe 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 submitYou are past the 10-per-minute-per-IP limit, or the eventId is not a valid event id
Modal never opensshowReportDialog 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 issueNo associated_event_id/event_id was sent, or the event it names was sampled away
403 from the report APIThe token lacks issue:write on that project
Everything is anonymousThe SDK sends no user; set Sentry.setUser({...}) or turn on showEmail/showName in the widget