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-tokenheader? - Does Gumlet sign webhook payloads? (No: use the Secret Token)
- How do I handle
video.status.ready,video.status.erroredorlive.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
401on a missing header or a mismatch,400for a body that isn't a JSON object,200once 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.
| Event | Sent When |
|---|---|
video.status.created | A video asset is created |
video.status.stream_ | Video can be streamed at a single resolution |
video.status.downloaded | Video file is downloaded for processing |
video.status.processed | Video is processed |
video.status.ready | Video is ready to embed |
video.status.errored | Processing failed |
video.status.deleted | Video asset is deleted |
video.status.repackaged | A subtitle or audio stream was added |
live.video.status.created | A live session is created |
live.video.status.preparing | A live session is preparing to start |
live.video.status.ready | A live session is ready for streaming |
live.video.status.connected | A live session connected to the source |
live.video.status.active | A live session is streaming |
live.video.status.disconnected | A live session disconnected from the source |
live.video.status.complete | A live session ended |
event.video.uploaded | A video is uploaded |
event.video.updated | Title, tags, description or privacy changed |
event.embed.viewed | A video is viewed for the first time |
event.embed.cta_clicked | A call-to-action is clicked in the embed |
event.embed.form_ | A lead is captured |
event.playlist.created | A playlist is created |
event.playlist.asset | An asset is added to or removed from a playlist |
event.playlist.deleted | A playlist is deleted |
event.comment.all | A comment is added, replied to or resolved |
event.channel.member_ | A 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
- references/overview.md - What Gumlet webhooks are, events and payload shape
- references/setup.md - Create the webhook and set the Secret Token
- references/verification.md - Token check details, gotchas, debugging 401s
- examples/ - Runnable Express, Next.js and FastAPI handlers with tests