Monitor your web application with the Reporting API
Use the Reporting API to monitor security violations, deprecated API calls, and more.
This is an API guide with detailed usage examples for the Reporting API (v1), which uses the Reporting-Endpoints
header.
If you're using the legacy Reporting API (Report-To
header), read about API migration instead.
Are you looking for Network Error Logging documentation? Head over to Network Error logging instead.
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 via 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. Let's dive in!
Demo and code
See the Reporting API in action starting from Chrome 96 and newer (Chrome Beta or Canary, as of October 2021).
- Demo reporting endpoint. This page receives and displays reports. Review the code.
- Demo report generation. This page uses the new Reporting API with the
Reporting-Endpoints
header. It also intentionally violates its own policies, uses deprecated APIs, and does other bad things in order to generate reports.Reporting-Endpoints
on this page is set to send reports to the demo reporting endpoint mentioned above. Review the code.
Overview
Let's 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 via 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
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
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:
Network Error Logging isn't listed because it isn't supported in the new version of the API. Check the migration guide for details.
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 . |
Deprecation warning | The page is using an API that is deprecated or will be deprecated; it calls it directly or via 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. |
Permissions policy (formerly feature policy) violation reports may be supported by default in the future. Right now, they're experimental. One example situation where such reports would be generated: your site has a permission policy that prevents microphone usage, and a script requests audio input.
Reports
What do reports look like?
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
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. To see the exact body of each report type, check out the demo reporting endpoint and follow the instructions to generate example reports. |
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.
Cross-origin endpoints don't receive credentials. This is a security measure and can't be changed.
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 and/or congested network at the time.
When you're debugging locally, you can turn off this delay for convenience. See how.
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.
In Chrome DevTools, you'll see a console error or warning pop up for violations that are committed by third-party scripts and cross-origin iframes. Not all of these will translate into reports being sent to your endpoint: the formers will, the latters won't.
Example with deprecations
Browser support
The table below 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 |
All other types: COOP/COEP violation, Document Policy violation, Deprecation, Intervention, Crash | ✔ Yes | ✘ No | ✘ No | ✘ No | ✔ Yes |
Browser support for CSP reporting is different from other reporting types, because CSP has been around for some time. CSP reports can be generated via:
- The legacy
report-uri
directive that doesn't rely on the Reporting API. - The newer
report-to
directive that relies on the Reporting API (and theReporting-To
or the newerReporting-Endpoints
headers).
This table only summarizes support for report-to
with the new Reporting-Endpoints
header. Read the [CSP reporting migration tips](/blog/reporting-api-migration/#csp-reporting-migration) 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.
Don't use it as-in in production, but feel free to use it for a quick prototype. Make sure to fork it before using it, so that nobody you don't trust gets to see reports generated by your page.
Head over to the boilerplate report collector.
Click Remix to Edit to make the project editable.
You now have your clone! You can customize it for your own purposes.
If you're not using the boilerplate and are building your own server from scratch:
- Check for
POST
requests with aContent-Type
ofapplication/reports+json
to 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.
Make sure your endpoint 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"
Reporting-Endpoints
header
Configure the 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 via 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 and/or crash reports, 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).
Despite its name, default
is not a fallback endpoint. For example, if you set up report-to my-endpoint
for Document-Policy
and omit to define my-endpoint
in Reporting-Endpoints
, Document-Policy
violations reports will be generated but will not be sent because the browser doesn't know where to send them to.
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();
});
This is different from the legacy Reporting API (Report-To
header), where you could set an "ambient" endpoint for one page, and automatically get reports for any page on the same origin. If you're migrating from the legacy Reporting API to the new Reporting API, check the migration guide.
Edit your policies
Now that the Reporting-Endpoints
header is configured, add a report-to
directive to each policy header for which you wish 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
Getting the report-to
syntax right can be tricky, because not all policies use the same header structure. Depending on the policy, the right syntax may be report-to=main-endpoint
or report-to main-endpoint
. Head over to the demo for code examples.
Example code
To see all this in context, below is an example Node server that uses Express and brings together all the pieces discussed in this article. It shows how to configure reporting for several different report types and displays the results.
Debug your reporting setup
Intentionally generate reports
When setting up the Reporting API, you'll likely need to intentionally violate your policies in order to check if reports are generated and sent as expected. To see example code that violates policies and does other bad things that will generate reports of all types, check out the demo.
Save time
Reports may be sent with a delay—about a minute, which is a long time when debugging. 😴 Luckily, when debugging in Chrome, you can use the flag --short-reporting-delay
to receive reports as soon as they're generated.
Run this command in your terminal to turn on this flag:
YOUR_PATH/TO/EXECUTABLE/Chrome --short-reporting-delay
This flag is not available via the Chrome UI, it's a command line flag only. Learn how to run Chromium with flags.
Use DevTools
In Chrome, use DevTools to see the reports that have been sent or will be sent.
As of October 2021, this feature is experimental. To use it, follow these steps:
- Use Chrome version 96 and newer (check by typing
chrome://version
in your browser) - Type or paste
chrome://flags/#enable-experimental-web-platform-features
in Chrome's URL bar. - Click Enabled.
- Restart your browser.
- Open Chrome DevTools.
- In Chrome DevTools, open the Settings. Under Experiments, click Enable Reporting API panel in the Application panel.
- Reload DevTools.
- Reload your page. Reports generated by the page DevTools is open in will be listed in Chrome DevTools' Application panel, under Reporting API.
Report status
The Status column tells you if a report has been successfully sent.
Status | Description |
---|---|
Success | The browser has sent the report and the endpoint replied with a success code (200 or another success response code 2xx ). |
Pending | The browser is currently making an attempt to send the report. |
Queued | The report has been generated and the browser is not currently trying to send it. A report appears as Queued in one of these two cases:
|
MarkedForRemoval | After retrying for a while (Queued ), the browser has stopped trying to send the report and will soon remove it from its list of reports to send. |
Reports are removed after a while, whether or not they're successfully sent.
The Queued
status isn't always informative, because it doesn't precisely indicate whether sending has failed or has not been attempted yet. Using short reporting delays helps: a report that remains Queued
in that case likely indicates that sending is failing.
Troubleshooting
Are reports not generated or not sent as expected to your endpoint? Here are a few tips to troubleshoot this.
Reports are not generated
Reports that show up in DevTools have been correctly generated. If the report you expect does not show up in this list:
- Check
report-to
in your policies. If this is misconfigured, a report won't be generated. Head over to Edit your policies to fix this. An additional way to troubleshoot this is to check the DevTools console in Chrome: if an error pops up in the console for the violation you expected, this means your policy is probably properly configured. - Keep in mind that only the reports that were generated for the document DevTools is open in will show up in this list. One example: if your site
site1.example
embeds an iframesite2.example
that violates a policy and hence generates a report, this report will show up in DevTools only if you open the iframe in its own window and open DevTools for that window.
Reports are generated but not sent or not received
What if you can see a report in DevTools, but your endpoint doesn't receive it?
Because the report is sent out-of-band by the browser itself and not by a certain site, the POST
requests containing the reports are not displayed in the Network panel of your Developer Tools.
Make sure to use short delays. Maybe the reason you can't see a report is because it hasn't been sent yet!
Check your
Reporting-Endpoints
header configuration. If there's an issue with it, a report that has been generated correctly will not be sent. In DevTools, the report's status will remainQueued
(it might jump toPending
, and then quickly back toQueued
when a delivery attempt is made) in this case. Some common mistakes that may cause this:The endpoint is used but not configured. Example:
Code with a mistake
Document-Policy: document-write=?0;report-to=endpoint-1;
Reporting-Endpoints: default="https://reports.example/default"
The
default
endpoint is missing. Some reports types, such as deprecation and intervention reports, will only be sent to the endpoint nameddefault
. Read more in Configure the Reporting-Endpoints header.Look for issues in your policy headers syntax, such as missing quotes. See details.
Check that your endpoint can handle incoming requests.
Make sure that your endpoint support CORS preflight requests. If it doesn't, it can't receive reports.
Test your endpoint's behavior. To do so, instead of generating reports manually, you can emulate the browser by sending to your endpoint requests that look like what the browser would send. Run the following:
curl --header "Content-Type: application/reports+json" \
--request POST \
--data '[{"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://dummy.example/script.js"},"type":"document-policy-violation","url":"https://dummy.example/","user_agent":"xxx"},{"age":510,"body":{"blockedURL":"https://dummy.example/img.jpg","destination":"image","disposition":"enforce","type":"corp"},"type":"coep","url":"https://dummy.example/","user_agent":"xxx"}]' \
YOUR_ENDPOINTYour endpoint should respond with a success code (
200
or another success response code2xx
). If it doesn't, there's an issue with its configuration.
Related reporting mechanisms
Report-Only
-Report-Only
policy headers and the Reporting-Endpoints
work together.
Endpoints configured in Reporting-Endpoints
and specified in the report-to
field of Content-Security-Policy
, Cross-Origin-Embedder-Policy
and Cross-Origin-Opener-Policy
, will receive reports when these policies are violated.
Endpoints configured in Reporting-Endpoints
can also be specified in the report-to
field of Content-Security-Policy-Report-Only
, Cross-Origin-Embedder-Policy-Report-Only
and Cross-Origin-Opener-Policy-Report-Only
. They'll also receive reports when these policies are violated.
While reports are sent in both cases, -Report-Only
headers do not enforce the policies: nothing will break or actually get blocked, but you will receive reports of what would have broken or been blocked.
If you're using a -Report-Only
header and have configured your reporting endpoints via the legacy header Report-To
, migrate to Reporting-Endpoints
if you can. Read more in the migration guide.
ReportingObserver
The ReportingObserver
JavaScript API can help you observe client-side warnings.
ReportingObserver
and the Reporting-Endpoints
header generate reports that look the same, but they enable slightly different uses cases.
Use ReportingObserver
if:
- You only want to monitor deprecations and/or browser interventions.
ReportingObserver
surfaces client-side warnings such as deprecations and browser interventions, but unlikeReporting-Endpoints
, it doesn't capture any other types of reports such as CSP or COOP/COEP violations. - You need to react to these violations in real-time.
ReportingObserver
makes it possible to attach a callback to a violation event. - You want to attach additional information to a report to aid in debugging, via the custom callback.
Another difference is that ReportingObserver
is configured only client-side: you can use it even if you have no control over server-side headers and can't set Reporting-Endpoints
.
Further reading
- Migration guide from Reporting API v0 to v1
- ReportingObserver
- Specification: legacy Reporting API (v0)
- Specification: new Reporting API (v1)
Hero image by Nine Koepfer / @enka80 on Unsplash, edited. Many thanks to Ian Clelland, Eiji Kitamura and Milica Mihajlija for their reviews and suggestions on this article.