Reporting-Endpoints, Report-To and CSP report-to vs report-uri (tested in Chromium)

CSP can send violation reports two ways: the old report-uri directive, or report-to plus a Reporting-Endpoints header. They use different payloads, different rules and different headers. We set up both in Chromium 153 and logged what actually reached the server.

1. The headers

Reporting-Endpoints: csp="https://example.com/csp-reports"
Content-Security-Policy: script-src 'self'; report-to csp

Reporting-Endpoints maps a name (csp) to a URL. The CSP report-to directive names the endpoint to use. The older way puts the URL right in the policy:

Content-Security-Policy: script-src 'self'; report-uri https://example.com/csp-reports

2. What arrived, measured

SetupResult in Chromium 153
report-uri onlyPOST in about 0.2 s, Content-Type: application/csp-report
Reporting-Endpoints + report-to, HTTPS endpointPOST in about 0.2 s, Content-Type: application/reports+json (with and without --short-reporting-delay in our headless run)
Both report-uri and report-to in one policyOnly the report-to endpoint got a report. The report-uri URL got nothing (waited 30 s).
Endpoint on another originOPTIONS preflight first (Access-Control-Request-Headers: content-type), then the POST
report-to with an http://127.0.0.1 endpointNo report in 60 s
Old Report-To JSON header (no Reporting-Endpoints) + report-toNo report in 30 s

3. The two payloads

report-uri sends one object per violation, with hyphenated keys:

Content-Type: application/csp-report

{"csp-report": {"document-uri": "https://localhost:8947/", "referrer": "",
  "violated-directive": "script-src-elem", "effective-directive": "script-src-elem",
  "original-policy": "script-src 'none'; report-uri https://localhost:8947/r",
  "disposition": "enforce", "blocked-uri": "inline", "line-number": 1,
  "source-file": "https://localhost:8947/", "status-code": 200, "script-sample": ""}}

report-to sends a JSON array (reports can be batched), with camelCase keys inside body:

Content-Type: application/reports+json

[{"age": 0, "type": "csp-violation", "url": "https://localhost:8946/",
  "user_agent": "Mozilla/5.0 (X11; Linux x86_64) ... HeadlessChrome/153.0.0.0 ...",
  "body": {"blockedURL": "inline", "disposition": "enforce",
    "documentURL": "https://localhost:8946/", "effectiveDirective": "script-src-elem",
    "lineNumber": 1, "originalPolicy": "script-src 'none'; report-to csp",
    "referrer": "", "sample": "", "sourceFile": "https://localhost:8946/", "statusCode": 200}}]

So a collector written for report-uri needs changes: accept the new content type, loop over the array, check type, and read the camelCase fields. Note there is no violatedDirective; use effectiveDirective.

4. Rules that bit us

5. A working setup

Reporting-Endpoints: csp="https://example.com/csp-reports"
Content-Security-Policy: script-src 'self'; report-uri https://example.com/csp-reports; report-to csp

Make the endpoint accept both application/csp-report and application/reports+json, return 204, and handle OPTIONS if it is on another origin. To test a policy without blocking anything, send it as Content-Security-Policy-Report-Only; for a starting policy see CSP header examples. Related: Sec-Fetch-* headers.

Frequently asked questions

What is the difference between CSP report-to and report-uri?

report-uri puts a URL in the policy and POSTs one application/csp-report object per violation. report-to names an endpoint from the Reporting-Endpoints header and POSTs an application/reports+json array of reports with type csp-violation. In Chromium 153 both arrived in well under a second in our headless test.

Should I use both report-uri and report-to?

Yes, as a fallback, but in Chromium 153 only the report-to endpoint received the report; report-uri was ignored when report-to was present.

Why are my report-to reports not arriving?

In our test an http:// endpoint got nothing; it must be HTTPS. A cross-origin endpoint also has to answer the CORS preflight. Real browsers can batch reports for about a minute.

What is the difference between Report-To and Reporting-Endpoints?

Report-To is the older JSON header with group and max_age; Reporting-Endpoints is the current name="url" header. With only Report-To, Chromium 153 sent no CSP report in our test.

What Content-Type do CSP reports use?

application/csp-report for report-uri and application/reports+json for report-to.

Written with AI assistance. Measured on 2026-10-01 in headless Chromium 153.0.8010.12 (Playwright) against Node.js servers on localhost with a self-signed certificate (HTTPS page, HTTPS endpoint, certificate errors ignored for the test). The page had one inline script blocked by script-src 'none'; the server logged every request to the report endpoint. Delivery timing was measured with and without Chromium's --short-reporting-delay switch. A normal desktop browser may batch reports for up to about a minute, so don't expect the instant delivery we saw. Other browsers were not tested. PlainHeaders is our extension; DevTools → Network → Headers or curl -I shows the same response headers, free.