Agent skill

Gumlet Webhooks Skill

Receive and authenticate Gumlet webhooks. Use when setting up a Gumlet webhook endpoint, checking the x-gumlet-token Secret Token header, debugging 401s, or handling video and live stream events like video.status.ready, video.status.errored, live.video.status.complete and event.video.uploaded.

Install this skill

npx skills add hookdeck/webhook-skills --skill gumlet-webhooks


Gumlet (video hosting and image optimisation) sends a JSON POST when something happens asynchronously: a video asset becomes ready or errors, a live stream starts or ends, a viewer submits a form in the player. Webhooks are not signed. If you set the optional Secret Token on the webhook, Gumlet sends it back, as plain text, in the x-gumlet-token header of every request, and your endpoint authenticates the request by comparing that header with the secret you stored.

Set a Secret Token on the Gumlet webhook. Webhooks without one send nothing to verify.

When to Use This Skill

  • How do I receive Gumlet webhooks?
  • How do I verify the x-gumlet-token header?
  • Does Gumlet sign webhook payloads? (No: use the Secret Token)
  • How do I handle video.status.ready, video.status.errored or live.video.status.complete?
  • Why does my Gumlet webhook endpoint return 401?

Verification (core)

const crypto = require('crypto');

// Hash both sides: equal-length buffers, so timingSafeEqual never throws.
function safeEqual(a, b) {
  const ha = crypto.createHash('sha256').update(String(a)).digest();
  const hb = crypto.createHash('sha256').update(String(b)).digest();
  return crypto.timingSafeEqual(ha, hb);
}

function verifyGumletRequest(headers, secret = process.env.GUMLET_WEBHOOK_SECRET) {
  if (!secret) return false;                   // never accept when unconfigured
  const received = headers['x-gumlet-token'];  // the whole Secret Token, plain text
  return Boolean(received) && safeEqual(received, secret);
}
  • Header: x-gumlet-token: <Secret Token> (the entire value, no prefix, no signature).
  • Return 401 on a missing header or a mismatch, 400 for a body that isn't a JSON object, 200 once handled. Any 2xx counts as delivered.
  • Read the body raw (express.raw({ type: '*/*' })) and authenticate before parsing it.
  • A static shared credential authenticates the sender, not the body, and gives no replay protection (no signature, no timestamp). Use HTTPS, and rotate the Secret Token if it leaks.

For complete handlers with tests, see examples/express/, examples/nextjs/, examples/fastapi/.

Event Types

The JSON body has a type field and no common envelope; dispatch on type.

EventSent When
video.status.createdA video asset is created
video.status.stream_readyVideo can be streamed at a single resolution
video.status.downloadedVideo file is downloaded for processing
video.status.processedVideo is processed
video.status.readyVideo is ready to embed
video.status.erroredProcessing failed
video.status.deletedVideo asset is deleted
video.status.repackagedA subtitle or audio stream was added
live.video.status.createdA live session is created
live.video.status.preparingA live session is preparing to start
live.video.status.readyA live session is ready for streaming
live.video.status.connectedA live session connected to the source
live.video.status.activeA live session is streaming
live.video.status.disconnectedA live session disconnected from the source
live.video.status.completeA live session ended
event.video.uploadedA video is uploaded
event.video.updatedTitle, tags, description or privacy changed
event.embed.viewedA video is viewed for the first time
event.embed.cta_clickedA call-to-action is clicked in the embed
event.embed.form_submittedA lead is captured
event.playlist.createdA playlist is created
event.playlist.assetAn asset is added to or removed from a playlist
event.playlist.deletedA playlist is deleted
event.comment.allA comment is added, replied to or resolved
event.channel.member_joinedA new channel member joined

Video status payloads carry asset_id, status, input and output; live payloads carry live_asset_id, status, input and output.

Delivery

Gumlet retries any non-2xx response every 30 minutes for 24 hours, so make handlers idempotent.

Environment Variables

GUMLET_WEBHOOK_SECRET=your-secret-token   # the Secret Token set on the Gumlet webhook

Local Development

Gumlet can't reach localhost. Tunnel with the Hookdeck CLI (no account required):

npx hookdeck-cli listen 3000 gumlet --path /webhooks/gumlet

Use the printed URL as the webhook endpoint URL in Gumlet.

Resources


Repository

hookdeck/webhook-skills

v0.1.0 · MIT · Updated Nov 10, 2026

View on GitHub →