---
title: "Webhooks"
description: "Signed event deliveries for links and subscriptions. Business plan."
---

> Documentation Index
> Fetch the complete documentation index at: https://docs.in.bio/llms.txt
> Use this file to discover all available pages before exploring further.

# Webhooks

Configure endpoints under **Settings → Webhooks** (Business plan). Each
endpoint has its own signing secret — shown once — and subscribes to any of:

`link.created` · `link.updated` · `link.deleted` · `link.clicked` ·
`link.expired` · `link.disabled` · `link.click_limit_reached` ·
`subscription.updated`

## Delivery format

A JSON `POST` to your URL:

```json
{
  "id": "evt_01J8...",
  "event": "link.created",
  "created_at": "2026-07-22T10:00:00+00:00",
  "data": { "id": 42, "slug": "spring-sale", "...": "link fields as in the API resource" }
}
```

Headers:

| Header | Value |
|---|---|
| `X-Inbio-Event` | The event name |
| `X-Inbio-Delivery` | Unique delivery id |
| `X-Inbio-Signature` | `t=<unix>,v1=<hex>` |

## Verifying signatures

`v1 = HMAC-SHA256(secret, "<t>.<raw body>")`. Recompute over the **exact raw
request body** and compare in constant time; reject stale timestamps to
prevent replays.

```php
[$t, $v1] = explode(',', $signature);
$t = substr($t, 2);
$v1 = substr($v1, 3);

$expected = hash_hmac('sha256', $t.'.'.$rawBody, $secret);

abort_unless(
hash_equals($expected, $v1) && abs(time() - (int) $t) < 300,
401
);
```

## Retries

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 response code is logged.

Source: https://docs.in.bio/api/webhooks/index.mdx
