Ingestion API
Browsers post CSP reports to HeaderHawk directly. You never call this endpoint
from your own code — you name it in a report-uri or report-to directive and
the browser does the rest. It is documented here because it is worth knowing
what the browser is sending and what a given status code means when you are
checking whether reporting works.
Everything below is derived from the OpenAPI contract the endpoint is built from, and a test fails if the two drift apart.
The endpoint
Section titled “The endpoint”POST https://ingest.headerhawk.com/csp/{ingestCode}ingestCode is the 8-character base62 identifier assigned when you create a
site. Copy the full URL from the site’s setup panel rather than assembling it;
it is the value every guide in these docs writes as YOUR_INGEST_CODE.
Request formats
Section titled “Request formats”The endpoint detects the format from the body, so a browser that sends a
CSP report as application/json is handled the same as one that uses the
registered type.
| Format | Content-Type |
Sent by |
|---|---|---|
| CSP Level 2 | application/csp-report |
report-uri directive |
| Reporting API | application/reports+json |
report-to directive |
| Script hashes | application/reports+json |
report-to, 'report-sample' with hash collection enabled |
CSP Level 2
Section titled “CSP Level 2”A single object under a csp-report key. document-uri,
violated-directive and original-policy are required; the rest are
optional and browser-dependent.
{ "csp-report": { "document-uri": "https://example.com/page", "referrer": "https://google.com/", "violated-directive": "script-src", "effective-directive": "script-src", "original-policy": "default-src 'self'; script-src 'self'; report-uri /csp", "disposition": "report", "blocked-uri": "https://evil.com/script.js", "status-code": 200 }}Reporting API
Section titled “Reporting API”An array of report envelopes. body and type are required, and type is
csp-violation for a policy violation. Inside body, documentURL,
effectiveDirective and originalPolicy are required.
[ { "age": 10, "type": "csp-violation", "url": "https://example.com/page", "user_agent": "Mozilla/5.0...", "body": { "documentURL": "https://example.com/page", "blockedURL": "https://evil.com/script.js", "disposition": "enforce", "effectiveDirective": "script-src", "originalPolicy": "default-src 'self'; script-src 'self'" } }]Browsers batch csp-violation and csp-hash reports into one array. Mixed
arrays are split and each type routed independently, so a request carrying both
is handled as two pipelines, not rejected. See
Script Integrity Monitoring for what the
csp-hash type collects.
Responses
Section titled “Responses”| Status | Meaning |
|---|---|
204 |
Violation reports accepted. No body. |
202 |
Script-hash reports accepted. Body carries accepted and correlationId. |
400 |
invalid_path or invalid_format — see below. |
404 |
not_found — no site is accepting reports for this code. |
429 |
rate_limited or quota_exceeded. Carries Retry-After in whole seconds. |
Every response carries an X-Correlation-ID header. Quote it when asking about
a specific request; it is what traces that request through the pipeline.
Error bodies are uniform:
{ "error": "invalid_format", "message": "Unknown report format"}204 does not mean “stored”
Section titled “204 does not mean “stored””A 204 is returned when the reports were accepted and when every report in
the request was discarded because its document-uri did not match the site’s
registered domain. The two are deliberately indistinguishable, so the endpoint
cannot be used to enumerate the domains a tenant monitors.
This is the single most common reason reporting looks broken: a site registered
as example.com discards reports from www.example.com, and the browser sees
success either way. The site’s setup panel counts anything dropped for this
reason — check it there, not in the response.
404 does not distinguish unknown from deactivated
Section titled “404 does not distinguish unknown from deactivated”An unrecognised code and a code belonging to a deactivated site both return
not_found, for the same reason: distinct statuses would let anyone spraying
the 8-character keyspace confirm which codes belong to real customers. An owner
who deactivated their own site already knows they did.
202 does not mean “every hash was recorded”
Section titled “202 does not mean “every hash was recorded””accepted counts the reports published for processing, and may be 0 because
sampling dropped the report, because the site’s sample rate is 0, or because
the documentURL did not match the registered domain. The response does not
distinguish them.
Limits
Section titled “Limits”- 100 reports per request. A request carrying more is truncated, not
rejected — a
2xxmeans “accepted”, not “all of it stored”, and a browser that cannot tell a400from a204would simply retry. - Per-source rate limiting, applied before the ingest code is looked up, so
it bounds a caller probing for valid codes as well as one flooding a known
one. Exceeding it returns
429witherror: "rate_limited". - Monthly tenant quota. Past the hard limit, ingestion returns
429witherror: "quota_exceeded".
Checking it by hand
Section titled “Checking it by hand”The endpoint accepts a hand-rolled report, which is the quickest way to confirm
your ingest code reaches a live site. Use a document-uri on your registered
domain, or it will be discarded and you will still get a 204.
cat > report.json <<'JSON'{ "csp-report": { "document-uri": "https://example.com/page", "violated-directive": "script-src", "original-policy": "default-src 'self'; report-uri /csp", "blocked-uri": "https://evil.com/script.js" }}JSON
curl -i -X POST https://ingest.headerhawk.com/csp/YOUR_INGEST_CODE \ -H "Content-Type: application/csp-report" \ --data-binary @report.jsonA 204 with an X-Correlation-ID header means the code resolved to a site.
The report appears in the dashboard once processing completes.
Health check
Section titled “Health check”GET https://ingest.headerhawk.com/healthReturns 200 with {"status":"healthy","timestamp":"..."}. It touches no
database and is there for uptime monitoring.
Next steps
Section titled “Next steps”- Quick Start — get reporting working
- Troubleshooting — when reports are not arriving
- Script Integrity Monitoring — the
csp-hashpipeline