homeaboutvideotry it nowpricingdocs
Guide

Get notified when a video is ready, without polling

Receive a signed HTTP request when a file is uploaded or deleted, or a video finishes processing.

Why not just poll

A video is not playable the moment it is uploaded: it has to be transcoded first. The simple way to find out when it is done is to ask for its status every few seconds. That works, but every waiting video costs a request per interval, the answer arrives up to one interval late, and the loop has to live somewhere that survives deploys.

A webhook turns it around. Fileboy sends your server a request the moment something happens, retries if your server is down, and keeps a log of what it sent. There is nothing to schedule and nothing to keep running.

Add an endpoint

  • Open your app in the dashboard and choose Webhooks.
  • Add the public https URL of a route on your server, and pick the events you want. Leave every event unticked to receive them all.
  • Open Signing secret, reveal it and store it as an environment variable on your server, for example FILEBOY_WEBHOOK_SECRET.
  • Press Send test event. The delivery shows in the table under the endpoint with the status code your server answered with.

Endpoints must use https and a public address. Fileboy will not call localhost or private network addresses, so while developing use a tunnel to your machine.

What Fileboy sends

Each event is a POST with a JSON body. These are the events you can subscribe to:

  • file.uploaded: a file was uploaded
  • file.deleted: a file was deleted
  • video.uploaded: a video was uploaded and is waiting to be processed
  • video.ready: processing finished and the video can be played. Includes the duration, size and playable URLs
  • video.failed: processing failed
  • video.deleted: a video was deleted
A video.ready event
{
  "id": "9f1c2e5a-3d0b-4a56-8b3f-0c7d8f2f6c11",
  "type": "video.ready",
  "created": "2026-11-10T10:30:12Z",
  "appId": "0b9e3b4c-5b52-4b5e-9d2e-1b0c2c8a4f77",
  "resource": {
    "id": "4a7b8f2e-6c1d-4f3a-9b0e-2d5c7e9a1b34",
    "name": "intro",
    "createdAt": "2026-11-10T10:28:40Z",
    "status": "ready",
    "duration": 94.2,
    "width": 1920,
    "height": 1080,
    "urls": {
      "manifest": "https://api.fileboy.io/videos/4a7b8f2e-6c1d-4f3a-9b0e-2d5c7e9a1b34/manifest",
      "hls": "https://api.fileboy.io/videos/4a7b8f2e-6c1d-4f3a-9b0e-2d5c7e9a1b34/manifest?type=hls",
      "dash": "https://api.fileboy.io/videos/4a7b8f2e-6c1d-4f3a-9b0e-2d5c7e9a1b34/manifest?type=dash",
      "poster": "https://api.fileboy.io/videos/4a7b8f2e-6c1d-4f3a-9b0e-2d5c7e9a1b34/poster"
    }
  }
}

id is unique to the event and stays the same on every retry, created is when it happened, and resource describes the file or video. File events carry id, name, originalFilename, mime, fileSize and metadata. Video events carry id, name, status and, for video.ready, duration, width, height and urls.

Every request has two headers you use to check it is genuine: Fileboy-Timestamp, the unix time in seconds that the request was sent, and Fileboy-Signature, which looks like v1= followed by 64 hex characters.

Check the signature

Anyone who knows your URL can post to it, so check the signature before trusting a request. The signature is the hex HMAC-SHA256, using your endpoint's signing secret as the key, of the text timestamp.body: the value of the Fileboy-Timestamp header, a dot, then the raw request body exactly as received. Compare it in constant time, and reject requests whose timestamp is more than five minutes from your clock so a captured request cannot be replayed later.

In Node, @cd2/fileboy-node (0.15 and later) does all of this for you. Pass it the raw body: if your framework parses the JSON first, the bytes change and the signature will not match.

webhooks.ts
import express from "express"
import { verifyWebhook } from "@cd2/fileboy-node"

const app = express()

// express.raw keeps the body as a Buffer, which is what verifyWebhook needs
app.post("/webhooks/fileboy", express.raw({ type: "application/json" }), (req, res) => {
  let event
  try {
    event = verifyWebhook(req.body, req.headers, process.env.FILEBOY_WEBHOOK_SECRET!)
  } catch {
    return res.status(400).send("Invalid signature")
  }

  switch (event.type) {
    case "video.ready":
      console.log(event.resource.id, "is ready,", event.resource.duration, "seconds")
      break
    case "video.failed":
      console.log(event.resource.id, "failed to process")
      break
  }

  res.sendStatus(200)
})

In any other language the check is a few lines. This is Ruby:

fileboy_webhooks_controller.rb
def valid_fileboy_signature?(raw_body, timestamp, signature, secret)
  return false if (Time.now.to_i - timestamp.to_i).abs > 300

  expected = "v1=" + OpenSSL::HMAC.hexdigest("SHA256", secret, "#{timestamp}.#{raw_body}")
  ActiveSupport::SecurityUtils.secure_compare(expected, signature.to_s)
end

# raw_body is request.raw_post, timestamp and signature are the
# Fileboy-Timestamp and Fileboy-Signature headers

Answer quickly and handle repeats

Reply with any 2xx status to say you have it. Do the slow work after replying, or put it on a queue, because Fileboy waits ten seconds for an answer and treats a slower one as a failure. Redirects are not followed.

Anything else counts as a failure and is retried with a growing delay: 30 seconds, then 1, 2, 4, 8, 16, 32, 64 and 128 minutes, ten attempts in all. That makes delivery at least once, so the same event can arrive twice, for instance if your server saved the result but crashed before answering. Store the id of each event you process and ignore ids you have seen.

Events are not guaranteed to arrive in the order they happened, so a video.deleted can overtake a video.ready that is still being retried. Use the created time or look at your own records before acting on an event.

See what was sent

The Deliveries table under each endpoint shows the most recent events with their status, how many attempts were made, the response status and the first part of the response body, or the reason no response was received. Press Retry on any delivery to send it again, which gives it a fresh set of ten attempts. Delivery history is kept for 30 days.

If you rotate the signing secret, the old one stops working immediately, so update your server straight after. An app can have up to ten endpoints.

Find out when an endpoint is down

Each endpoint shows whether it is healthy. When one keeps failing (by default five attempts in a row, over at least 15 minutes) every member of the app gets one email naming the endpoint, with its most recent errors and a link to its deliveries, and another when it recovers. Under Delivery alerts on the Webhooks page you can change the rule, add a team inbox, turn alerts off for an endpoint, and send yourself a test alert.

You can also have Fileboy disable an endpoint that has been failing for days, after a warning email. It is off unless you turn it on, because a disabled endpoint receives no new events. See failing endpoints and alerts for the details.

Checked against the current libraries in November 2026. Found something that no longer works? Tell us.

Go deeper

Still choosing a tool?

Fileboy is small on purpose. This comparison says where it fits and where another option is the better call.

Fileboy vs Uploadcare · All comparisons

Try it

The free plan needs no card and includes 1,000 files, so you can run every example above.

More guides: How to add image upload to a React app · Resize images on the fly with a URL · Stream video with HLS without running ffmpeg · Add captions and subtitles to a video · All guides

Add Fileboy to your next project

We built Fileboy to stop rebuilding file handling for every app. Create an app, paste your key and upload a file. The free plan needs no card.