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.
- Content-Type: The request uses the
application/reports+jsonmedia type. - Payload Structure: The request body contains an array of JSON objects, each representing an event.
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.