VideohatiDocs
Guides

Guide: Receive events with webhooks

Register a webhook endpoint, verify the signature, and react to Videohati events like a video becoming ready.

A webhook is a message Videohati sends to your server when something happens — a video finishes processing, a viewer starts watching, a payment succeeds. You give Videohati a URL, and it posts a small JSON message there each time an event fires.

This is the reliable way to know when a video is ready without polling for it.

1. Register an endpoint

Tell Videohati where to send events. The secret in the response is shown once — store it now, because you need it to verify every future delivery.

import { Videohati } from "@videohati/node";

const client = new Videohati({ apiKey: process.env.VIDEOHATI_API_KEY });

const endpoint = await client.webhooks.register(projectId, {
  url: "https://example.com/webhooks/videohati",
});

console.log(endpoint.secret); // shown once — save it as VIDEOHATI_WEBHOOK_SECRET

Prefer clicking? You can register the same endpoint in the dashboard under your project's webhook settings. The secret is still shown only once.

2. The events

Videohati sends these event types. Each delivery names its type in the type field.

EventFires when
video.upload.completedA file finished uploading, before processing.
video.encoding.readyThe video finished processing and is ready to play.
video.encoding.failedProcessing failed for a video.
video.deletedA video was deleted.
playback.startedA viewer started a playback session.
playback.completedA playback session ended.
payment.succeededA payment went through.
payment.failedA payment did not go through.

3. The delivery envelope

Every delivery is a JSON body with the same shape. The data object holds the details for that event type.

{
  "id": "01JZ9WV3N8GQ5T2M7K4C6XBARE",
  "type": "video.encoding.ready",
  "created_at": "2026-07-05T12:00:00Z",
  "project_id": "01JZ9WV3N8GQ5T2M7K4C6XBARP",
  "data": {
    "videoId": "01JZ9WV3N8GQ5T2M7K4C6XBARH"
  }
}

4. Verify the signature

Every delivery carries a Videohati-Signature header. Verify it with your endpoint secret before you trust the body. Verification needs the raw request body, exactly as received — not a re-serialized JSON object.

Node.js, with Express:

import express from "express";
import { verifyWebhookSignature, SIGNATURE_HEADER } from "@videohati/node";

const app = express();

app.post(
  "/webhooks/videohati",
  express.raw({ type: "application/json" }), // keep the raw body
  (req, res) => {
    const header = req.header(SIGNATURE_HEADER);
    const result = verifyWebhookSignature({
      payload: req.body, // Buffer of the raw body
      header,
      secret: process.env.VIDEOHATI_WEBHOOK_SECRET,
    });

    if (!result.valid) return res.status(400).end();

    // result.event is the parsed envelope.
    if (result.event?.type === "video.encoding.ready") {
      // The video is ready — create a playback session, notify a user, etc.
    }

    res.status(200).end();
  },
);

The same check in Laravel, using the SignatureVerifier:

use Illuminate\Http\Request;
use Videohati\Laravel\Webhooks\SignatureVerifier;

Route::post('/webhooks/videohati', function (Request $request) {
    $event = SignatureVerifier::verifyAndParse(
        $request->getContent(), // the raw body
        $request->header(SignatureVerifier::HEADER),
        config('services.videohati.webhook_secret'),
    );

    if ($event === null) {
        abort(400);
    }

    if ($event['type'] === 'video.encoding.ready') {
        // React to the ready video.
    }

    return response()->noContent(); // 204
});

5. Respond fast

Return a 2xx status within 10 seconds. Videohati treats a slow or non-2xx response as a failed delivery. If you have real work to do, acknowledge first, then run the work in the background.

Retries and the circuit breaker

When a delivery fails, Videohati retries it with a growing delay. If an endpoint keeps failing, its circuit opens and deliveries pause so a broken endpoint does not pile up. After you fix the endpoint, reopen it:

await client.webhooks.reopenCircuit(projectId, endpoint.id);

Test and inspect

Send a synthetic video.encoding.ready delivery to confirm your endpoint works:

await client.webhooks.sendTest(projectId, endpoint.id);

Review what was sent and how each attempt went with the delivery log:

const deliveries = await client.webhooks.listDeliveries(projectId, endpoint.id, {
  limit: 20,
});