Use the Reporting API to monitor security violations, deprecated API calls, and more.
Some errors only occur in production. You won't see them locally or during development because real users, real networks, and real devices change the game. The Reporting API helps catch some of these errors—such as security violations or deprecated and soon-to-be-deprecated API calls across your site, and transmits them to an endpoint you've specified.
It lets you declare what you'd like to monitor using HTTP headers, and is operated by the browser.
Setting up the Reporting API gives you peace of mind that when users experience these types of errors, you'll know, so you can fix them.
This post covers what this API can do and how to use it. Lets dive in!
Overview
Lets assume that your site, site.example, has a Content-Security-Policy and a
Document-Policy. Don't know what these do? That's okay, you'll still be able to
understand this example.
You decide to monitor your site in order to know when these policies are violated, but also because you want to keep an eye on deprecated or soon-to-be-deprecated APIs your codebase may be using.
To do so, you configure a Reporting-Endpoints header, and map these endpoint
names using the report-to directive in your policies where needed.
Reporting-Endpoints: main-endpoint="https://reports.example/main", default="https://reports.example/default"
# Content-Security-Policy violations and Document-Policy violations
# will be sent to main-endpoint
Content-Security-Policy: script-src 'self'; object-src 'none'; report-to main-endpoint;
Document-Policy: document-write=?0; report-to=main-endpoint;
# Deprecation reports don't need an explicit endpoint because
# these reports are always sent to the `default` endpoint
Something unforeseen happens, and these policies get violated for some of your users.
Example violations
index.html
<script src="script.js"></script>
<!-- CSP VIOLATION: Try to load a script that's forbidden as per the Content-Security-Policy -->
<script src="https://example.com/script.js"></script>
script.js, loaded by index.html
// DOCUMENT-POLICY VIOLATION: Attempt to use document.write despite the document policy
try {
document.write('<h1>hi</h1>');
} catch (e) {
console.log(e);
}
// DEPRECATION: Call a deprecated API
const webkitStorageInfo = window.webkitStorageInfo;
The browser generates a CSP violation report, a Document-Policy violation report, and a Deprecation report that capture these issues.
With a short delay—up to a minute—the browser then sends the reports to the endpoint that was configured for this violation type. The reports are sent out-of-band by the browser itself (not by your server nor by your site).
The endpoint(s) receive(s) these reports.
You can now access the reports on these endpoints and monitor what went wrong. You're ready to start troubleshooting the problem that's affecting your users.
Example report
{
"age": 2,
"body": {
"blockedURL": "https://site2.example/script.js",
"disposition": "enforce",
"documentURL": "https://site.example",
"effectiveDirective": "script-src-elem",
"originalPolicy": "script-src 'self'; object-src 'none'; report-to main-endpoint;",
"referrer": "https://site.example",
"sample": "",
"statusCode": 200
},
"type": "csp-violation",
"url": "https://site.example",
"user_agent": "Mozilla/5.0... Chrome/92.0.4504.0"
}
Use cases and report types
The Reporting API can be configured to help you monitor many types of interesting warnings or issues that happen throughout your site:
| Report type | Example of a situation where a report would be generated |
|---|---|
| CSP violation (Level 3 only) | You've set a Content-Security-Policy (CSP) on one of your pages, but the page is trying to load a script that's not allowed by your CSP. |
| COOP violation | You've set a Cross-Origin-Opener-Policy on a page, but a cross-origin window is trying to interact directly with the document. |
| COEP violation | You've set a Cross-Origin-Embedder-Policy on a page, but the document includes a cross-origin iframe that has not opted into being loaded by cross-origin documents. |
| Document Policy violation | The page has a document policy that prevents usage of document.write, but a script tries to call document.write. |
| Permissions policy violation | The page has a permissions policy that prevents microphone usage, and a script that requests audio input. |
| Deprecation warning | The page is using an API that is deprecated or will be deprecated; it calls it directly or using a top-level third-party script. |
| Intervention | The page is trying to do something that the browser decides not to honor, for security, performance or user experience reasons. Example in Chrome: the page uses document.write on slow networks or calls navigator.vibrate in a cross-origin frame that the user hasn't interacted with yet. |
| Crash | The browser crashes while your site is open. |
Reports
The browser sends reports to the endpoint you've configured. It sends requests that look as follows:
POST
Content-Type: application/reports+json
The payload of these requests is a list of reports.
Example list of reports
[
{
"age": 420,
"body": {
"columnNumber": 12,
"disposition": "enforce",
"lineNumber": 11,
"message": "Document policy violation: document-write is not allowed in this document.",
"policyId": "document-write",
"sourceFile": "https://site.example/script.js"
},
"type": "document-policy-violation",
"url": "https://site.example/",
"user_agent": "Mozilla/5.0... Chrome/92.0.4504.0"
},
{
"age": 510,
"body": {
"blockedURL": "https://site.example/img.jpg",
"destination": "image",
"disposition": "enforce",
"type": "corp"
},
"type": "coep",
"url": "https://dummy.example/",
"user_agent": "Mozilla/5.0... Chrome/92.0.4504.0"
}
]
Here's the data you can find in each of these reports:
| Field | Description |
|---|---|
age |
The number of milliseconds between the report's timestamp and the current time. |
body |
The actual report data, serialized into a JSON string. The fields contained in a report's body are determined by the report's type. ⚠️ Reports of different types have different bodies.
|
type |
A report type, for example csp-violation or coep. |
url |
The address of the document or worker from which the report was generated. Sensitive data such as username, password, and fragment are stripped from this URL. |
user_agent |
The User-Agent header of the request from which the report was generated. |
Credentialed reports
Reporting endpoints that have the same origin as the page that generates the report receive the credentials (cookies) in the requests that contain the reports.
Credentials may give useful additional context about the report; for example, whether a given user's account is triggering errors consistently, or if a certain sequence of actions taken on other pages is triggering a report on this page.
When and how does the browser send reports?
Reports are delivered out-of-band from your site: the browser controls when they're sent to the configured endpoint(s). There's also no way to control when the browser sends reports; it captures, queues, and sends them automatically at a suitable time.
This means that there's little to no performance concern when using the Reporting API.
Reports are sent with a delay—up to a minute—to increase the chances to send reports in batches. This saves bandwidth to be respectful to the user's network connection, which is especially important on mobile. The browser can also delay delivery if it's busy processing higher priority work, or if the user is on a slow or congested network at the time.
Third-party and first-party issues
Reports that are generated due to violations or deprecations happening on your page will be sent to the endpoint(s) you've configured. This includes violations committed by third-party scripts running on your page.
Violations or deprecations that happened in a cross-origin iframe embedded in your page will not be reported to your endpoint(s) (at least not by default). An iframe could set up its own reporting and even report to your site's—that is, the first-party's—reporting service; but that's up to the framed site. Also note that most reports are generated only if a page's policy is violated, and that your page's policies and the iframe's policies are different.
Example with deprecations
Browser support
The following table sums up browser support for the Reporting API v1, that
is with the Reporting-Endpoints header. Browser support for the Reporting API
v0 (Report-To header) is the same, except for one report type: Network Error
Logging isn't supported in the new Reporting API. Read the migration
guide for details.
| Report type | Chrome | Chrome iOS | Safari | Firefox | Edge |
|---|---|---|---|---|---|
| CSP violation (Level 3 only)* | ✔ Yes | ✔ Yes | ✔ Yes | ✘ No | ✔ Yes |
| Network Error Logging | ✘ No | ✘ No | ✘ No | ✘ No | ✘ No |
| COOP/COEP violation | ✔ Yes | ✘ No | ✔ Yes | ✘ No | ✔ Yes |
| All other types: Document Policy violation, Deprecation, Intervention, Crash | ✔ Yes | ✘ No | ✘ No | ✘ No | ✔ Yes |
This table only summarizes support for report-to with the new
Reporting-Endpoints header. Read the CSP reporting migration
tips if you're looking
to migrate to Reporting-Endpoints.
Using the Reporting API
Decide where reports should be sent
You have two options:
- Send reports to an existing report collector service.
- Send reports to a reporting collector you build and operate yourself.
Option 1: Use an existing report collector service
Some examples of report collector services are:
If you know of other solutions, open an issue to let us know, and we'll update this post!
Beside pricing, consider the following points when selecting a report collector: 🧐
- Does this collector support all report types? For example, not all reporting endpoint solutions support COOP/COEP reports.
- Are you comfortable sharing any of your application's URLs with a third-party report collector? Even if the browser strips sensitive information from these URLs, sensitive information may get leaked this way. If this sounds too risky for your application, operate your own reporting endpoint.
Option 2: Build and operate your own report collector
Building your own server that receives reports isn't that trivial. To get started, you can fork our lightweight boilerplate. It's built with Express and can receive and display reports.
When you build your own report collector:
- Check for
POSTrequests with aContent-Typeofapplication/reports+jsonto recognize reports requests sent by the browser to your endpoint. - If your endpoint lives on a different origin than your site, ensure it supports CORS preflight requests.
Option 3: Combine Option 1 and 2
You may want to let a specific provider take care of some types of reports, but have an in-house solution for others.
In this case, set multiple endpoints as follows:
Reporting-Endpoints: endpoint-1="https://reports-collector.example", endpoint-2="https://my-custom-endpoint.example"
Configure the Reporting-Endpoints header
Set a Reporting-Endpoints response header. Its value must be one or a series
of comma-separated key-value pairs:
Reporting-Endpoints: main-endpoint="https://reports.example/main", default="https://reports.example/default"
If you're migrating from the legacy Reporting API to the new Reporting API, it
may make sense to set both Reporting-Endpoints and Report-To. See
details in the migration guide.
In particular, if you're using reporting for Content-Security-Policy
violations using the report-uri directive only, check the migration steps for
CSP reporting.
Reporting-Endpoints: main-endpoint="https://reports.example/main", default="https://reports.example/default"
Report-To: ...
Keys (endpoint names)
Each key can be a name of your choice, such as main-endpoint or endpoint-1.
You can decide to set different named endpoints for different report
types—for example, my-coop-endpoint, my-csp-endpoint. With this, you
can route reports to different endpoints depending on their type.
If you want to receive intervention, deprecation, crash reports or a
combination of these, set an endpoint named default.
If the Reporting-Endpoints header defines no default endpoint, reports of
this type will not be sent (although they will be generated).
Values (URLs)
Each value is a URL of your choice, where the reports will be sent to. The URL to set here depends on what you decided in Step 1.
An endpoint URL:
- Must start with a slash (
/). Relative paths are not supported. - Can be cross-origin; but in that case credentials are not sent with the reports.
Examples
Reporting-Endpoints: my-coop-endpoint="https://reports.example/coop", my-csp-endpoint="https://reports.example/csp", default="https://reports.example/default"
You can then use each named endpoint in the appropriate policy, or use one single endpoint across all policies.
Where to set the header?
In the new Reporting API—the one that is covered in this post—
reports are scoped to documents. This means that for one given origin,
different documents, such as site.example/page1 and site.example/page2, can
send reports to different endpoints.
To receive report for violations or deprecations take place on any page of your site, set the header as a middleware on all responses.
Here's an example in Express:
const REPORTING_ENDPOINT_BASE = 'https://report.example';
const REPORTING_ENDPOINT_MAIN = `${REPORTING_ENDPOINT_BASE}/main`;
const REPORTING_ENDPOINT_DEFAULT = `${REPORTING_ENDPOINT_BASE}/default`;
app.use(function (request, response, next) {
// Set up the Reporting API
response.set(
'Reporting-Endpoints',
`main-endpoint="${REPORTING_ENDPOINT_MAIN}", default="${REPORTING_ENDPOINT_DEFAULT}"`,
);
next();
});
Edit your policies
Now that the Reporting-Endpoints header is configured, add a report-to
directive to each policy header for which you want to receive violation reports.
The value of report-to should be one of the named endpoints you've configured.
You can use the multiple endpoint for multiple policies, or use different endpoints across policies.

report-to is not needed for deprecation, intervention and crash
reports. These reports aren't bound to any policy. They're generated as long as
a default endpoint is set up and are sent to this default endpoint.
Example
# Content-Security-Policy violations and Document-Policy violations
# will be sent to main-endpoint
Content-Security-Policy: script-src 'self'; object-src 'none'; report-to main-endpoint;
Document-Policy: document-write=?0;report-to=main-endpoint;
# Deprecation reports don't need an explicit endpoint because
# these reports are always sent to the default endpoint