Nuxt Nitro server route for an AI video webhook with readRawBody

A Nuxt server/api route calls readRawBody, checks Sume's sume-v1 HMAC and answers 401 on a mismatch before it queues the finished video download.

4 min readSume
All posts

In a Nuxt server route (Nitro, built on h3), use readRawBody(event) to get the unparsed request body and getRequestHeader(event, name) for the signature headers. The h3 v1 docs say readRawBody returns a string by default and a Buffer if you pass a falsy encoding (h3 request utilities, read 2026-10-06). The string form is what a text HMAC wants.

This is the receiver for a Sume callback_url. Sume signs <timestamp>.<raw_body> with HMAC SHA-256 and sends sume-v1=<hex> in x-sume-webhook-signature (Sume webhooks guide, read 2026-10-06). Check h3's version: Nuxt 3 ships h3 v1, and later majors may rename helpers.

What does server/api/sume-hook.post.ts contain?

The .post.ts suffix limits the route to POST. createError with 401 stops the handler on a bad signature. The secret is read from the environment and an empty value is refused.

import { createHmac, timingSafeEqual } from 'node:crypto'

export default defineEventHandler(async (event) => {
  const secret = process.env.SUME_COM_WEBHOOK_SIGNING_SECRET ?? ''
  const ts = getRequestHeader(event, 'x-sume-webhook-timestamp') ?? ''
  const sig = getRequestHeader(event, 'x-sume-webhook-signature') ?? ''
  const raw = (await readRawBody(event)) ?? ''
  const fresh = /^\d+$/.test(ts) && Math.abs(Date.now() / 1000 - Number(ts)) <= 300
  const want = Buffer.from('sume-v1=' +
    createHmac('sha256', secret).update(`${ts}.${raw}`).digest('hex'))
  const ok = !!secret && fresh && sig.split(',').some((p) => {
    const got = Buffer.from(p.trim())
    return got.length === want.length && timingSafeEqual(got, want)
  })
  if (!ok) throw createError({ statusCode: 401 })
  const body = JSON.parse(raw)
  if (body.event === 'job.completed') {
    // queue a download for body.job_id
  }
  return { received: true }
})

Which events arrive for a video?

Sume job webhook events, from the Sume webhooks guide (read 2026-10-06)
eventstatus in the bodyMeaning for a /v1/videos job
job.completedOKOutput is ready; payload.artifacts lists it
job.failedERRORNo output; the hold is released
job.canceledERRORCancelled before generation started

What about duplicates and missed deliveries?

Treat job_id as the key and make the handler idempotent. Keep a periodic status read as a safety net: the webhook is terminal-only, so a lost delivery would otherwise leave a clip marked running forever.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume