verifyWebhook async: forget await and you get a promise
verifyWebhook in @sume-com/sdk is async. Without await, a Promise is truthy, so your signature check never rejects. The await, raw body and replay rules.

verifyWebhook in @sume-com/sdk is async, so you have to await it. Without the await you hold a Promise, and a Promise is always truthy in JavaScript, so if (!ok) never fires and a forged delivery passes your check. It is one of four rules the docs say decide whether verification works.
Why is verifyWebhook async?
The implementation uses WebCrypto rather than node:crypto. That keeps the package importable from Workers, Deno and bundlers that refuse node: specifiers. The cost is that the check cannot be synchronous.
What does the correct handler look like?
This is the docs shape: read the raw text first, await the check, and reject on false.
import { verifyWebhook } from "@sume-com/sdk";
export async function POST(request: Request) {
const body = await request.text(); // raw, before any JSON.parse
// Without await, ok is a Promise and `!ok` is always false.
const ok = await verifyWebhook({
body,
headers: request.headers,
secret: process.env.SUME_COM_WEBHOOK_SIGNING_SECRET!,
});
if (!ok) return new Response("bad signature", { status: 401 });
return new Response(null, { status: 204 });
}What does it return on a bad delivery?
It returns false rather than throwing. A missing header, a garbage timestamp and a wrong signature are all just failed verification, so there is one thing to branch on and no try/catch. That is also why the missing await is silent: nothing throws to tell you.
What else has to be right?
Set the secret from SUME_COM_WEBHOOK_SIGNING_SECRET, and refuse to start if it is empty.
| Rule | Detail |
|---|---|
| Raw body | A parsed-and-reserialized object does not verify |
| Replay window | toleranceSeconds defaults to 300; 0 skips the timestamp check |
| Comparison | Constant-time, with the replay window enforced before the HMAC is computed |
| No client | verifyWebhook takes no client and makes no request |
How do I catch a missing await in testing?
Send your route a request with a deliberately wrong signature and check that it answers 401. With the await missing, that request would pass. Then use Send test, which posts a dummy signed webhook.test payload, to confirm a genuine signature is accepted.
Do the framework details change anything?
The raw body rule is where frameworks bite. In Express, mount express.raw({ type: "application/json" }) on the webhook route only. In the Next.js App Router, call await request.text() before anything else. During a secret rotation the signature header can carry two sume-v1= entries, and verifyWebhook in @sume-com/sdk 0.2.0 already handles that.
Is a missing await ever caught by a linter?
That depends on your setup, and the Sume docs do not cover lint rules. A no-floating-promises style check in TypeScript flags an unawaited promise as a statement, but const ok = verifyWebhook(...) followed by if (!ok) is a use of the value, so it can slip through. The reliable guard is a test with a bad signature that expects 401, plus reading verifyWebhook as the one call in the handler that must always carry an await.
Sources
Related posts
More in Developers
- Validate a video filter program for free before you encode
POST /v1/video-filter/check runs the same validation as the encode with no job and no credits. See what it returns and what it cannot promise about the encode.
- unsupported_media_source: why the media API rejects your video URL
unsupported_media_source means video_url is not on the Sume media host. Import the clip first; which Sume video endpoints need a hosted URL and which don't.
- video_trim_range_conflict: send end or duration, not both
The video trim API returns video_trim_range_conflict when a body has both end and duration. Send start plus exactly one of them; other range errors explained.
- Wan 3.0 prompt guide for API users: prompt vs request fields
Write a Wan 3.0 prompt as a shot list with sound cues, and set length and size in request fields. What to put in the prompt and what to send to Sume's wan-3.0.
Written by Sume