Documentation menu

Feedback and failure context

Open Errors → Feedback → Collection settings in the environment you want to use. Comments and screenshots are disabled by default. Enable collection, optionally allow reviewed images, and choose retention and the daily submission limit. errors.read opens the register; comments, screenshots, context, assignees and internal notes additionally require people.pii.read. Settings require errors.write; triage and deletion require both write and PII. Changes use same-origin CSRF validation, a reason, and a passkey verified within ten minutes.

Browser installation

The optional entry point adds no code to the base tracker or script-tag bundle. It is safe to import during server rendering; open it from a browser handler. It requires an allowed public ingest key, granted feedback consent and persistent analytics consent.

import { createClient } from "@saaspro/browser";
import { createFeedbackWidget } from "@saaspro/browser/feedback";

const client = createClient().init({
  key: "YOUR_PUBLIC_INGEST_KEY",
  host: "https://saaspro.dev",
  consent: "pending",
});
const feedback = createFeedbackWidget(client, {
  consent: "pending",
  release: "web@2.1.0",
  onError: error => console.warn(error.message),
  onSubmitted: receipt => console.info("Feedback accepted", receipt.id),
});

// Connect these to the visitor's separate consent choices.
client.consent("granted");
feedback.setConsent("granted");

// Connect this to your Report a problem button.
await feedback.open();

// Connect these to privacy settings and component cleanup.
feedback.optOut();
feedback.setConsent("granted");
feedback.destroy();

Keep consent, preference and cleanup calls in their respective handlers, not together in startup code. open() returns whether it displayed the form. It returns false without consent, with ephemeral analytics, while another form is open, when collection is paused, or after a configuration failure. onError reports request failures. Create the widget before the relevant error: it observes subsequent errors after capture hooks accept them. Dropped errors are not included. The SDK flushes pending events before submitting feedback.

The form appears at the lower right, or inside an optional container: HTMLElement. It uses native controls, a closed shadow root, visible focus, and light/dark styles. Allow the API host in connect-src and blob: in img-src for local previews. An optional nonce supports the stylesheet fallback under strict CSP.

Visitors choose a local PNG/JPEG, inspect a preview, and check I reviewed this image and agree to share its visible contents. They can remove the attachment before sending. The widget never captures a screen automatically, fetches a remote image URL, or masks visible content. Visitors should crop or redact private details before choosing a file.

One image is allowed per report: at most 2 MiB, 4096 pixels per side, and 8 million decoded pixels. The server verifies signatures and rejects other formats, SVG, PNG animation chunks, JPEG multipicture containers, concatenated images, and decoded multipage/frame inputs. Sharp 0.35.4 uses pixel/channel limits, a three-second processing timeout, and at most two concurrent image operations per process. It applies orientation and re-encodes as PNG with metadata removed; the output must also fit within 2 MiB. Visible personal details are not masked. See Sharp's input metadata and output metadata and processing limits contracts.

Feedback consent defaults to pending, independently from analytics. Turn off feedback stores a host/key preference in local storage. Saved opt-out wins over an initial granted option until an explicit setConsent("granted"). A storage failure leaves opt-out active in memory and calls onError. Identity, session, reset, consent and cross-tab opt-out changes close the form and abort pending requests. Comments, images and receipts stay only in memory and are discarded on close or completion. Consent flags describe this collection behavior; they are not authenticated identity or a legal consent record.

Context and triage

An error link requires an accepted occurrence matching the exact organization, application, environment, event ID, timestamp, anonymous ID and session ID. Its release comes from that occurrence. An unmatched supplied release remains labeled SDK-reported; feedback remains usable without an error link. The worker checks for late matching errors for one hour after the report arrives, visiting the least recently checked context first so unmatched reports do not block later matches. Links include an exact timestamp so reused event IDs cannot select another occurrence. Expired occurrences are shown as no longer retained.

Session links require accepted session evidence with the same scope and anonymous ID. Replay additionally requires Analytics access and a retained recording covering that moment. SDK identities are spoofable: correlation never authenticates a person or changes account permissions. The public collector returns only a receipt, never error/session/replay existence.

Filter the register by status, open a private report, inspect its screenshot, assign an eligible member, add internal notes, and mark it open, in progress, resolved or ignored. Assignees must currently hold Errors read and People PII access. Each report allows 100 internal notes of up to 2,000 characters. Notes never send a message to the visitor. Optimistic revisions prevent a stale tab from overwriting another operator. Issue pages link back to related feedback.

Protocol and limits

POST /api/v1/feedback accepts public or secret ingest keys through key, Authorization: Bearer, or x-spm-key. Browser requests send JSON as text/plain, without credentials. Public keys require an allowed, non-null Origin. Secret keys may omit Origin. Management tokens and public bot submissions are refused.

  • Config: { action: "config", key? }{ enabled, screenshotsEnabled, maxImageBytes }.
  • Submit: { action: "submit", key?, id, token, submittedAt, comment, consent: true, anonymousId, sessionId, error?: { id, timestamp }, release?, screenshot?: { mediaType, data, reviewed: true } } → HTTP 202 { accepted: true, id, duplicate }.

Report, anonymous and session IDs are UUIDs. Tokens contain 32 cryptographically random bytes encoded as 64 lowercase hexadecimal characters. Times use ISO date-time strings. Comments contain 1–4,000 characters. Screenshot data is canonical base64 with image/png or image/jpeg. The strict protocol rejects identity elevation fields, arbitrary URLs, and unknown fields.

The complete body is capped at 2,810,000 bytes with a five-second read deadline. The existing 600-token bucket replenished every ten seconds costs 5 tokens per config and 60 per submission, per key and server instance. Environment quotas are durable: 500 submissions per UTC day by default (maximum 2,000), 10,000 retained reports, and 256 MiB retained images. Deletion does not refund daily counts. Internal notes have the separate per-report limit above. These are collection controls, not billing metrics.

The first send freezes the widget's content and receipt. Retry submission preserves the same ID, token, normalized content and original time. Changed retries conflict, and receipts are bound to the issuing key. Only token hashes are stored. Original submission timestamps expire after thirty minutes. Closing the form loses its pending receipt; reopening starts a new report. Pause, revocation and consent withdrawal may prevent retry.

Retention and private delivery

Retention defaults to thirty days, configurable from one to ninety days and capped by application retention. Expired content disappears from reads immediately. The feedback.maintain worker runs every five minutes in bounded batches; physical removal and late linking require that worker. Delete removes the report, screenshot, context and notes. A thirty-day tombstone prevents receipt reuse; unchanged retries remain expired afterward. Daily counts remain ninety days. App/organization deletion cascades through feedback data.

Collection, settings, triage, deletion, retention and late linking append audit events without comments, images, notes, tokens or raw SDK identities. Reasons remain in the audit ledger: put private findings in internal notes. Images use an authenticated console endpoint with independent permissions, private, no-store, nosniff and same-origin resource policy. No public feedback or image read endpoint exists.