Gareth Wilson Gareth Wilson

Guide to Gumlet Webhooks: Features and Best Practices

Published


Gumlet webhooks tell your application when video work finishes in the background. Uploading and transcoding a video takes time, and so does starting a live stream, so instead of polling the Gumlet API you can have Gumlet call your endpoint when a video is ready to embed, when processing fails, when a live session ends, or when a viewer submits a lead form in the player.

This guide covers how Gumlet webhooks are configured, the events and payloads they send, how to authenticate them with the Secret Token, how retries work, and the practices that keep a Gumlet receiver reliable.

What are Gumlet webhooks?

A Gumlet webhook has a URL, a trigger list and an optional Secret Token. The trigger list holds the events you want, and the webhook applies to the video sources or workspaces you choose. When one of those events happens, Gumlet sends a JSON POST whose type field names the event.

Gumlet doesn't sign webhook requests. If you set a Secret Token, Gumlet sends it back as plain text in the x-gumlet-token header "with each request", and your endpoint compares it with the token you stored. If you don't set one, the header still arrives but holds an empty string, so there is nothing to check.

Gumlet webhook features

FeatureDetails
ConfigurationDashboard (Developer > Webhooks) or the webhooks API
MethodPOST with a JSON body
Event fieldtype in the body
ScopeThe video sources or workspaces you select for the webhook
AuthenticationOptional Secret Token, sent as plain text in x-gumlet-token. No signature
SuccessAny 2xx
Timeout25 seconds
RetriesEvery half hour for 24 hours after a non-2xx, a timeout or a connection error
RedirectsFollowed, up to 5
Source IPsNot published

Common events

AreatypeFires when
Video processingvideo.status.created, video.status.downloaded, video.status.stream_ready, video.status.processed, video.status.readyAn asset is created, its source file is downloaded, it can stream at one resolution, it is processed, and it is ready to embed
Video problems and changesvideo.status.errored, video.status.deleted, video.status.repackagedProcessing fails, the asset is deleted, or a subtitle or audio stream is added
Live videolive.video.status.created, preparing, ready, connected, active, disconnected, completeA live session moves through its lifecycle (each value is prefixed live.video.status.)
Uploads and editsevent.video.uploaded, event.video.updatedA video is uploaded, or its title, tags, description or privacy change
Playerevent.embed.viewed, event.embed.cta_clicked, event.embed.form_submittedA video is viewed for the first time, a call to action is clicked, or a lead form is submitted
Playlists, comments and channelsevent.playlist.created, event.playlist.asset, event.playlist.deleted, event.comment.all, event.channel.member_joinedA playlist changes, a comment is added, replied to or resolved, or someone joins a channel

Gumlet's two lists don't quite agree. The webhooks guide includes video.status.processed, which the API reference's event list doesn't, and the API reference adds video.status.optimized, event.video.analytics and event.image.analytics, which the guide doesn't. Handle all of them and acknowledge anything you don't recognise.

Status bodies carry the asset's status, its input and output, and timestamps in milliseconds. Here is Gumlet's live.video.status.complete example, trimmed:

{
  "type": "live.video.status.complete",
  "status": "complete",
  "live_asset_id": "68b671c1373a93b5103e1f9a",
  "input": {
    "live_video_source_id": "68678418fc386bd77fd5c689",
    "resolution": ["360p", "480p", "720p", "1080p"],
    "title": "Live stream at 04:25:38 on 2nd September"
  },
  "output": {
    "playback_url": "https://video.gumlet.io/68678418fc386bd77fd5c689/68b671c1373a93b5103e1f9a/master.m3u8",
    "embed_url": "https://play.gumlet.io/embed/live/68b671c1373a93b5103e1f9a",
    "duration": 0
  },
  "created_at": 1756787138405,
  "updated_at": 1756787392403,
  "recording_asset_id": "68b671c1373a93b5103e1f9c",
  "vod_collection_id": "68678419fc386bd77fd5c69c"
}

The event.* product events are different: Gumlet describes their body as "the originating event payload" with type set, and doesn't document their fields. Log a few before you depend on them.

If you create a video with process_low_resolution_input set to true and the input is smaller than Gumlet's minimum frame size, the webhook also carries a warning object with the code WRN_LOW_FRAME_SIZE, telling you that transformations were not applied.

Setting up Gumlet webhooks

  1. Open Developer > Webhooks in the Gumlet dashboard.
  2. Enter your HTTPS endpoint URL.
  3. Choose the triggers you want and the video sources they apply to.
  4. Enter a Secret Token. Use a long random value, for example the output of openssl rand -hex 32, and store it as GUMLET_WEBHOOK_SECRET.
  5. Save.

You can also create webhooks through Gumlet's webhooks API with the same settings.

With Hookdeck. Set a Secret Token on the Gumlet webhook. Webhooks without one send nothing to verify. Create a Hookdeck source with the Gumlet type, paste the same value into Secret Token, and use the source URL as the webhook URL in Gumlet. Hookdeck compares it with the x-gumlet-token header on each request. The source accepts POST only, which is the method Gumlet uses.

Securing Gumlet webhooks

Read x-gumlet-token, reject it if it's missing or empty, and compare it with your stored token in constant time before you parse the body.

const crypto = require('crypto');
const express = require('express');

const SECRET = process.env.GUMLET_WEBHOOK_SECRET;

function safeEqual(received, expected) {
  const a = Buffer.from(received, 'utf8');
  const b = Buffer.from(expected, 'utf8');
  // timingSafeEqual throws on unequal lengths, so check first.
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

const app = express();

app.post('/webhooks/gumlet', express.raw({ type: '*/*' }), (req, res) => {
  const received = req.get('x-gumlet-token');
  // An empty header means the webhook has no Secret Token: reject it.
  if (!SECRET || !received || !safeEqual(received, SECRET)) {
    return res.sendStatus(401);
  }

  let event;
  try {
    event = JSON.parse(req.body.toString('utf8'));
  } catch {
    return res.sendStatus(400);
  }

  // Route on event.type, queue the work, then respond.
  res.sendStatus(200);
});

The same check in Python:

import hmac
import os

SECRET = os.environ.get("GUMLET_WEBHOOK_SECRET", "")


def verify_gumlet_request(headers) -> bool:
    if not SECRET:
        return False  # fail closed when unconfigured
    received = headers.get("x-gumlet-token")  # Starlette and Flask headers are case-insensitive
    if not received:
        return False  # missing, or empty because no Secret Token is set
    # Compare bytes: compare_digest raises TypeError on a non-ASCII str.
    return hmac.compare_digest(received.encode("utf-8"), SECRET.encode("utf-8"))

A static token authenticates the sender, not the body. It can't detect a modified payload, and with no signed timestamp a captured request can be replayed and still pass. Use HTTPS, a long random token, and change it in Gumlet and in your receiver together if it might have leaked.

Make Gumlet webhooks production-ready. Hookdeck Event Gateway checks x-gumlet-token on every request, stores each event durably, and delivers it to your service with retries you control.

Gumlet webhook limitations and pain points

The Secret Token is optional

The Problem: Your receiver accepts requests from anyone who knows the URL.

Why It Happens: The Secret Token is optional, and a webhook without one sends x-gumlet-token as an empty string. A check like received === secret passes when both are empty.

Workarounds:

  • Always set a Secret Token, and fail closed if your stored value is empty.
  • Treat an empty header the same as a missing one.

How Hookdeck Can Help: Hookdeck's Gumlet source requires a Secret Token, so it never accepts the empty header. Hookdeck answers Gumlet with a 200 either way, then records a request without the right token as failed verification, so it never reaches your destination.

Half-hour retries

The Problem: A short outage on your side delays a "video ready" notification by 30 minutes or more.

Why It Happens: Gumlet retries a failed delivery every half hour for 24 hours. A timeout counts as a failure, and the timeout is 25 seconds.

Workarounds:

  • Respond with 200 as soon as the token checks out, and process in the background.
  • For anything user-facing, fall back to the Gumlet API if you haven't heard about a video within your expected processing time.

How Hookdeck Can Help: Hookdeck accepts each delivery as soon as it arrives, so Gumlet doesn't wait for your service or schedule a retry. Hookdeck then delivers to your service with retries on your own schedule, and lets you replay any stored event.

Undocumented product-event bodies

The Problem: Your handler breaks on an event.* webhook because a field isn't where you expected.

Why It Happens: Gumlet documents the body of the video and live status webhooks, but for product events it only says the body is the originating payload with type set.

Workarounds:

  • Capture real event.* deliveries before you write handlers for them.
  • Parse defensively and log anything you can't handle.

How Hookdeck Can Help: Hookdeck stores every request with its headers and body, so you can see the real shape of each event, and transformations can normalise bodies before they reach your service.

Best practices

Route on type

Every body carries type. Switch on it, and acknowledge unknown values with 200 so a new event never turns into a stream of retries.

Act on video.status.ready, not earlier statuses

video.status.stream_ready fires as soon as a single resolution can stream. Wait for video.status.ready before you publish an embed, and treat video.status.errored as a terminal failure to surface to users.

Make handlers idempotent

Retries can deliver an event more than once. Key on type plus the asset ID (asset_id or live_asset_id) and updated_at, and skip events you have already handled. See our guide to webhook idempotency.

Don't rely on redirects

Gumlet follows up to five redirects, but each one adds latency inside the 25-second timeout. Register the final URL.

Acknowledge fast

Verify, store the event, return 200, and do the work afterwards. See why to process webhooks asynchronously.

Conclusion

Gumlet sends a JSON POST for video, live video and product events, with the event name in type and an optional Secret Token in x-gumlet-token rather than a signature. Always set the token and reject an empty header, compare it in constant time, route on type, and respond within 25 seconds, because every failure costs a half-hour wait.

Hookdeck Event Gateway sits in front of your receiver, checks the token, answers Gumlet at once, and keeps every event until your service has processed it.

Get started with Hookdeck for free and handle Gumlet webhooks reliably in minutes.


Gareth Wilson

Gareth Wilson

Product Marketing

Multi-time founding marketer, Gareth is PMM at Hookdeck and author of the newsletter, Community Inc.