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.

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).
| Question | What the entry says |
|---|---|
| Setting | skipMiddlewareRequestBody, set in vercel.json or vercel.ts |
| Default | false, so existing projects do not change until you turn it on |
| Effect | The client request body is not sent to Routing Middleware |
| Still receive the body | Vercel Functions and rewrite targets |
| Stated benefit | Less Fast Origin Transfer, and better time to first byte for large bodies |
| Caveat | Enable it only if your Routing Middleware does not read request bodies |
| Rollout | Redeploy 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.
| Item | Value |
|---|---|
| Events | job.completed, job.failed, job.canceled |
| Headers | x-sume-webhook-timestamp, x-sume-webhook-signature |
| Retries | Up to 10 attempts, 30 s apart by default, 10 s timeout each |
| Idempotency key on your side | job_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()andclone(). 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
- Verify a canceled Sume job was refunded: read the usage ledger by job
After you cancel a queued job, check GET /v1/usage with job_id and read the row status: reserved, captured or refunded. A Python script prints the answer.
- A video model row missing from Sume's list: why, and how to check
Veo and Genjutsu list in the Sume catalog only where their provider route is configured. How listing works, plus a Python check that fails on a missing id.
- Video-trim says unsupported_media_source: which Sume routes take URLs
Trim, filter and compose need a media.sume.com clip; upscale, STT, RMBG and captions take public HTTPS URLs. Imports take TikTok and Instagram. Read 2026-10-10.
- What to log from a Sume API error: request_id, code, no secrets
Log the status, error.code, error.request_id, retry-after and the path without its query. Keep keys, signed URLs and media URLs out. A 25-line Python logger.
Written by Sume