Docs

Production monitoring: see what real users hit.

Add one script tag and Check’d captures the errors, failed requests and slow pages from your live app, groups them into issues, and puts them in the same inbox as your test findings.

Install

In your project, open Settings, Production monitoring and create an ingest key. The page shows this snippet with your key filled in, a button that sends a test event, and waits until your first real event arrives.

<script src="https://getchecked.twelveai.app/sdk/checked.v1.min.js"
        data-key="ck_pub_..."
        data-env="production"
        data-release="1.4.2"></script>
Script tag

Put it in the head of every page, before your own scripts. It is under 10 KB gzipped, has no dependencies, and sends nothing unless an error, a failed request or a slow page happens, or you call track.

React, Next.js, Vue and other bundlers

npm install @checked/browser

import { Checked } from "@checked/browser";

Checked.init({ key: "ck_pub_...", environment: "production", release: "1.4.2" });
Checked.identify({ id: user.id });            // optional, after sign-in
Checked.captureError(error);                    // errors you catch yourself
Checked.track("checkout_completed", { plan: "pro" });
npm

In Vue, also pass render errors on: app.config.errorHandler = (err) => Checked.captureError(err). In Next.js, call init once from a client component in the root layout.

Node.js servers

npm install @checked/node

import { Checked, expressErrorHandler } from "@checked/node";

Checked.init({ key: "ck_pub_...", environment: "production", release: process.env.RELEASE });
app.use(expressErrorHandler());                 // after your routes
Node

The Node SDK captures uncaught exceptions and unhandled rejections, and ships helpers for Express, Fastify and AdonisJS error handlers. 4xx errors are skipped unless you ask for them.

What it captures

  • Errors: uncaught errors and unhandled promise rejections with stack traces, plus anything you pass to captureError.
  • Failed requests: fetch and XMLHttpRequest calls that return 5xx or fail to connect, with method, URL, status and duration. Never bodies or headers.
  • Slow pages: poor Largest Contentful Paint, Interaction to Next Paint, Cumulative Layout Shift, server response time and load time.
  • Breadcrumbs: the last 30 clicks, page changes (including single-page app route changes) and requests before each error.
  • Context: release, environment, page URL, browser, operating system, device type and screen size, an anonymous visitor id, and the user id if you call identify.
  • Key actions: Checked.track(name, props) for the moments you care about, like a completed checkout.

Options

Pass these to Checked.init, or as data- attributes on the script tag (data-env, data-release, data-sample-rate, data-console-errors, data-capture-4xx).

OptionDefaultWhat it does
keyrequiredThe project’s public ingest key (ck_pub_...). It can only send events, so it is safe in page source.
environmentproductionKeeps staging and production apart in the inbox.
releasenoneYour app version. Powers “first seen in” and regression detection.
sampleRate10 to 1, applied to errors, failed requests and slow pages. Custom events are never sampled.
captureConsoleErrorsfalseAlso report console.error calls.
captureRequests4xxfalseAlso report 4xx responses. 5xx and network failures are always reported.
captureQueryStringsfalseKeep ?query and #hash in URLs. Off by default because they often carry tokens.
replayoffSession replay: { onErrorSampleRate, sampleRate, maskAllText, maskSelector, blockSelector, maxDurationMinutes }. On the script tag: data-replay="true" plus data-replay-error-sample-rate, data-replay-sample-rate, data-replay-mask-all-text, data-replay-mask, data-replay-block.
respectDoNotTrackfalseWhen on, browsers with Do Not Track or Global Privacy Control send nothing.
sendEmailfalseidentify() hashes the email in the browser. Turn this on only if you want the address itself stored.
beforeSendnoneA function that receives each event last. Return it changed, or null to drop it.
reportAllVitalsfalseReport every Web Vital. By default only poor ones are sent.

Privacy

  • Input values are never read. A click on a field is recorded as the field, like input[name=email], not what was typed. Session replay masks every input in the browser too.
  • Query strings and fragments are stripped from every URL, including URLs inside error messages and stack traces.
  • Request and response bodies and headers are never captured.
  • Anything that looks like a bearer token, a password=, token= or api_key= value, a JWT or a card number is removed on our servers too, as a second line of defence.
  • Emails passed to identify are hashed in the browser unless you turn on sendEmail.
  • beforeSend lets you scrub or drop any event before it leaves the page, and sampleRate and respectDoNotTrack reduce what is sent.
  • Occurrences are deleted after your plan’s retention period. The issue itself, its status and comments stay.

You are the controller of the data your app sends. If your users are in a region that requires consent for error monitoring, load the script after consent.

Session replay

