What
POST /api/{project_id}/envelope/ returns 401 when the Sentry key is supplied as a URL query parameter, and 200 only when auth arrives via the X-Sentry-Auth header. Sentry JS-family SDKs (@sentry/node, @sentry/bun, @sentry/browser; verified with @sentry/bun 10.75.2) send auth as a query string — ?sentry_key=...&sentry_version=7 (see _encodedAuth() / getEnvelopeEndpointWithUrlEncodedAuth() in @sentry/core api.js) — and set no auth header at all. Result: every event from a stock JS SDK is rejected.
Evidence (2026-09-22, live on trapfall.ajianaz.dev v0.3.0, identical envelope payloads)
| Auth style |
Result |
?sentry_key=<key>&sentry_version=7 (JS SDK style) |
401, empty body |
X-Sentry-Auth: Sentry sentry_key=<key>, sentry_version=7 |
200, event ingested |
| no auth (control) |
401 |
Cross-check with installed SDK source: @sentry/core/build/cjs/api.js builds the ingest URL with query-encoded auth; the Bun fetch transport sends only Content-Type. The Rust SDK uses the X-Sentry-Auth header, which is why the rungu/cira integrations work today.
Impact
- Stock JS/TS SDK consumers (the largest Sentry SDK family) cannot ingest at all: silent event loss (SDK logs a transport error, dashboard stays empty).
- Blocks the gatehouse (Bun) integration — currently bridged per-app via
tunnel + transportOptions.headers (works, but every JS consumer must re-implement the bridge).
Suggested fix
In the ingest auth check, accept the Sentry key from either the X-Sentry-Auth header (current behavior) or the sentry_key query parameter (JS SDK style): key = header_value.or_else(|| query_param("sentry_key")). Keep the 401 for missing/unknown keys either way.
What
POST /api/{project_id}/envelope/returns 401 when the Sentry key is supplied as a URL query parameter, and 200 only when auth arrives via theX-Sentry-Authheader. Sentry JS-family SDKs (@sentry/node,@sentry/bun,@sentry/browser; verified with@sentry/bun10.75.2) send auth as a query string —?sentry_key=...&sentry_version=7(see_encodedAuth()/getEnvelopeEndpointWithUrlEncodedAuth()in@sentry/coreapi.js) — and set no auth header at all. Result: every event from a stock JS SDK is rejected.Evidence (2026-09-22, live on trapfall.ajianaz.dev v0.3.0, identical envelope payloads)
?sentry_key=<key>&sentry_version=7(JS SDK style)X-Sentry-Auth: Sentry sentry_key=<key>, sentry_version=7Cross-check with installed SDK source:
@sentry/core/build/cjs/api.jsbuilds the ingest URL with query-encoded auth; the Bun fetch transport sends onlyContent-Type. The Rust SDK uses theX-Sentry-Authheader, which is why the rungu/cira integrations work today.Impact
tunnel+transportOptions.headers(works, but every JS consumer must re-implement the bridge).Suggested fix
In the ingest auth check, accept the Sentry key from either the
X-Sentry-Authheader (current behavior) or thesentry_keyquery parameter (JS SDK style):key = header_value.or_else(|| query_param("sentry_key")). Keep the 401 for missing/unknown keys either way.