axonpush
Concepts

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 a pt_… token in browser and mobile clients; an ak_… 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:

EndpointUsed for
POST /api/{channelId}/envelopeThe envelope protocol, events, transactions, sessions, check-ins, attachments.
POST /api/{channelId}/storeThe legacy single-event endpoint.
POST /api/{channelId}/securityBrowser-posted CSP / Expect-CT / Expect-Staple reports.
POST /api/{channelId}/minidumpNative 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:

  1. X-Sentry-Auth: Sentry sentry_key=<key>, …, what modern SDKs send.
  2. Authorization: DSN <dsn>, the legacy form. The key is parsed out of the DSN’s userinfo.
  3. ?sentry_key=<key>, used by browser CDN builds that cannot set headers.

How items map to events

Sentry itemaxonpush event typeNotes
event with exception.values[]agent.error
event without exceptionsapp.log
transactionapp.spanOne event for the transaction, plus one per entry in spans[].
check_incustommetadata.sentryItemType = "check_in", metadata.checkIn = true
session / sessionscustommetadata.sentryItemType = "session", metadata.session = true
attachmentapp.logBase64 in payload.attachment.data.
csp-reportapp.logmetadata.sentryItemType = "csp", severity WARN.
minidumpagent.errorBase64 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 levelseverityNumberseverityText
debug5DEBUG
info9INFO
warning13WARNING
error17ERROR
fatal21FATAL

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, returns 400 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:

  1. Set the Sentry client’s environment to the exact axonpush slug (prod, dev, staging).
  2. Leave environment unset in the Sentry client and let the key decide.
  3. Set allowEnvironmentOverride: true on 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:organization