Replay shows what the person saw and did in the seconds before an error: the page, their clicks and scrolling, page changes and failed requests, next to the issue. It is off until you turn it on, and it loads as a separate script, so pages without it never download the recorder.

<script src="https://getchecked.twelveai.app/sdk/checked.v1.min.js"
        data-key="ck_pub_..."
        data-replay="true"
        data-replay-error-sample-rate="1"
        data-replay-sample-rate="0"></script>
Script tag
Checked.init({
  key: "ck_pub_...",
  replay: {
    onErrorSampleRate: 1,   // keep the last minute, upload it when an error happens
    sampleRate: 0,          // fraction of sessions recorded from the start
    maskAllText: false,     // inputs are always masked; this masks all text too
    blockSelector: ".card-form",
    load: () => import("@checked/browser/replay"),
  },
});
npm
  • On error (the default): the browser keeps the last 30 to 60 seconds in memory and uploads nothing unless an error or failed request is captured. Then it uploads that minute and keeps recording the rest of the visit.
  • Whole sessions: sampleRate records that fraction of sessions from the start.
  • Masked by default: every input, textarea and dropdown is masked in the browser before anything is recorded. Passwords show a fixed mask, so not even their length is kept.
  • Your markers: add data-checked-mask to hide text inside an element, and data-checked-block to leave an element out entirely (a grey box of the same size is shown). maskAllText masks every piece of text on the page.
  • Never recorded: canvas, cross-origin iframes, query strings in page and link addresses, typed values, and anything when Do Not Track or Global Privacy Control is on.
  • Who can watch: members of your workspace only. Replays have no public links and are deleted after your plan’s replay retention.

Each plan includes a monthly number of replays, a retention period and a maximum length and size per replay. Past them the recorder stops quietly; your app never sees an error. If your site sets a Content-Security-Policy, allow the Check’d origin in script-src and connect-src.

Issues and grouping

Occurrences of the same problem become one issue, so ten thousand identical errors are one line in your inbox:

  • Errors group by error type, the message with numbers, ids and URLs taken out, and the top frames of your own code (function and file, ignoring line numbers and build hashes).
  • Failed requests group by method, host, route with ids collapsed (/orders/:id) and status class (5xx, 4xx, network).
  • Slow pages group by page route and metric.

Each issue has a status (new, investigating, resolved, ignored, regressed), an owner, comments, the browsers, systems and releases it affects, and a 30 day chart. Test findings from runs, website scans and load tests sit in the same inbox, so a bug your tests found and the same bug hitting users are side by side.

Minified code without source maps groups less precisely, because function names change between builds. Source map support is planned.

Alerts go to the email addresses and Slack channel in your project’s notification settings when a new issue appears, a resolved one comes back, or an issue spikes past a threshold you set.

Releases

Set release on the SDK and Check’d records the first release each issue appeared in. When you resolve an issue you can say which release fixed it. If it shows up again in a newer release, it is marked regressed and you get an alert. Errors from older releases still open in someone’s tab do not count.

To record deploys from CI, call the releases API with a workspace API key:

curl -X POST https://getchecked.twelveai.app/api/v1/releases \
  -H "Authorization: Bearer $CHECKED_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"projectId": "shop-kqzxm", "version": "1.4.2", "environment": "production", "commitSha": "abc123"}'
Register a release

Limits

Each plan includes a monthly number of events, a retention period and a per-key rate. Past the monthly allowance, events are dropped and counted on your Plan and usage page, and your app never sees an error. Monitoring allowances are early and may change; current numbers are shown in the app.

Ingest API

The SDKs use this endpoint. You can call it yourself from any language. Send up to 50 events per request and keep bodies under 256 KB. Sending JSON as text/plain avoids a CORS preflight in browsers.

POST https://getchecked.twelveai.app/api/v1/ingest/events?key=ck_pub_...
Content-Type: text/plain;charset=UTF-8

{
  "key": "ck_pub_...",
  "sdk": { "name": "checked.browser", "version": "1.0.0" },
  "sentAt": 1760000000000,
  "events": [
    {
      "type": "error",
      "ts": 1760000000000,
      "environment": "production",
      "release": "1.4.2",
      "url": "https://app.example.com/orders/123",
      "user": { "anonymousId": "a_9f2c" },
      "error": { "type": "TypeError", "message": "...", "stack": "...", "handled": false, "mechanism": "onerror" }
    }
  ]
}
Ingest request
202Accepted. The body says how many were accepted and dropped.
200Over this month’s allowance. Dropped and counted; do not retry.
400The body is not a batch of events.
401Unknown or revoked key.
403The page’s origin is not on the key’s allowed list.
413The body is too large.
429Too many events for this key right now. Retry after the Retry-After seconds.

Questions? Contact support.