Webhooks push events to your server the moment they happen — no polling. Configure endpoints under Settings → Webhooks on the Business plan. Each endpoint has its own signing secret (shown once) and subscribes to any of the events below.
Events
| Event | Fires when |
|---|---|
link.created |
A link is created. |
link.updated |
A link’s fields change. |
link.deleted |
A link is deleted. |
link.clicked |
A link receives a (human) click. |
link.expired |
A link passes its expiry date. |
link.disabled |
A link is disabled. |
link.click_limit_reached |
A link hits its click limit. |
subscription.updated |
Your plan or subscription changes. |
Delivery format
Each delivery is a JSON POST to your URL. The envelope wraps the resource in
data (link events carry the same fields as the link resource).
{
"id": "evt_01J8XZ...",
"event": "link.created",
"created_at": "2026-07-22T10:00:00+00:00",
"data": { "id": 42, "slug": "spring-sale", "...": "link resource fields" }
}| Header | Value |
|---|---|
X-Inbio-Event |
The event name (e.g. link.created). |
X-Inbio-Delivery |
Unique delivery id. |
X-Inbio-Signature |
t=<unix>,v1=<hex> — see below. |
Verifying signatures
Compute v1 = HMAC-SHA256(secret, "<t>.<raw body>") over the exact raw
request body, compare in constant time, and reject stale timestamps to
prevent replays.
[$t, $v1] = explode(',', $signature);
$t = substr($t, 2); // strip "t="
$v1 = substr($v1, 3); // strip "v1="
$expected = hash_hmac('sha256', $t . '.' . $rawBody, $secret);
abort_unless(
hash_equals($expected, $v1) && abs(time() - (int) $t) < 300,
401,
);import crypto from "node:crypto";
const [tPart, vPart] = signature.split(",");
const t = tPart.slice(2);
const v1 = vPart.slice(3);
const expected = crypto
.createHmac("sha256", secret)
.update(`${t}.${rawBody}`)
.digest("hex");
const valid =
crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(v1)) &&
Math.abs(Date.now() / 1000 - Number(t)) < 300;import hashlib, hmac, time
t_part, v_part = signature.split(",")
t, v1 = t_part[2:], v_part[3:]
expected = hmac.new(secret.encode(), f"{t}.{raw_body}".encode(), hashlib.sha256).hexdigest()
valid = hmac.compare_digest(expected, v1) and abs(time.time() - int(t)) < 300Retries
Non-2xx responses are retried 5 times with growing backoff (1m, 10m, 1h,
6h). After 20 consecutive failures the endpoint is disabled automatically — you
can re-enable it and replay individual deliveries from Settings → Webhooks,
where every delivery and its response code is logged.