Vercel skipMiddlewareRequestBody and a Sume webhook route

Vercel's Oct 8 skipMiddlewareRequestBody stops sending request bodies to Routing Middleware. What it means for a Sume webhook route that signs the raw body.

4 min readSume
All posts

Setting skipMiddlewareRequestBody to true in vercel.json or vercel.ts stops Vercel from forwarding the client request body to Routing Middleware, and your Functions still receive it. For a Sume webhook route that is safe, because the signature check happens in the route handler on the raw body, not in middleware. It is only a problem if some middleware in the project reads request bodies.

What Vercel announced

The facts below are from the Vercel changelog entry of 2026-10-08 (read 2026-10-10).

Vercel changelog, skipMiddlewareRequestBody, read 2026-10-10
QuestionWhat the entry says
SettingskipMiddlewareRequestBody, set in vercel.json or vercel.ts
Defaultfalse, so existing projects do not change until you turn it on
EffectThe client request body is not sent to Routing Middleware
Still receive the bodyVercel Functions and rewrite targets
Stated benefitLess Fast Origin Transfer, and better time to first byte for large bodies
CaveatEnable it only if your Routing Middleware does not read request bodies
RolloutRedeploy for the change to take effect

Why a Sume receiver cares

The Sume webhooks page says each delivery is signed over <timestamp>.<raw_body>, so the handler needs the exact bytes. A request that passes through an auth or geo middleware first is one more place where the body could be read, parsed, or replaced. Skipping the body for middleware means the only code that touches those bytes is the route that verifies them.

A Sume terminal event is a small JSON object, so the transfer savings are tiny for this route. The reason to set the flag is hygiene: middleware that does not need a body should not receive one. The setting is project-wide, so check every middleware before you flip it. I could not find a per-route form in the entry.

import { createHmac, timingSafeEqual } from "node:crypto";

export async function POST(req: Request): Promise<Response> {
  const secret = process.env.SUME_COM_WEBHOOK_SIGNING_SECRET;
  const ts = req.headers.get("x-sume-webhook-timestamp") ?? "";
  const header = req.headers.get("x-sume-webhook-signature") ?? "";
  const raw = await req.text(); // the route sees the body even with the flag on
  const fresh = Math.abs(Date.now() / 1000 - Number(ts)) <= 300;
  if (!secret || !fresh) return new Response(null, { status: 401 });

  const digest = createHmac("sha256", secret).update(`${ts}.${raw}`).digest("hex");
  const want = Buffer.from(`sume-v1=${digest}`);
  let ok = false;
  for (const entry of header.split(",")) {
    const got = Buffer.from(entry.trim());
    if (got.length === want.length && timingSafeEqual(got, want)) ok = true;
  }
  if (!ok) return new Response(null, { status: 401 });

  const event = JSON.parse(raw) as { event: string; job_id: string };
  // store event.job_id durably, then answer 2xx
  return Response.json({ received: event.job_id });
}

How I checked the handler

I ran this handler on Node with type stripping, signed a test body with a known secret, and called POST with a Request object. A header of sume-v1=00,sume-v1=<good> returned 200 with the job id, and a header holding only the wrong entry returned 401. I did not deploy it to Vercel, so the flag itself is described from the changelog, not from a run.

Delivery facts from the Sume webhooks page
ItemValue
Eventsjob.completed, job.failed, job.canceled
Headersx-sume-webhook-timestamp, x-sume-webhook-signature
RetriesUp to 10 attempts, 30 s apart by default, 10 s timeout each
Idempotency key on your sidejob_id

Checklist before you set the flag

Do these in order so a webhook outage does not teach you what your middleware does.

  • Search the middleware for request.json(), request.text(), formData() and clone(). Any hit means the flag changes behavior for that route.
  • Exclude the webhook path from the middleware matcher if you can. Then the middleware never sees it with or without the flag.
  • Send a test delivery from the Sume dashboard after the redeploy, and confirm the route answers 2xx.
  • Keep a status poll as a backup, since the docs say webhook delivery is an optimization and not the only recovery path.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume