Reporting API: Send Browser Reports to JavaScript Backends

The Reporting API is a modern browser mechanism that automatically captures client-side issues—including Content Security Policy (CSP) violations, deprecated API usage, Permissions Policy violations, and network errors—and transmits them directly to a designated server endpoint. This article explains how web applications configure the Reporting API via HTTP headers, how browsers batch and transmit these diagnostic reports as JSON payloads, and how a JavaScript backend receives and processes them.

1. Configuring the Reporting-Endpoints Header

To instruct the browser where to send diagnostic data, the web server must return the Reporting-Endpoints HTTP response header with the requested web page. This header maps custom endpoint names to specific target URLs on your JavaScript backend.

Reporting-Endpoints: default="https://api.example.com/reports", csp-endpoint="https://api.example.com/csp-reports"

Once defined, other security and diagnostic headers (such as Content-Security-Policy or Document-Policy) reference these endpoint names to route specific report types.

Content-Security-Policy: script-src 'self'; report-to csp-endpoint;

2. Browser Event Detection and Batching

When a user visits a page, the browser monitors for specific triggers: * Deprecation Reports: Triggered when the page executes an obsolete JavaScript API or CSS property. * CSP Violations: Triggered when scripts, stylesheets, or frames violate the security policy. * Crash and Intervention Reports: Triggered if a tab crashes or the browser blocks a behavior due to performance or security limits.

Rather than sending a separate HTTP request for every individual event, the browser buffers reports locally and transmits them in asynchronous batches. This design prevents report traffic from degrading page performance or consuming excessive network bandwidth.

3. Payload Format and Transmission

When sending reports, the browser issues an HTTP POST request to the configured backend endpoint.

An example payload containing a deprecation report and a CSP violation looks like this:

[
  {
    "type": "deprecation",
    "age": 420,
    "url": "https://example.com/dashboard",
    "user_agent": "Mozilla/5.0 ...",
    "body": {
      "id": "websql",
      "anticipatedRemoval": "2024-12-01",
      "message": "Web SQL is deprecated and will be removed.",
      "sourceFile": "https://example.com/main.js",
      "lineNumber": 45,
      "columnNumber": 12
    }
  },
  {
    "type": "csp-violation",
    "age": 120,
    "url": "https://example.com/dashboard",
    "user_agent": "Mozilla/5.0 ...",
    "body": {
      "blockedURL": "https://malicious-cdn.com/evil.js",
      "disposition": "enforce",
      "documentURL": "https://example.com/dashboard",
      "effectiveDirective": "script-src-elem",
      "originalPolicy": "script-src 'self'; report-to csp-endpoint;",
      "statusCode": 200
    }
  }
]

4. Handling Reports in a JavaScript Backend

Because reports arrive as JSON over standard HTTP POST requests, a JavaScript backend (such as Node.js with Express or Next.js API routes) only needs to accept the application/reports+json content type, parse the array, and persist or log the incoming data.

import express from 'express';

const app = express();

// Parse JSON bodies, including application/reports+json
app.use(express.json({ type: ['application/json', 'application/reports+json'] }));

app.post('/reports', (req, res) => {
  const reports = req.body;

  if (Array.isArray(reports)) {
    for (const report of reports) {
      console.log(`[${report.type}] on ${report.url}:`, report.body);
      // Route to monitoring services, loggers, or databases
    }
  }

  // Respond with a 204 No Content or 200 OK
  res.status(204).end();
});

app.listen(3000, () => {
  console.log('Reporting API backend running on port 3000');
});

The server returns an empty 200 or 204 status code upon successful receipt, allowing the browser to clear its internal report queue.