Guides / WebhooksMenu

Webhooks

Get an HTTP request or an email whenever files change, and verify that it came from us.

Event listeners watch a server for file activity. When an event matches, we send a webhook to your URL or an email to your address.

Create a listener

Open a server, go to Event listeners and click Add listener:

  1. Pick the events to trigger on.
  2. Optionally add filters. All filters must match.
  3. Choose Webhook and enter an https:// URL, or choose Email and enter an address.

For webhooks, a signing secret starting with whsec_ is shown once. Store it with your application.

Use Send test to deliver a sample event right away.

Events

Event When
upload A file was uploaded
first-upload A file was uploaded for the first time in a session
download A file was downloaded
first-download A file was downloaded for the first time in a session
delete A file was deleted
rename A file or folder was renamed or moved
mkdir A folder was created
rmdir A folder was removed
ssh_cmd An SSH command such as md5sum was run

Filters

Filter on the file path or the username with contains, does not contain, starts with, ends with or equals. For example, path starts with /incoming only fires for files dropped into that folder.

Payload

Webhooks are POST requests with a JSON body:

{
  "user": { "id": "acme", "name": "acme" },
  "event": "upload",
  "status": "OK",
  "full_path": "/incoming/invoices.zip",
  "protocol": "SFTP",
  "ip": "203.0.113.10",
  "timestamp": 1792575945021000000,
  "file_size": 48213,
  "download_url": "https://nbg1.your-objectstorage.com/…?X-Amz-Signature=…",
  "download_expires_at": 1792579545
}
Field Description
event One of the events above.
status OK, or KO when the operation failed.
full_path Path of the file. For rename, the new path.
source_path Only for rename: the previous path.
timestamp Unix time in nanoseconds.
download_url A link to download the file, valid for one hour. Sent for successful upload, rename and download events.
download_expires_at Unix time in seconds when download_url stops working.

Each delivery attempt carries a fresh download_url, so retried webhooks always have a working link. Fetch the file with a plain GET; no credentials are needed.

Temporary file names

Many FTP clients upload under a temporary name and rename the file when the transfer completes, for example offers.zip.part → offers.zip. To process only finished files, listen to both upload and rename and add a filter such as path ends with .zip. Because full_path holds the new name for renames, the same handler works for both events.

Verifying signatures

Each request has an X-CloudFile-Signature header like t=1792575945,v1=5f2c…. Compute an HMAC-SHA256 of {t}.{raw body} with your signing secret and compare it to v1. Reject requests where t is more than five minutes old.

import { createHmac, timingSafeEqual } from "node:crypto";

export function verify(rawBody, header, secret) {
  const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
  const expected = createHmac("sha256", secret).update(`${parts.t}.${rawBody}`).digest("hex");
  const fresh = Math.abs(Date.now() / 1000 - Number(parts.t)) < 300;
  return fresh && timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1));
}
import hashlib, hmac, time

def verify(raw_body: bytes, header: str, secret: str) -> bool:
    parts = dict(p.split("=", 1) for p in header.split(","))
    expected = hmac.new(secret.encode(), f"{parts['t']}.".encode() + raw_body, hashlib.sha256).hexdigest()
    fresh = abs(time.time() - int(parts["t"])) < 300
    return fresh and hmac.compare_digest(expected, parts["v1"])

Retries

Respond with any 2xx status within 10 seconds. Otherwise we retry with exponential backoff for several hours. Each attempt is listed under the listener with its status code or error.

Webhook URLs must use HTTPS and resolve to a public address.