report-uri vs report-to vs Reporting-Endpoints: which CSP reporting setup should you use in 2026?
Three overlapping mechanisms deliver CSP violation reports, and the browser support picture changed in March 2026. What each one does, how the report formats differ, and the header combination to deploy today.
CSP violation reporting suffers from a naming disaster: a deprecated directive (report-uri), a current directive (report-to), a deprecated header that looks exactly like the current directive (Report-To), and the header that actually replaced it (Reporting-Endpoints). Four names, two generations, one job.
Here’s the setup to deploy in 2026, and then the explanation of each piece:
Reporting-Endpoints: csp-endpoint="https://report.example.com/endpoint"
Content-Security-Policy: default-src 'self';
report-uri https://report.example.com/endpoint;
report-to csp-endpoint
Both reporting directives, one endpoint declaration. Browsers that understand report-to use it and ignore report-uri. Everything older falls back to report-uri. No browser reports twice. This belt-and-suspenders combination is what CentralCSP’s endpoint accepts natively, all three mechanisms on one URL, precisely because the transition is nowhere near finished.
(Different question: if you searched “report uri” looking for the reporting service by that name, that’s Report URI, Scott Helme’s product. We compare it with CentralCSP in a dedicated article.)
The four names, untangled
report-uri (directive, CSP Level 2). The original. Takes a URL directly, fires a POST per violation, immediately. Deprecated in the spec for years, still honored by every browser in circulation. Its report is a single JSON object under a csp-report key, kebab-case fields (blocked-uri, violated-directive), content type application/csp-report.
report-to (directive, CSP Level 3). The replacement. Takes an endpoint name, not a URL, and relies on a separate header to resolve that name. Reports travel through the Reporting API: batched, delivered asynchronously (sometimes minutes later), camelCase fields (blockedURL, effectiveDirective) inside a wrapper carrying age, type, url and user_agent, content type application/reports+json.
Report-To (header, Reporting API v0). Deprecated. Declared endpoints as a JSON blob. Its lowercase near-twin being a current directive has confused a generation of engineers and at least one security vendor’s documentation. If your config sets it, migrate.
Reporting-Endpoints (header, Reporting API v1). Current. One line, name-to-URL mappings, quoted HTTPS URLs only:
Reporting-Endpoints: csp-endpoint="https://report.example.com/endpoint", default="https://report.example.com/other"
It also carries every other Reporting API report type (deprecation, crash, COOP violations), which is why the names matter: one header can feed several streams to several endpoints.
What changed in 2026
Support, finally. Reporting-Endpoints reached cross-browser baseline in September 2024. The report-to directive followed and, per MDN’s compatibility data, has been baseline across the latest browser versions since March 2026.
Read that carefully though: “latest versions”. Enterprise fleets pinned to older browsers, embedded WebViews, and the long tail of unupdated devices still speak only report-uri. Dropping the legacy directive today means going blind on exactly the clients most likely to be running outdated, vulnerable software. Our recommendation stands until your own user-agent data says otherwise: ship both directives, let each browser pick.
The differences that actually bite
report-uri | report-to + Reporting-Endpoints | |
|---|---|---|
| Delivery | Immediate, one POST per violation | Batched, asynchronous, possibly minutes later |
| Content type | application/csp-report | application/reports+json |
| Payload | Single csp-report object | Array of report objects with metadata wrapper |
| Field style | blocked-uri, violated-directive | blockedURL, effectiveDirective |
| Endpoint declaration | URL in the directive itself | Name resolved via Reporting-Endpoints |
| Endpoint requirements | Any URL | HTTPS only, silently dropped otherwise |
| Other report types | CSP only | Deprecation, crash, COOP, and more |
Two of these rows cause most real-world confusion. The batching means “I deployed report-to and nothing arrives” is often just impatience, and it makes the Reporting API a poor fit for real-time incident detection on its own. And the HTTPS-only rule fails silently: an http:// endpoint in Reporting-Endpoints produces no error, no warning, and no reports, ever.
The format split also means a homegrown collector needs two parsers and a content-type switch. Managed endpoints absorb this. CentralCSP normalizes both formats into one indexed stream, so a violation looks the same in your dashboard whether it arrived from a 2019 browser or yesterday’s Chrome. The same endpoint takes the other Reporting API types too (NEL, crash, deprecation, COOP, COEP: 12 in all) if you decide to route them there. The reporting setup docs cover the exact header lines.
Migration checklist
- Add
Reporting-Endpointswith a named HTTPS endpoint. - Add
report-to <name>to your CSP, keeping the existingreport-uriline. - Delete any
Report-Toheader (capital T) left over from Reporting API v0. - Confirm your collector handles
application/reports+jsonand its batched arrays. - Revisit dropping
report-uriwhen your traffic stats say legacy clients are gone. For most sites, that day is years away.
The naming mess is permanent. The setup, at least, doesn’t have to be complicated.
Frequently asked questions
Is report-uri deprecated? Should I remove it?
The directive is deprecated in the spec but still honored by browsers, and until your entire audience runs 2026-era browsers it remains your only coverage for older clients. Keep it alongside report-to. Browsers that understand report-to ignore report-uri, so nothing is double-reported.
Why am I receiving no reports through report-to?
Check four things: the Reporting-Endpoints header is present and its endpoint name matches the report-to value exactly, the endpoint URL is HTTPS (insecure endpoints are silently ignored), your collector accepts the application/reports+json content type, and you have waited a few minutes, because Reporting API deliveries are batched rather than immediate.
What is the difference between report-to and the Report-To header?
The report-to CSP directive is current and points at an endpoint name. The Report-To HTTP header (capital letters, from Reporting API v0) is deprecated and replaced by the Reporting-Endpoints header. If your config still sets Report-To, migrate it to Reporting-Endpoints.
Do report-uri and report-to send the same JSON?
No. report-uri POSTs a single csp-report object with kebab-case fields like blocked-uri as application/csp-report. report-to delivers batched arrays of report objects with camelCase fields like blockedURL as application/reports+json. A collector has to parse both formats during the transition.