Skip to content
BasiX Media

Webhooks

Get notified, and verify the signature.

BasiX POSTs a signed JSON body to your HTTPS endpoint when something happens. Create endpoints in Dashboard → Webhooks and pick the events. Each endpoint has a secret starting with whsec_.

Events:

  • file.uploaded: A file landed — whatever sent it: dashboard, API key or presigned link.
  • file.deleted: A file went to the trash. It is still recoverable at this point.
  • import.finished: A zip import finished, successfully or not.
  • quota.warning: Storage crossed 80%, then 90%, then 95%. Once per threshold.
  • log.alert: A log alert rule fired. Sent only to the endpoints that rule targets.
  • ping: Sent only when you press Test.

Every delivery uses the same envelope:

json
{
  "id": "clx8f2...",
  "event": "file.uploaded",
  "createdAt": "2026-10-25T14:03:11.204Z",
  "account": "a7k2m9x4",
  "data": {
    "id": "k7x2m9q4pz1a",
    "path": "avatars/john.png",
    "url": "https://cdn.basix.media/a7k2m9x4/avatars/john.png",
    "mime": "image/png",
    "sizeBytes": 15320,
    "sha256": "...",
    "originalName": "john.png",
    "replaced": false
  }
}

data per event:

  • file.uploaded: id, path, url, mime, sizeBytes, sha256, originalName, replaced.
  • file.deleted: id, path, url, mime, sizeBytes.
  • import.finished: id, fileName, prefix, status, total, extracted, skipped, refused, bytes, error.
  • quota.warning: threshold (80, 90 or 95), percent, usedBytes, quotaBytes.
  • log.alert: rule (id, name), count, windowMinutes, events (up to 10).

Headers:

text
X-BasiX-Event: file.uploaded X-BasiX-Delivery: clx8f2... X-BasiX-Attempt: 1 X-BasiX-Signature: t=1790000000,v1=<hex>

Verify the signature. v1 is the hex HMAC-SHA256 of <t>.<raw body> with your secret. Use the raw body, before any JSON parsing. Reject old timestamps.

import crypto from 'node:crypto' import express from 'express' const app = express() app.post('/basix', express.raw({ type: 'application/json' }), (req, res) => { const parts = Object.fromEntries(req.get('X-BasiX-Signature').split(',').map((p) => p.split('='))) if (Math.abs(Date.now() / 1000 - Number(parts.t)) > 300) return res.sendStatus(400) const expected = crypto .createHmac('sha256', process.env.BASIX_WEBHOOK_SECRET) .update(`${parts.t}.${req.body}`) .digest() const given = Buffer.from(parts.v1, 'hex') if (given.length !== expected.length || !crypto.timingSafeEqual(given, expected)) return res.sendStatus(400) res.sendStatus(200) handle(JSON.parse(req.body)) })

Delivery rules.

  • Answer 2xx within 5 seconds. Do the work after answering.
  • Failed deliveries are retried after 1 min, 5 min, 30 min, 2 h and 6 h. A 4xx other than 429 is not retried.
  • After 20 deliveries that ran out of attempts, the endpoint is disabled. The dashboard shows it.
  • The same delivery can arrive twice. Use X-BasiX-Delivery (stable across retries) to ignore duplicates.
  • The URL must be HTTPS with a valid certificate, on port 443 or 8443, on a public address. Redirects are not followed.

Try it on your own server

Create a free account and your API key is ready in about two minutes. 3 GB included, bandwidth never billed, no card needed.

Create a free account
Webhooks — Standalone docs — BasiX