Sentry DSN ingest
Point an existing Sentry SDK at axonpush by changing one environment variable, the envelope routes, how each item type is normalised, and the two things that will surprise you.
If a Sentry client is already in your stack, axonpush can be the endpoint it posts to. The DSN is the only thing that changes. Errors, transactions, CSP reports, minidumps, check-ins and session envelopes all arrive as axonpush events on the same timeline as everything else.
Sentry ingest is behind the sentry_ingest feature flag and that flag is
off by default. On a deployment where it has not been turned on, all four
routes below return 404. If your envelopes are vanishing and the DSN looks
right, this is the first thing to check.
DSN format
SENTRY_DSN=https://<key>@api.axonpush.xyz/<channelId><key>, an API key (ak_…) or a public ingest token (pt_…). Use apt_…token in browser and mobile clients; anak_…key shipped to a client is a key everyone has.<channelId>, the axonpush channel the events land in. It occupies the slot Sentry calls the project ID, and it is an opaque string, not a number.
Wiring it up
from axonpush.integrations.sentry import install_sentry
install_sentry(
api_key="ak_...",
channel_id="ch_...",
environment="prod",
release="checkout@1.4.2",
traces_sample_rate=0.2,
)The Python and TypeScript SDKs each ship a helper that builds the DSN and
forwards every other option through to sentry_sdk.init / Sentry.init
untouched, so nothing about your existing Sentry configuration has to change.
Both fall back to AXONPUSH_API_KEY, AXONPUSH_CHANNEL_ID and AXONPUSH_HOST
when an argument is omitted. Both also auto-detect an environment when you do
not pass one, Python reads AXONPUSH_ENVIRONMENT, SENTRY_ENVIRONMENT,
APP_ENV, ENV; TypeScript reads the same list with NODE_ENV inserted after
SENTRY_ENVIRONMENT. That NODE_ENV fallback is a trap worth knowing about,
read the environments section below before you rely on it.
The .NET SDK has no Sentry helper. Point Sentry.SentryOptions.Dsn at the DSN
above directly.
Routes
Every endpoint the official clients use is implemented:
| Endpoint | Used for |
|---|---|
POST /api/{channelId}/envelope | The envelope protocol, events, transactions, sessions, check-ins, attachments. |
POST /api/{channelId}/store | The legacy single-event endpoint. |
POST /api/{channelId}/security | Browser-posted CSP / Expect-CT / Expect-Staple reports. |
POST /api/{channelId}/minidump | Native crash minidumps. |
Envelopes are decompressed when Content-Encoding says so. Anything over
6 MB is rejected with 413.
Where the key is read from
Three sources, first match wins:
X-Sentry-Auth: Sentry sentry_key=<key>, …, what modern SDKs send.Authorization: DSN <dsn>, the legacy form. The key is parsed out of the DSN’s userinfo.?sentry_key=<key>, used by browser CDN builds that cannot set headers.
How items map to events
| Sentry item | axonpush event type | Notes |
|---|---|---|
event with exception.values[] | agent.error | |
event without exceptions | app.log | |
transaction | app.span | One event for the transaction, plus one per entry in spans[]. |
check_in | custom | metadata.sentryItemType = "check_in", metadata.checkIn = true |
session / sessions | custom | metadata.sentryItemType = "session", metadata.session = true |
attachment | app.log | Base64 in payload.attachment.data. |
csp-report | app.log | metadata.sentryItemType = "csp", severity WARN. |
minidump | agent.error | Base64 in payload.minidump. |
contexts.trace.trace_id and contexts.trace.span_id are lifted onto the
event, so a Sentry transaction and an OTLP span from the same request join into
one trace. Child spans inherit the transaction’s span as their parent when they
do not name one.
level maps to OTel severity so Sentry-sourced logs sort correctly against
structured logs from Pino, Winston or stdlib logging:
| Sentry level | severityNumber | severityText |
|---|---|---|
debug | 5 | DEBUG |
info | 9 | INFO |
warning | 13 | WARNING |
error | 17 | ERROR |
fatal | 21 | FATAL |
An event with no level is treated as error.
Attachment and minidump truncation
Payloads up to 1 MiB are stored base64-encoded. Above that the bytes are
dropped and the record carries truncated: true with the real sizeBytes, so
you still see that a large minidump happened without the store carrying it. If
you routinely exceed the cap, upload to your own object store and reference the
URL from the event.
Releases
When an incoming event carries a release, axonpush upserts a release record
for the app automatically. That is what makes release usable as an
alert-rule filter, without your having to register releases
separately.
Environments: the thing that surprises people
The environment field on a Sentry event is treated as an environment
override, exactly as if it had arrived in X-Axonpush-Environment. That
means the environment resolution rules apply, and one
of them rejects rather than ignores:
An API key pinned to an environment, without
allowEnvironmentOverride, that receives a different environment, returns400 env_override_forbidden.
Sentry SDKs send environment by default, and installSentry in the
TypeScript SDK will fill it from NODE_ENV if you do not. So a key pinned to
prod, in a Node process where NODE_ENV=production, fails on every request,
because production is not the slug prod. Three ways out, in order of
preference:
- Set the Sentry client’s
environmentto the exact axonpush slug (prod,dev,staging). - Leave
environmentunset in the Sentry client and let the key decide. - Set
allowEnvironmentOverride: trueon the key, and create every slug your clients send.
An environment slug that does not exist returns 400 unknown_environment, with
the known slugs listed in the response body.
None of this applies to a public ingest token. A pt_… token is pinned to
one environment and the environment field on the event is ignored outright,
no override, no rejection. Browser clients are therefore immune to this class of
failure.
Response shape
Success returns the event ID Sentry clients expect:
{ "id": "9f2c4e1b7a3d4f5e8c0b1a2d3e4f5a6b" }Items that were accepted but not mapped come back alongside it:
{
"id": "9f2c…",
"partialSuccess": {
"rejectedItems": 2,
"reasons": [
{ "type": "profile", "reason": "not-mapped" },
{ "type": "surprise", "reason": "unknown-type" }
]
}
}Item types accepted and dropped as not-mapped: client_report, statsd,
profile, profile_chunk, replay_event, replay_recording. Anything else
is reported as unknown-type. Because the response is a 200, the Sentry SDK
does not retry them, leaving them enabled costs bandwidth and nothing else.
Rate limiting
When the organisation’s ingest quota is exhausted, axonpush answers with the header Sentry clients already know how to back off on:
HTTP/1.1 429 Too Many Requests
X-Sentry-Rate-Limits: 60:error:organization, 60:transaction:organizationRelated
- Environments, the override rules in full
- OpenTelemetry over OTLP, the other wire format
- Get notified when your agent fails, turning an
agent.errorinto a page
OpenTelemetry over OTLP
OTLP/HTTP ingest in both JSON and protobuf, how a request is routed to a channel without a channel header, and how to forward the same events back out to a downstream collector.
Audit log
The append-only record of organisation-level change, what is captured, what is deliberately not, the entry shape, and how to page through it without misreading the totals.