JavaScript errors from real visitors, with the request context that caused them. Two ways in: the script we ship, or twenty lines of your own.
Before you start
You need the project's js_key (public, from the PROJECTS view) and the exact origins your site is served from.
1. Understand the authentication — it is not a secret
- Browser reports go to
POST /api/v1/ingest/js/<js_key>; the key is in the URL and is public by design. - The project's allowed origins do the real work: a report whose
Originis not on the list is dropped. - Limits: 10 reports per request, 64 KB, about 30 per minute per address.
Why not a secret? Anything shipped to a browser is public the moment it ships. Pretending otherwise produces a secret that leaks and a team that believes it did not. The origin allowlist is the control that actually holds, so a stolen js_key buys an attacker the ability to send reports from your own domains — which is what your own site already does.
2. Allowlist your origins
In PROJECTS → the project → allowed JS origins, add every origin the app is served from, one per line, scheme://host[:port] and nothing else:
https://www.example.com
https://staging.example.com
http://localhost:8080
Anything not listed is dropped silently. That includes www versus bare domain — they are different origins.
3. Add the client
Script tag
<script src="https://yourconsole.cloud/js/console-client.js"></script>
<script>
ovosConsole.init({
url: 'https://yourconsole.cloud',
key: 'YOUR_PUBLIC_JS_KEY',
release: 'v2.113.1', // optional, but it earns its place
environment: 'production',
});
</script>Async, after paint
<script>
window.ovosConsoleConfig = {
url: 'https://yourconsole.cloud',
key: 'YOUR_PUBLIC_JS_KEY',
};
</script>
<script async src="https://yourconsole.cloud/js/console-client.js"></script>Your own
const report = (error) =>
{
const body = JSON.stringify([{
v: 1,
runtime: 'browser',
entry: 'web',
kind: 'error',
priority: 3,
message: error.message,
events: [{message: error.message, className: error.name, backtrace: error.stack}],
context: {uri: location.pathname, ua: navigator.userAgent},
}]);
// sendBeacon survives the page unloading, which is exactly when errors happen
navigator.sendBeacon('https://yourconsole.cloud/api/v1/ingest/js/YOUR_PUBLIC_JS_KEY', body);
};
addEventListener('error', (event) => report(event.error ?? new Error(event.message)));
addEventListener('unhandledrejection', (event) => report(event.reason));WordPress
The plugin ships the same script and fills the key in for you:
Settings → ovos console → Report JavaScript errors
Trace correlation and DOM snapshots are switches on the same screen.4. Verify it
Open the site, then in the browser console:
throw new Error('console test from ' + location.host);
The row appears in the grid within a second, with runtime browser. If it does not: check the Origin against the allowlist first — that is the cause nine times out of ten, and it fails silently by design.
What the shipped client captures
- Uncaught errors and unhandled promise rejections.
- Failed
fetch/ XHR calls, with the stack from the call site. - Breadcrumbs — the clicks and navigations that led there.
- A masked DOM snapshot on the first error per page load, optional.
- Automation evidence: a
webdriveradmission, and the external scripts a visitor never attempted to load — the signature of a bot running inline JS.
It is wrapped so it can never throw into your page. If the console is unreachable the call simply fails and your site does not notice.
Trace correlation
Turn it on and the client sends a W3C traceparent on same-origin fetch and XHR calls. The server error and the browser failure then carry one trace id, and the console lines them up as a single request across services.
Disable it if a firewall or security plugin rejects the extra header.
What never leaves the browser
- Input values are stripped from DOM snapshots before upload.
- Scripts are removed from snapshots entirely.
- No cookies, no storage, no keystrokes, no session recording.
Next: Build your own client if you would rather not ship ours, or Ingest API v1 for the full field list.