Browse topics

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

EventSent when
link.createdA link is created (form, bulk or API)
link.updatedA link is changed or enabled/disabled; data.changed lists the changed fields
link.deletedA link is deleted
link.expiredA link passes its expiration date (sent once)
click.recordedA visitor clicks a link (bots excluded)
webhook.testYou 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 2xx response 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.