Troubleshooting
POST /ingest always answers 204, whether an event was accepted or dropped, so there is no error to read in the Network tab. Start every “no data showing up” investigation with GET /v1/validate.
Common silent-drop causes, ranked
Section titled “Common silent-drop causes, ranked”data-domaindoesn’t match any trusted domain (typo, or the domain was never added).- Page origin doesn’t match a trusted domain. Usually a
www.vs bare-domain mismatch, a staging subdomain nobody added, orfile:///opaque origins. - Local development without the project’s “Allow local origins” toggle or an explicit
localhostdomain. See Localhost & development. - A Content-Security-Policy blocking the script or the API origin.
- An adblocker blocking
cdn.analytics.canarycoders.esorapi.analytics.canarycoders.es. - The project’s daily event cap has been reached.
Causes 1 to 3 are exactly what /v1/validate catches. Causes 4 to 6 never reach the server, so see what /v1/validate can’t see.
Walking through /v1/validate
Section titled “Walking through /v1/validate”Call it with the exact domain/origin pair a real visitor’s browser would send. Pull them off the actual page (location.hostname for domain, location.origin for origin) rather than guessing:
curl "https://api.analytics.canarycoders.es/v1/validate?domain=myapp.com&origin=https://staging.myapp.com" \ -H "Authorization: Bearer cly_live_5f3a9c2e1b7d4a80f6c3e9d2a1b4c7e0"{ "domain": "myapp.com", "origin": "https://staging.myapp.com", "wouldAccept": true, "checks": { "domainOwnedByProject": true, "originPresent": true, "originParseable": true, "originIsLocal": false, "allowLocalOrigins": false, "originTrusted": true }, "reasons": []}Each checks field mirrors one step of the real /ingest logic:
| Field | Fails when |
|---|---|
domainOwnedByProject |
domain doesn’t match any of this project’s trusted domains. Matching is on label boundaries, so evil-myapp.com never matches myapp.com. |
originPresent |
No origin was supplied to the check. This mirrors a request with no Origin header, which is always dropped. |
originParseable |
origin isn’t a valid URL (it should look like https://app.example.com). |
originIsLocal |
Informational only. true when the origin’s host is localhost, 127.0.0.1, or *.localhost. |
originTrusted |
The origin’s host doesn’t pass the trust rule for its kind: local origins need allowLocalOrigins (or localhost explicitly trusted); everything else needs a label-boundary match against a trusted domain. |
wouldAccept is domainOwnedByProject && originTrusted. When it’s false, reasons spells out which checks failed in plain English, so you don’t have to reverse-engineer the table by hand.
What /v1/validate can’t see
Section titled “What /v1/validate can’t see”It is a pure server-side check of the domain and Origin acceptance rules. It cannot tell you whether the request ever reached the server in the first place.
Content-Security-Policy
Section titled “Content-Security-Policy”A CSP that doesn’t allow the snippet or the API blocks the request in the browser before it is sent, so nothing shows up server-side to validate against. Add both origins:
Content-Security-Policy: script-src 'self' https://cdn.analytics.canarycoders.es; connect-src 'self' https://api.analytics.canarycoders.es;Adblockers
Section titled “Adblockers”Ad and tracker blockers may block lytics.js itself or the /ingest request by URL pattern. This is also invisible to /v1/validate, since the check runs against a hypothetical request you provide rather than observing what actually left a real visitor’s browser.
The daily event cap
Section titled “The daily event cap”Once a project’s daily event count exceeds its cap, further events are dropped silently: same 204, no signal to the client. Check current usage against the cap on the dashboard’s Settings page. This one isn’t exposed via /v1 or the SDK, only the dashboard UI.
Still stuck?
Section titled “Still stuck?”Confirm GET /v1/validate with the exact domain/origin pair a real visitor’s browser would send. A mismatch there is the only failure mode this endpoint is guaranteed to catch. Everything else on this page fails client-side, before the server ever sees a request.