# 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.

- Canonical: https://info.centralcsp.com/articles/report-uri-vs-report-to-vs-reporting-endpoints/
- Published: 2026-08-09
- Language: en
- Publisher: CentralCSP (https://centralcsp.com)

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:

```http
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](https://centralcsp.com/en/platform/monitoring/) 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](/articles/centralcsp-vs-report-uri/).)

## 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:

```http
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](https://centralcsp.com) 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](https://centralcsp.com/en/docs/web-security/reporting-api/headers/reporting-endpoints) cover the exact header lines.

## Migration checklist

1. Add `Reporting-Endpoints` with a named HTTPS endpoint.
2. Add `report-to <name>` to your CSP, keeping the existing `report-uri` line.
3. Delete any `Report-To` header (capital T) left over from Reporting API v0.
4. Confirm your collector handles `application/reports+json` and its batched arrays.
5. Revisit dropping `report-uri` when 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.
