DSN and the region model
Format
https://<publicKey>@<region>.<ingestHost>/<projectId>
Example: https://a1b2c3d4@us.ingest.example.com/clx0project0id
| Part | Rules |
|---|---|
| scheme | https (http is tolerated for local development only) |
publicKey |
[A-Za-z0-9_-]+, unique across the install |
| password half | must be empty — a DSN carrying a secret is rejected as malformed |
region |
first hostname label; [a-z][a-z0-9-]{0,15} |
ingestHost |
everything after the region label, port included |
| path | exactly one segment, the project id |
| query / fragment | must be absent |
Parsing lives in domain/src/lib/dsn.ts (parseDsn / buildDsn) and, for SDKs, in
shared/src/protocol/dsn.ts. create-bucker re-implements the same rules locally
so that npx pulls down zero dependencies.
The public key is not a credential for anything but ingest
ProjectKey.publicKey appears in client bundles by design. It authenticates ingest
only — it cannot read an issue, list a project, or call the control plane. There is
no secret half to protect, which is why listing keys back to project members is safe
and why the onboarding endpoint can return the DSN.
Legacy Sentry DSNs carried a secret in the password slot. Bucker DSNs never do, and
parseDsn rejects one that tries.
Why the region prefix exists on day one
The first hostname label is the data region. Adding an EU region later is then purely additive — a new hostname, the same identifier scheme — instead of a migration that rewrites every customer's DSN and every config file that quotes it.
The DSN is derived, never stored: key.service.ts builds it from
{ publicKey, projectId, region } on every read. Changing INGEST_HOST or
INGEST_REGION propagates to every key immediately.
Today only us and eu are accepted values for the server's own INGEST_REGION, and
only one region actually runs. The parser accepts any well-formed region label, so
SDKs and the CLI are already forward-compatible.
Ingest endpoint
POST https://<region>.<ingestHost>/api/<projectId>/envelope/
Content-Type: application/x-sentry-envelope
The bare form without the trailing slash is registered too, so curl and hand-rolled
clients do not hit a surprise 404.
Authentication is the public key, sent either as the sentry_key query parameter or
inside the envelope header's dsn field. The browser SDK uses the query parameter
deliberately: it keeps the request CORS-simple, so there is no preflight round trip in
front of every event.
A 200 carries { "id": "<eventId>" } and an X-Bucker-Ingest-Disposition header.
It means the envelope was accepted and queued, not that an issue exists yet —
that happens when a worker drains the queue.
Configuration
| Variable | Default | Meaning |
|---|---|---|
INGEST_REGION |
us |
Region label this install stamps into DSNs |
INGEST_HOST |
localhost:4000 |
Ingest host without the region prefix |
INGEST_INLINE_MAX_BYTES |
65536 |
Above this, the raw envelope goes to blob storage instead of the row |
INGEST_MAX_ENVELOPE_BYTES |
20971520 |
Hard reject above this, with an accounted too_large outcome |