All Glossary Terms
HTTP & Concept

What is a raw body?

The raw body is the exact byte sequence of an HTTP request — required for webhook signature verification before any parsing. The source signed `HMAC_SHA256(secret, raw_body)`; if your code parses the body to a dict and reserializes via `JSON.stringify(parsed)`, whitespace, key order, and Unicode escapes can all differ, breaking the HMAC. Framework fixes vary: Express needs `express.raw({ type: 'application/json' })`, Fastify needs a buffer content-type parser, FastAPI uses `await request.body()`, Flask uses `request.get_data(cache=True)`, Rails uses `request.raw_post`. Always read raw bytes before any other middleware touches the request — body streams are typically single-use.

The raw body is the bytes the client sent, literally. Not a parsed JS object. Not a JSON re-serialization. The bytes themselves, as they came in over TCP. For webhooks, the raw body is a hard requirement because HMAC signature verification computes a hash over those exact bytes.

Why it matters: the source signed HMAC_SHA256(secret, raw_body). If your code parses the body to a dict and then reserializes JSON.stringify(parsed), the resulting bytes are *almost* the same — but not exactly. Whitespace differs. Key order may differ. Unicode escape sequences may be canonicalized. Numeric formatting may change (1.0 vs 1). Any of these changes alters the HMAC, and verification fails. Every Stripe integration hits this once.

The fix is framework-specific:

- Express/Node.js: app.use('/webhooks/stripe', express.raw({ type: 'application/json' })). The raw middleware passes req.body as a Buffer — the actual bytes. Don't pair this with express.json() on the same route. - Fastify: register a content-type parser that captures the raw body, e.g., fastify.addContentTypeParser('application/json', { parseAs: 'buffer' }, (req, body, done) => done(null, body)). - Hono/Deno/Bun: use await c.req.raw.arrayBuffer() or await c.req.text() before any .json() call. - Python (Flask): request.get_data(cache=True) returns the raw bytes. Beware: request.json triggers parsing and may consume the body stream depending on Werkzeug version. - Python (FastAPI): await request.body() returns bytes. Don't pair the route with a Pydantic body model — the model triggers parsing. - Go (net/http): io.ReadAll(r.Body) — but r.Body can only be read once, so cache it before any parsing middleware. - Ruby (Rails): request.raw_post. Skip Rails's automatic JSON parsing for the webhook route.

The other rule: read the raw body before doing anything else with the request. Body streams are typically single-use. If a logging middleware or auth middleware reads req.body first, the bytes may be consumed, and your verification handler sees an empty body. Order matters: raw body capture, then verify, then parse, then handle.

When debugging signature failures, log a fingerprint of the raw body — sha256(raw_body).slice(0, 8) — alongside the expected signature fingerprint. If the same request is processed by a working and broken code path, the body fingerprint differences will tell you exactly where reserialization is happening.

See Raw Body in real traffic

WebhookWhisper captures every webhook with full headers, body, signature, and timing — so concepts like raw body stop being abstract and become something you can inspect.

Start Free