Node 26 enters LTS in October 2026: pin your Sume webhook receiver

Node 26 moves to LTS this month and Node 27 Alpha starts. Pin the runtime that runs your Sume webhook receiver and prove it with a signed self-test.

4 min readSume
All posts

Pin your Sume webhook receiver to Node 26 once it reaches LTS this month, and keep Node 24 as the fallback until your own tests pass. According to the Node.js announcement, Node 26 (released April 2026) enters LTS in October 2026, and Node 27 starts its Alpha phase the same month. The post also says that if you already only upgrade to LTS versions, little changes beyond version numbering.

Why this matters for a receiver: it is the one process that must stay up while a paid job finishes, and it is usually deployed once and left alone. A runtime that reaches end of life without anyone noticing is the typical way such a service becomes a risk. Writing the end-of-life date for your chosen line into the runbook, next to the secret rotation date, is a cheap habit.

For a webhook receiver that is good news: the code that verifies a Sume signature uses only node:crypto and node:http, so the upgrade is a base-image change plus one test run.

The new schedule in one table

From October 2026 Node ships one major per year, and every release becomes LTS (no odd and even split). The dates below come from the announcement; the latest release numbers come from the release listing.

Node release plan relevant to a webhook receiver (read 2026-10-08)
LineWhat happensWhen
Node 26Moves from Current to LTSOctober 2026
Node 26Maintenance, then end of lifeMaintenance Oct 2027, EOL Apr 2029
Node 27Alpha beginsOctober 2026
Node 2727.0.0 release, LTS, end of lifeApr 2027, LTS Oct 2027, EOL Apr 2030
Phases per majorAlpha 6 months, Current 6 months, LTS 30 months36 months total

Steps to move the receiver

  • Keep the signing secret in SUME_COM_WEBHOOK_SIGNING_SECRET, the variable name the Sume webhook docs use, and refuse to start when it is empty.
  • Verify against the raw request bytes, never a re-serialized JSON body. Sume signs <timestamp>.<raw_body> with HMAC-SHA256 and sends x-sume-webhook-signature: sume-v1=<hex>.
  • Run the self-test below on Node 24 and Node 26 in CI. Treat Node 27 Alpha as an allowed-to-fail job, not a deploy target.
  • Change the image tag, deploy, then use Sume's test delivery to confirm the new runtime still returns a 2xx.

Self-test script

This script starts a receiver, signs a body the way Sume does, and checks that a tampered body is refused with a 401. It stops at startup if the secret is empty, which is the behavior a production verifier should have too.

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

const secret = process.env.SUME_COM_WEBHOOK_SIGNING_SECRET ?? "";
if (!secret) throw new Error("SUME_COM_WEBHOOK_SIGNING_SECRET is empty");
function verify(raw, h) {
  const ts = h["x-sume-webhook-timestamp"];
  if (!ts || Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false;
  const want = createHmac("sha256", secret).update(`${ts}.${raw}`).digest();
  return String(h["x-sume-webhook-signature"] ?? "").split(",").some((p) => {
    const got = Buffer.from(p.trim().replace(/^sume-v1=/, ""), "hex");
    return got.length === want.length && timingSafeEqual(got, want);
  });
}
const server = http.createServer(async (req, res) => {
  let raw = "";
  for await (const c of req) raw += c;
  res.statusCode = verify(raw, req.headers) ? 204 : 401;
  res.end();
});
server.listen(0, async () => {
  const body = JSON.stringify({ event: "job.completed", job_id: "j1" });
  const ts = String(Math.floor(Date.now() / 1000));
  const sig = createHmac("sha256", secret).update(`${ts}.${body}`).digest("hex");
  const url = `http://127.0.0.1:${server.address().port}`;
  const h = { "x-sume-webhook-timestamp": ts, "x-sume-webhook-signature": `sume-v1=${sig}` };
  console.log("signed:", (await fetch(url, { method: "POST", headers: h, body })).status);
  console.log("tampered:", (await fetch(url, { method: "POST", headers: h, body: body + " " })).status);
  server.close();
});

What Sume does not do

Sume does not pick your Node version, and the TypeScript SDK needs only fetch and WebCrypto, so it also runs on Node 18 and later, Bun, Deno and Workers. Sume retries a failed job delivery up to 10 times at about 30 second spacing, so a deploy that takes the receiver down for longer than that needs polling as a backup. See the webhook docs.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume