Guide Fundamentals ~8 min read Updated June 14, 2026

What is a Sentry envelope? The wire format behind SDK compatibility

A Sentry envelope is the newline-delimited JSON request body an SDK uses to ship events, attachments, transactions, sessions, and other telemetry to an ingest server. It is the wire contract that defines what "Sentry-compatible" actually means.

TL;DR

20 seconds. An envelope is the outer wrapper a Sentry SDK uses to deliver telemetry. It is a plain UTF-8 body of newline-separated JSON: one envelope header, then one or more item pairs (item header plus item payload). The body is POSTed to /api/{project_id}/envelope/. Events are one kind of item; attachments, transactions, sessions, replays, profiles, and check-ins are others. One envelope can carry several at once.

60 seconds. Before envelopes, every payload type had its own endpoint and its own request shape. Sentry introduced the envelope format around SDK v7 to unify ingest, support multi-payload requests, and stream large attachments without a separate upload step. The format is documented at develop.sentry.dev/sdk/envelopes/ and every modern Sentry SDK speaks it. A backend that claims Sentry-SDK compatibility is really claiming envelope compatibility: parse the header, dispatch each item by type, return the correct status code, and honour the X-Sentry-Rate-Limits response header. Backends that only understand the legacy /store/ endpoint silently drop attachments, sessions, and profiles.

This guide covers the envelope structure, the item types you will see on the wire, why Sentry replaced /store/ with /envelope/, how the SDK builds and ships an envelope, the rate-limit response that lives on this endpoint, and the two encoding gotchas that quietly drop events.

The definition

A Sentry envelope is the request body format a Sentry-compatible SDK uses to deliver one or more telemetry items in a single HTTP POST.

The body is plain UTF-8 text. It begins with one JSON object on a line by itself: the envelope header. After that, items follow in pairs. Each pair is a JSON item header on one line, then the item payload on the next. The payload may be JSON, may be binary attached as a base64-encoded value, or may be a length-prefixed binary blob, depending on the item type. Newline characters (\n, byte 0x0A) separate every line.

The endpoint is derived from the DSN. For a DSN of https://KEY@errors.example.com/4, the envelope POST goes to https://errors.example.com/api/4/envelope/. The X-Sentry-Auth header carries the public key from the DSN and the SDK metadata. The Content-Type is application/x-sentry-envelope; the content is often gzipped, with Content-Encoding: gzip set accordingly.

The format was specified by Sentry and is open: the reference lives at develop.sentry.dev/sdk/envelopes/. GlitchTip, Bugsink, and urgentry all implement the same parser because the SDKs send the same bytes regardless of which backend the DSN points at.

The anatomy of an envelope

An envelope has exactly one header line, followed by zero or more item pairs. A pair is two lines: an item header, then the item payload.

{"event_id":"9ec79c33ec9942ab8353589fcb2e04dc","dsn":"https://KEY@errors.example.com/4","sent_at":"2026-06-14T10:00:00Z"}
{"type":"event","length":195,"content_type":"application/json"}
{"event_id":"9ec79c33ec9942ab8353589fcb2e04dc","timestamp":1718360400.0,"platform":"python","level":"error","exception":{"values":[{"type":"ValueError","value":"bad input"}]}}
{"type":"attachment","length":12,"filename":"trace.txt","content_type":"text/plain"}
hello, world

Three things are worth knowing about the framing:

  • The envelope header is required. It carries the event_id (a 32-character hex string without dashes), the optional dsn, and the optional sent_at timestamp. The event_id is what the SDK returns to user code so the application can log "your error was reported as X."
  • Each item header declares its payload. The type field tells the server how to dispatch the item. The length field is the byte length of the payload that follows; when present, the server reads exactly that many bytes and does not look for a newline. When length is absent, the server reads up to the next newline. Length-prefixing is what lets binary attachments coexist with JSON in one body.
  • Newlines separate lines, not items. The first newline after the envelope header is mandatory. The newline after each item header is mandatory. The newline after each item payload is recommended but not strictly required for the last item. This is the source of the trailing-newline gotcha discussed later.

Item types you will see on the wire

The type field on an item header dispatches the payload to the right pipeline. The set is open, which is part of how Sentry adds new telemetry without breaking existing SDKs, but the canonical types every Sentry-compatible backend should accept are:

  • event. A single error or message event. This is the original Sentry payload: exception type, stack trace, breadcrumbs, tags, contexts, user information.
  • transaction. A performance-monitoring trace root with one or more spans. Used for distributed tracing. Always travels in an envelope; never sent to the legacy /store/ endpoint.
  • attachment. Arbitrary binary data attached to an event. Minidumps, logs, screenshots, source files. Length-prefixed so binary bytes do not have to be escaped.
  • session. A release-health session update. The SDK sends start, update, and exit events so the backend can compute crash-free sessions and crash-free users per release.
  • profile. A continuous profiling payload, typically pprof or Sentry's own profiling JSON. Attached to a transaction by trace ID.
  • replay_event and replay_recording. Session-replay metadata and the rrweb-style event stream. Two items per replay segment, with the recording item length-prefixed.
  • check_in. A cron-monitor heartbeat. Lightweight payload that records "this scheduled job ran" or "this scheduled job is overdue."
  • client_report. The SDK telling the backend how many events it dropped client-side and why. Used for accurate event-volume accounting when sampling or rate-limits cause local drops.

A backend that knows about event but not session will silently fail release-health features. A backend that does not parse length-prefixed items cannot accept attachments or replays. This is why the envelope spec, not the event spec, is the real compatibility surface.

Why Sentry replaced /store/ with /envelope/

The /api/{project_id}/store/ endpoint accepted one JSON event per request. It worked for the first decade of Sentry but accumulated three concrete problems as the SDK feature set grew.

First, every payload type needed its own endpoint. Attachments went to /api/{project_id}/events/{event_id}/attachments/. Sessions went to /api/{project_id}/sessions/. Profiles went to /api/{project_id}/profiling/. An SDK that wanted to send a crash report with a minidump and a release-health session update made three separate POSTs, three TLS handshakes, three round trips. Wire overhead dominated the cost on slow mobile networks.

Second, the SDK had no way to atomically deliver a logical bundle. If the attachment POST failed after the event POST succeeded, the backend had an event with no minidump, and the SDK had no good retry strategy. The envelope shape lets the SDK declare "here is an event and its attachments and a session update, all in one request." The backend either accepts the bundle or it does not.

Third, length-prefixed binary inside a multipart-style body is much cheaper than base64 inside JSON. A 500 KB minidump becomes a 500 KB section of the body, not a 670 KB base64 string inside a JSON value. For replays, which can be several megabytes of rrweb data per segment, the saving compounds fast.

The envelope format also opens the door to streaming. A long-running profile or a large replay can be appended to an envelope and shipped without rewriting the whole JSON document. SDKs that stream do not yet exist in mainline Sentry, but the format supports it.

How the SDK builds and ships an envelope

The build path is the same across Sentry-Python, Sentry-JavaScript, Sentry-Go, Sentry-Java, and the others, because the format is shared. A simplified view of what every SDK does:

  1. Generate a 32-character hex event ID. Stash it on the event for the user-code return path.
  2. Serialize the event to JSON. Apply scrubbers, tag normalisation, breadcrumb truncation, and any user-supplied before_send hook.
  3. Build the envelope header with the event ID, the DSN, and a current UTC timestamp.
  4. For each payload (event, attachment, session, profile, replay), build an item header and append the serialized payload. Set length on binary items.
  5. Join everything with newlines. Gzip the body if it is above a small threshold.
  6. POST to the envelope endpoint with X-Sentry-Auth and the right content headers.
  7. Inspect the response. A 200 means the server accepted the bundle. A 429 with X-Sentry-Rate-Limits means back off for specific categories. A 4xx with X-Sentry-Error means the bundle was malformed.

The SDK does not wait for the backend to parse each item. The envelope is sent as one body. On the backend, urgentry parses the header, splits the body into items, dispatches each item type, and returns the aggregate status. A partial-acceptance outcome (one item rejected, others accepted) is possible at the protocol level, and the response payload carries per-item details when the spec opts in to that.

The X-Sentry-Rate-Limits envelope-aware response

The envelope endpoint is also where backpressure lives. When the backend wants the SDK to slow down, it returns the X-Sentry-Rate-Limits response header. The header value is a list of rules, each carrying a retry-after duration in seconds, the categories the rule applies to (events, transactions, sessions, attachments, all), and an optional reason code.

X-Sentry-Rate-Limits: 60:transaction:organization, 120:attachment:project

Reading this: hold off transactions for 60 seconds, hold off attachments for 120 seconds, ship everything else normally. The SDK respects the rule per category, which is finer-grained than a blanket 429. A flood of error events does not block crash-attachment delivery for the next minute, because the rule names a category.

This is the protocol-correct backpressure channel and the only one the SDK is built to read. A backend that returns a plain 429 without the header forces the SDK into its default exponential backoff, which throws away events the backend would have accepted under different rules. The rate-limit guide at /guides/self-hosting/rate-limits-and-quotas/ walks through the four absorption layers and where the header sits in each.

The envelope as the real compatibility surface

When urgentry calls itself Sentry-SDK compatible, the practical claim is that the envelope endpoint parses every standard item type, returns the right status codes, and emits the right rate-limit header. The DSN tells the SDK where to send. The envelope tells the backend what was sent. If either side of that contract slips, the SDK either silently drops data or fails noisy.

Three concrete tests for "is this backend really envelope-compatible":

  1. POST an envelope containing one event item, one attachment item with a 100 KB binary payload, and one session item. Expect a 200 with all three accepted.
  2. POST an envelope with a type the backend does not recognise. Expect a 200, with the unknown item silently ignored. Strict rejection breaks forward compatibility because every Sentry release adds new types.
  3. Drive enough traffic to trigger rate limiting. Inspect the response for X-Sentry-Rate-Limits with category-scoped rules. A bare 429 without the header is non-compliant.

The compatibility matrix at /sentry-alternative/ includes the envelope endpoint as a single line, but the line stands in for hundreds of lines of parser code. urgentry passes the three tests above against the Sentry SDK test corpus on every release.

The two encoding gotchas worth knowing

Trailing newlines and item length

The spec says the last item does not need a trailing newline, but SDKs differ. Sentry-JavaScript ships envelopes with no trailing newline. Sentry-Python ships envelopes with a trailing newline. A parser that treats the body as strictly header\nitem_header\nitem_payload\nitem_header\nitem_payload and rejects anything else will accept Python events and reject JavaScript events.

Equally, a parser that uses splitlines() on the whole body and ignores length in item headers will silently corrupt any binary attachment that happens to contain a newline byte. The parser must honour length when present and only fall back to newline splitting when it is absent.

Content-Type and gzip

The canonical content type is application/x-sentry-envelope. Some older SDKs send application/octet-stream. A strict backend that rejects anything other than the canonical type will drop legacy traffic. The pragmatic rule is to accept both, and to use the content type only as a routing hint, not as validation.

The body is often gzipped, indicated by Content-Encoding: gzip. A backend that forgets to decompress before parsing will see what looks like a malformed envelope. Always honour the encoding header before reading the body.

Frequently asked questions

What is a Sentry envelope?

A Sentry envelope is the newline-delimited JSON request body that a Sentry-compatible SDK sends to the ingest server. It carries one envelope header followed by one or more item pairs, where each item is itself a small header and a payload. Events, transactions, attachments, sessions, replays, and check-ins all travel inside envelopes.

Why did Sentry replace the /store/ endpoint with /envelope/?

The /store/ endpoint took one JSON object per request, which meant attachments, transactions, and session updates each needed their own HTTP call. The envelope format packs many items into one request and supports streaming, which dropped ingest overhead and let the SDK ship richer telemetry without N times the network cost.

How does an envelope differ from an event?

An event is one item type that can travel inside an envelope. The envelope is the outer wrapper that may hold one event, several attachments, a session update, a profile, or any combination. A backend that only knows about events but not envelopes cannot accept modern Sentry SDK traffic.

Does urgentry accept the same envelope format as Sentry?

Yes. urgentry accepts the same newline-delimited envelope payload at /api/{project_id}/envelope/ that any Sentry-compatible SDK sends. That is what compatibility means in practice: the SDK ships the same bytes, and urgentry parses them with the same item dispatch rules.

Can I inspect what my SDK is sending?

Yes. Most SDKs expose a debug log that prints the envelope before it leaves the process. You can also point the DSN at a request-bin or a local capture tool to see the raw bytes. The format is plain text JSON delimited by newlines, so it reads cleanly without special tooling.

Sources

  1. Sentry envelope specification — the authoritative reference for the envelope shape, item header fields, and the canonical content type.
  2. Sentry envelope item types — the catalogue of item types every compatible SDK and backend should support, including event, transaction, attachment, session, profile, replay, and check-in.
  3. Sentry rate-limit protocol — how the X-Sentry-Rate-Limits response header is structured and how the SDK is expected to interpret category-scoped rules.
  4. getsentry/relay — the open-source Rust ingest service that owns the canonical envelope parser; useful as the reference implementation.
  5. urgentry compatibility matrix — the audited mapping of Sentry endpoints to urgentry handlers, including the envelope endpoint at /api/{project_id}/envelope/.

One DSN swap. Full envelope-format compatibility.

urgentry parses the same Sentry envelope a Sentry SDK ships, on a single Go binary against SQLite. Events, transactions, attachments, sessions, and check-ins all route through the same ingest path. Change one environment variable and the bytes start arriving.