Hono 4.13.13 deprecates app.mount(): mount a Sume webhook sub-app

Hono 4.13.13 deprecates app.mount() in favor of Mount Middleware. A Sume webhook receiver written as a Hono sub-app uses app.route() and keeps its raw body.

4 min readSume
All posts

Hono 4.13.13, released 2026-10-04, adds hono/mount as Mount Middleware and deprecates app.mount(). That method mounts a foreign fetch-style app. A Sume webhook receiver is a Hono route, so it uses app.route() and does not depend on mount at all. The fix for a receiver is to keep the verifier in its own sub-app and read the raw body inside it.

What changed in 4.13.13

The release notes name the new middleware and the deprecation. They do not say when app.mount() will be removed. Earlier notes (4.13.10) say hono/<adapter> imports are removed in v5, so plan for one clean-up pass.

Hono releases from 2026-09-28 to 2026-10-04, read 2026-10-08
VersionDateChange that matters here
4.13.102026-09-28Adapters split into @hono/* packages
4.13.112026-09-29serveStatic double-decode fix; % rejected unless allowPercentInPath: true
4.13.122026-09-30Patch release
4.13.132026-10-04hono/mount added; app.mount() deprecated

Where a Sume receiver fits

A Sume job webhook carries job.completed, job.failed or job.canceled, signed over <timestamp>.<raw_body>. The verifier must see the exact bytes, so keep it in a sub-app with no body-parsing middleware ahead of it.

Mount order matters: register any JSON middleware on other sub-apps, not on the app that handles /hooks.

A sub-app for the receiver

app.route() is the supported way to compose Hono apps. The sub-app below refuses to run without a secret.

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

export const hooks = new Hono<{ Bindings: { SUME_COM_WEBHOOK_SIGNING_SECRET?: string } }>();

hooks.post("/sume", async (c) => {
  const secret = c.env?.SUME_COM_WEBHOOK_SIGNING_SECRET ?? globalThis.process?.env?.SUME_COM_WEBHOOK_SIGNING_SECRET;
  if (!secret) return c.text("not configured", 500);
  const body = await c.req.text();
  if (!(await verifyWebhook({ body, headers: c.req.raw.headers, secret }))) {
    return c.text("bad signature", 401);
  }
  const { event, job_id } = JSON.parse(body);
  console.log(event, job_id);
  return c.body(null, 204);
});

// in the main app: app.route("/hooks", hooks);

After you deploy

Check the route with a signed dummy, then wait for a real job.

  • POST /v1/webhooks/test-deliveries sends a signed webhook.test to the URL you give it.
  • Return a 2xx after you store the event; Sume retries up to 10 times at 30 second spacing.
  • Use POST /v1/jobs/{job_id}/webhook/redeliver to replay a real terminal event with a fresh signature.

Why a sub-app is the safer shape

A receiver has one job: read bytes, verify, store, answer. Putting it in its own Hono instance keeps that job away from middleware that other routes need, such as a JSON body parser, a CORS layer or a logger that reads the body. Any one of those can consume the stream before the verifier sees it, and then every signature check fails with no obvious cause.

With app.route("/hooks", hooks) the sub-app keeps its own middleware stack. Order inside the sub-app still counts, so register nothing before the handler that touches the body.

The 4.13.10 notes say hono/<adapter> imports are removed in v5, and 4.13.13 deprecates app.mount(). Neither affects a plain route. Search your code for both once, record the result in your upgrade notes, and move on. If you do use app.mount() to host another fetch-style app, read the 4.13.13 notes for the Mount Middleware replacement before you change anything.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume