Webhooks
Webhooks notify your own systems when something happens, by sending a signed HTTP POST request with a JSON body.
Create a webhook
Go to Webhooks → Add webhook and enter:
- a name,
- the endpoint URL (preferably
https://), and - the events to receive.
The signing secret (whsec_…) is shown once after creating the webhook. Store it in your application. You can rotate it at any time; the old secret stops working immediately.
Use Send test to deliver a webhook.test event right away and see the response.
Events
| Event | Sent when |
|---|---|
link.created | A link is created (form, bulk or API) |
link.updated | A link is changed or enabled/disabled; data.changed lists the changed fields |
link.deleted | A link is deleted |
link.expired | A link passes its expiration date (sent once) |
click.recorded | A visitor clicks a link (bots excluded) |
webhook.test | You press Send test |
Request format
POST /your/endpoint HTTP/1.1
Content-Type: application/json
User-Agent: ShortLink-Webhooks/1.0
X-ShortLink-Event: link.created
X-ShortLink-Delivery: 6f1c2b9e-…
X-ShortLink-Timestamp: 1767225600
X-ShortLink-Signature: v1=5d41402abc4b2a76b9719d911017c592…
Idempotency-Key: 6f1c2b9e-…
{"id":"6f1c2b9e-…","event":"link.created","created_at":"2026-01-01T00:00:00Z","data":{"link":{…}}}
id is unique per event and equals X-ShortLink-Delivery and Idempotency-Key. A retried delivery keeps the same id, so store processed ids to ignore duplicates. data.link has the same fields as the REST API link object.
Verifying the signature
The signature is an HMAC-SHA256 of the timestamp, a dot, and the raw request body, using your signing secret:
signature = "v1=" + hex(HMAC_SHA256(secret, timestamp + "." + raw_body))
PHP example:
$body = file_get_contents('php://input');
$ts = $_SERVER['HTTP_X_SHORTLINK_TIMESTAMP'] ?? '';
$expected = 'v1=' . hash_hmac('sha256', $ts . '.' . $body, $secret);
$valid = hash_equals($expected, $_SERVER['HTTP_X_SHORTLINK_SIGNATURE'] ?? '')
&& abs(time() - (int) $ts) < 300;
Always compare in constant time and reject old timestamps to prevent replays.
Delivery and retries
- Deliveries are sent by the cron job, usually within a minute.
- Any
2xxresponse counts as success. Respond quickly (the timeout is 8 seconds) and do slow work afterwards. - Failed deliveries are retried after 1 minute, 5 minutes, 30 minutes, 2 hours, 6 hours and 12 hours, up to 6 attempts in total. After that the delivery is marked failed.
- Redirects are not followed. Endpoints on private network addresses are rejected.
- While a webhook is disabled, new events are not queued and pending deliveries are held until it is enabled again.
Delivery log
The webhook page lists every delivery with its status (success, pending, failed), attempts, response code, error and duration. Filter by status, open a delivery to see the payload and the response, and use Retry to send a pending or failed delivery immediately.