Next.js 16.4 Cache Components: will a Sume webhook route break?

Next.js 16.4 turns on Cache Components in create-next-app. A POST route handler is never cached, so a Sume webhook route works if it reads the raw body.

4 min readSume
All posts

No, a Sume webhook route does not break under Next.js 16.4. The release recommends Cache Components for every app and makes create-next-app enable it by default, but the Route Handlers docs say only GET handlers can opt into caching. A POST handler is never cached, so the route that receives a Sume job.completed delivery runs on every request.

The one thing that can still break it is the body. Sume signs the raw bytes, so the handler must read await request.text() before it parses anything. This post lists what changed in 16.4, what stays the same for webhooks, and the route to ship.

What Next.js 16.4 changed

The 16.4 post was published on 2026-10-06. It recommends Cache Components for every app and says the feature becomes the default in Next 17. New projects from create-next-app get it turned on now.

The Route Handlers page (version 16.4.0) states that handlers are not cached by default, that only GET can use export const dynamic = 'force-static', and that other methods are never cached. With Cache Components, GET handlers run at request time by default, and prerendering stops when the handler reads request.body or headers().

Next.js 16.4 facts that touch a webhook route, read 2026-10-08
FactValueEffect on a Sume receiver
Release date of 16.42026-10-06Check your lockfile before you upgrade
Cache Components in create-next-appOn by defaultNew apps start with it
Default in Next 17YesPlan the move now
POST route handlers cachedNeverWebhook route runs on each delivery
use cache in handler bodyNot allowed directlyKeep it out of the receiver

What Sume requires of the receiver

A Sume job webhook is a POST with the events job.completed, job.failed and job.canceled. Sume signs <timestamp>.<raw_body> with HMAC-SHA256 and sends x-sume-webhook-timestamp and x-sume-webhook-signature: sume-v1=<hex>. The default replay window is 300 seconds.

Sume tries up to 10 times, 30 seconds apart, with a 10 second timeout per attempt, so return a 2xx after you store the event. Dedupe on job_id.

The route to ship

verifyWebhook from @sume-com/sdk is async and returns false instead of throwing. It handles the multi-signature header that Sume sends during a secret rotation. The secret check below refuses an empty value, so a missing env var fails closed.

import { verifyWebhook } from "@sume-com/sdk";

export async function POST(request: Request) {
  const secret = process.env.SUME_COM_WEBHOOK_SIGNING_SECRET;
  if (!secret) return new Response("not configured", { status: 500 });

  const body = await request.text(); // raw bytes, before JSON.parse
  const ok = await verifyWebhook({ body, headers: request.headers, secret });
  if (!ok) return new Response("bad signature", { status: 401 });

  const event = JSON.parse(body);
  // store event.job_id durably first, then return
  console.log(event.event, event.job_id);
  return new Response(null, { status: 204 });
}

Upgrade checklist

Keep the receiver outside any use cache scope and do not wrap it in a cached helper.

  • Read the body with request.text() first; never request.json().
  • Store the secret as SUME_COM_WEBHOOK_SIGNING_SECRET.
  • Send a POST /v1/webhooks/test-deliveries call after the upgrade to confirm the route still verifies.
  • Keep polling status_url as a fallback for deliveries that never arrive.

Where caching can still bite

The webhook route itself is safe, but the code around it may not be. Cache Components moves the default toward cached or prerendered work, so any helper the route calls for the job state is a risk. A function that reads your database for the job row and is wrapped in use cache would return an old row after the event arrives.

Keep the write path uncached. The read that renders the result page can be cached with a short life, and you can revalidate it from the webhook route once the event is stored. The order matters: store, then revalidate, then return the 2xx within the 10 second per-attempt limit that Sume enforces.

Run the app, call POST /v1/webhooks/test-deliveries with the URL of the route, and check that the dummy webhook.test returns 204. Then submit one cheap job with mode: "webhook" and compare the stored job_id with the job record. If the test passes and the real event fails, the difference is almost always the body: a middleware or a proxy rewrote it.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume