Ryan Trann
AUTHOR
June 7, 2026
5 min read
TL;DR
Webhooks are HTTP POST requests a service sends to your server when something happens. They're the right pattern for async work like video processing because they eliminate polling, reduce latency, and give you reliable event-driven updates. Verify every incoming request with a signature check, respond with a 200 immediately, and process the actual work in a background queue.

Most APIs work in a request/response model. Your app makes an API call, the server responds with a status and payload. Everything happens in one synchronous round trip. That model works great, except when you have tasks that take time: file uploads, billing events, background jobs, AI processing, and especially video.
Keeping an API request open for 45 seconds while a video uploads and transcodes isn't viable. Polling every few seconds asking "is it done yet?" wastes compute and adds latency. That's where webhooks come in.
A webhook is an HTTP POST request the external service (Stripe, Hyperserve, etc.) sends to your server when something happens that you should know about.
A payment provider might send:
{
"type": "invoice.paid",
"data": { "id": "inv_123", "amount": 1200 }
}A form service might send:
{
"type": "form.submitted",
"data": { "formId": "abc", "fields": {...} }
}Your server receives that POST, verifies it's legitimate, and updates your system. The payload includes data about the event plus some identifying metadata (an ID, typically), which lets you associate the event with a row in your database and make a meaningful update.
Webhooks flip the interaction around: instead of polling for new info, the service notifies you the moment an event occurs.
The model is simple: event happens → service sends POST → your app reacts.
When your server receives a webhook, you need to verify it actually came from the authentic source, not an attacker trying to inject fake events.
Most webhook providers include a signature in the request headers. This signature is a cryptographic hash of the payload, generated using a secret key only you and the service know (usually found in the service's dashboard).
A typical verification flow:
X-Webhook-Signature)Example verification code:
Webhook signature verification
javascriptimport crypto from 'crypto';
export default function handler(req, res) {
const signature = req.headers['x-webhook-signature'];
const secret = process.env.WEBHOOK_SECRET;
const computed = crypto
.createHmac('sha256', secret)
.update(JSON.stringify(req.body))
.digest('hex');
if (computed !== signature) {
return res.status(401).json({ error: 'Invalid signature' });
}
// Process webhook
const event = req.body;
// ...
res.status(200).end();
}Without verification, anyone could POST to your webhook endpoint and trigger fraudulent updates. Always verify signatures before processing. Different services use different schemes (HMAC-SHA256 is the most common), so check your provider's docs for the exact method.
Most production webhook handlers follow a similar pattern to keep things reliable and maintainable:
Here's what this looks like in practice (using Supabase):
Production webhook handler
javascriptimport crypto from "crypto";
import { supabase } from "./supabase";
import { queue } from "./queue";
export default async function handler(req, res) {
// Step 1: Verify signature (see previous section)
// ...
const event = req.body;
// Step 2: Check idempotency
const { data: existing, error: existingError } = await supabase
.from("webhook_events")
.select("event_id")
.eq("event_id", event.id)
.single();
if (existing && !existingError) {
return res.status(200).json({ received: true });
}
// Step 3: Log the event
const { error: insertError } = await supabase
.from("webhook_events")
.insert({
event_id: event.id,
type: event.type,
payload: event,
received_at: new Date().toISOString(),
});
if (insertError) {
return res.status(500).json({ error: insertError.message });
}
// Step 4: Queue for async processing
await queue.add("process-webhook", {
eventId: event.id,
type: event.type,
data: event.data,
});
// Step 5: Respond immediately
res.status(200).json({ received: true });
}
// Worker to process events
export async function processWebhook(job) {
const { type, data } = job.data;
switch (type) {
case "video.processing.completed":
await supabase
.from("videos")
.update({
status: "ready",
urls: data.urls,
thumbnail: data.thumbnail,
})
.eq("id", data.videoId);
break;
case "video.processing.failed":
await supabase
.from("videos")
.update({
status: "failed",
error: data.error,
})
.eq("id", data.videoId);
break;
}
}This pattern is production-ready and keeps your webhook endpoint fast and reliable, even when processing involves database writes, external API calls, or other slow operations. The service gets an immediate acknowledgment, and your system processes the event safely in the background.
MVP shortcut
From a user's perspective, webhooks translate directly into a better experience:
For video specifically, this means users aren't left staring at a spinner wondering if their upload worked. They get clear, timely updates at every stage: upload complete, processing started, transcoding finished, video ready. The experience feels instant and predictable, which builds trust in your platform.
Video is slower than typical web operations. Uploads take time. Processing takes longer. Transcoding, compressing, generating thumbnails — it's all async.
If you rely on polling, you:
Webhooks eliminate the guesswork. When each stage completes, the pipeline notifies your backend with a reliable event, letting you:
This becomes a key component of a predictable video workflow.
Video webhook handler
javascriptexport default function handler(req, res) {
const event = req.body;
switch (event.type) {
case "video.processing.completed":
// Update DB → mark ready
// Attach transcoded URLs
break;
case "video.processing.failed":
// Set status to "error"
break;
}
res.status(200).end();
}Your app never needs to ask "is this finished?" The pipeline pushes every update to you.
Hyperserve follows this same pattern, because it's the right way to communicate long-running video operations back to your app.
When you upload a video through Hyperserve:
Events include:
video.uploadedvideo.processing.startedvideo.processing.completedvideo.ready (all assets available)video.failedEach event contains the video ID, any metadata you attached to the video, and any generated assets (resolutions, poster, and thumbnail URLs), letting your system update instantly.
You get a predictable, event-driven flow that plugs straight into your existing backend.
Hyperserve handles upload, transcoding, storage, and delivery — you just call the API.
A webhook is an HTTP POST request one service sends to another when an event happens. Instead of polling for updates, your app gets notified immediately, which fits async work like payments, uploads, and video processing.
Signature verification proves the request really came from the provider and not a random attacker. Most services sign the payload with a shared secret, and your server checks that signature before processing the event.
Webhooks let a video system report progress as each stage finishes, like upload complete, processing started, transcoding done, or video ready. That keeps your database, UI, and downstream jobs in sync without constant polling.
Most providers retry failed webhook deliveries, which is why idempotency matters. If your handler sees the same event twice, it should safely ignore the duplicate instead of processing it again.
Yes. Hyperserve uses webhooks to send event updates for upload and processing stages, along with the video ID and generated assets. That lets your app update status immediately without building the pipeline logic yourself.
The rapid deployment video backend for modern devs
© 2026 Hyperserve. All rights reserved.
Made by Misty Mountain Software