ShonyLabs
DocsPricingChangelog
Log inGet started

Getting started

IntroductionQuickstart

Tracking

Tracking snippetCustom goalsIdentify usersExclusions

Privacy

GDPR & cookieless tracking

Revenue

Transactions APIPayment connectors

Server & bots

Bots & server-side tracking

Reliability

Error tracking

Social listening

Brand mentions

Collaboration

Team & access

Live map

Public live map

Integrations

Google Search Console

No-code platforms

WordPressGoogle Tag ManagerWebflowWixSquarespaceFramerGhostOther platforms

Reference

API referenceCLI & MCP server

Error tracking

Know the moment your server starts returning errors or your frontend starts throwing — without digging through logs.

Log in to jump straight to your site's Settings → Errors to turn this on.

How it works

Both tracking modes are off by default and configured independently under Settings → Errors. Every occurrence is grouped into a de-duped issue— repeat hits of the same underlying error increment a counter instead of flooding you with one row per request. You get an email the first time a new issue shows up, or if one you've already marked resolved starts happening again.

  • Server-side — your own backend calls an authenticated API whenever it hits an error.
  • Frontend — a checkbox; the tracking snippet you've already installed starts capturing uncaught JavaScript errors automatically, no code change needed.

Server-side: POST /v1/errors

Call this from your own backend whenever it hits an error worth knowing about — a Laravel exception handler, an Express error middleware, or any language that can make an HTTP request. Authenticated with your site's API key, the same as the Transactions API.

cURL
curl -X POST https://api.shonylabs.com/v1/errors \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "message": "Call to undefined method App\\Models\\User::foo()",
    "status_code": 500,
    "path": "/checkout",
    "stack": "Error: Call to undefined method\n#0 /app/Http/Controllers/CheckoutController.php(42)"
  }'
FieldRequiredNotes
messageYesUp to 500 characters. Two reports with the same message, status code, and path (numbers in the message are collapsed first) are grouped into the same issue.
status_codeNoThe HTTP status this error produced, e.g. 500. Only recorded if it falls within the range configured in Settings → Errors (default 500-599). Omit it entirely for an error with no HTTP response behind it (e.g. a background job) — those are always tracked once enabled.
pathNoUp to 500 characters, e.g. /checkout.
stackNoUp to 4,000 characters. Shown when you expand the issue on the Errors page.
contextNoA flat JSON object of your own key/value pairs — order ID, environment, provider, anything worth attaching. Up to 10 keys, 255 characters per value.
timestampNoISO 8601 event time. Defaults to the time the request is received.
shonylabs_visitor_idNoLinks the error back to the visitor it happened to — see "Linking an error to a visitor" below.

A response of { "accepted": false, "reason": "..." }(still HTTP 200) means the report was received but intentionally not recorded — either error tracking isn't enabled for this site, or the status code you sent isn't in the tracked range. Your integration doesn't need to treat that as a failed request.

Laravel example

Add this to your exception handler's report() method so every unhandled exception gets reported:

PHP
// app/Exceptions/Handler.php
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;

public function report(Throwable $e)
{
    parent::report($e);

    Http::withToken(env('SHONYLABS_API_KEY'))->post('https://api.shonylabs.com/v1/errors', [
        'message' => $e->getMessage(),
        'status_code' => $e instanceof HttpExceptionInterface ? $e->getStatusCode() : 500,
        'stack' => (string) $e,
        'path' => request()->path(),
        'shonylabs_visitor_id' => request()->cookie('shonylabs_visitor_id'),
    ]);
}

Express example

Register this as your last error-handling middleware:

JavaScript
// Express error-handling middleware — register it last, after all other app.use()/routes.
// req.cookies requires the cookie-parser middleware; omit shonylabs_visitor_id if you don't use it.
app.use((err, req, res, next) => {
  fetch('https://api.shonylabs.com/v1/errors', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${process.env.SHONYLABS_API_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      message: err.message,
      status_code: err.statusCode || 500,
      path: req.path,
      stack: err.stack,
      shonylabs_visitor_id: req.cookies?.shonylabs_visitor_id,
    }),
  }).catch(() => {});

  next(err);
});

Attaching context

Attach whatever will help you diagnose the error later — an order ID, environment, or the provider involved:

cURL
curl -X POST https://api.shonylabs.com/v1/errors \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "message": "Payment provider timeout",
    "status_code": 502,
    "context": {
      "order_id": "order_9F2K",
      "provider": "stripe",
      "environment": "production"
    }
  }'

Linking an error to a visitor

The tracking snippet already mirrors the visitor's ID into a first-party shonylabs_visitor_id cookie — the same one the Transactions API uses to attribute a purchase. Read it server-side and pass it along (see the shonylabs_visitor_idline in the Laravel and Express examples above) and the Errors page will show who was affected, with a link straight to their visitor journey. It's optional — omit it and the issue still records everything else, just without a name attached.

Frontend: automatic JavaScript error capture

Turn on Enable frontend error trackingunder Settings → Errors and the tracking snippet you've already installed starts listening for uncaught errors and unhandled promise rejections in visitors' browsers — no code change, no redeploy. This works with both the cookie-based and cookieless tracking snippets. Unlike a server-reported error, a browser error always carries the visitor who hit it — no extra field to pass, it's automatic.

Resource load failures (a broken image or script tag) are not reported — only real JavaScript runtime errors and unhandled promise rejections.

Viewing and resolving issues

Your site's Errors pagelists every issue — source, status code, message, occurrence count, first/last seen, and (when available) the most recently affected visitor, linked to their journey. Click a row to expand the stack trace and any context you attached. Once you've shipped a fix, click Resolve — if the same fingerprint occurs again later, the issue reopens and you get a fresh email, the same as a brand new issue.

ShonyLabs Analytics

Privacy-friendly web analytics that shows which channels actually drive revenue.

© 2026 ShonyLabs. All rights reserved.

Product

  • Log in
  • Sign up
  • Pricing
  • Changelog
  • Documentation
  • API reference

Guides

  • Quickstart
  • Tracking snippet
  • Goals
  • Transactions
  • Identify visitors
  • Cookieless tracking

Company

  • About
  • Contact
  • Terms of service
  • Privacy policy
  • Data processing agreement