Webhooks
Webhooks let Linkzly notify your server the moment something happens — a link is clicked, a domain is verified, a team member joins, or an API key is revoked. I
Webhooks
Webhooks let Linkzly notify your server the moment something happens — a link is clicked, a domain is verified, a team member joins, or an API key is revoked. Instead of polling the Linkzly API on a schedule to check for changes, your server receives an HTTP POST request automatically as events occur.
Use webhooks to:
- Sync click data into your own database in real time
- Trigger downstream workflows (for example, send a Slack alert when a domain is verified)
- Keep an external analytics platform up to date with the latest events
- Audit API key activity in your own logging system
Navigate to Webhooks under the Integration section in the left sidebar to create and manage your webhooks.
18.1 Available Event Types
Subscribe your webhook to any combination of the events below. You can update your subscriptions at any time. In the event selection UI, events are grouped into categories and tagged with a frequency badge (HIGH, MEDIUM, or LOW) to help you plan for expected volume.
Link Management
| Event | Display Name | Frequency | Trigger |
|---|---|---|---|
link.created |
Link Created | MEDIUM | A new short link is created |
link.updated |
Link Updated | MEDIUM | A short link is modified |
link.deleted |
Link Deleted | LOW | A short link is deleted |
link.clicked |
Link Clicked | HIGH | A short link receives a click |
Note:
link.clickedis a high-volume event. If your link receives thousands of clicks per hour, subscribe to this event only if your server can handle the volume. Consider using the Analytics API to pull aggregated data instead.
QR Code
| Event | Display Name | Frequency | Trigger |
|---|---|---|---|
qr_code.created |
QR Code Created | MEDIUM | A new QR code is generated |
qr_code.scanned |
QR Code Scanned | HIGH | A QR code is scanned |
Note:
qr_code.scannedis a high-volume event. The same volume considerations apply as forlink.clicked.
Domain Management
| Event | Display Name | Frequency | Trigger |
|---|---|---|---|
domain.created |
Domain Created | LOW | A custom domain is added to the organization |
domain.verified |
Domain Verified | LOW | A domain's DNS verification succeeds |
domain.deleted |
Domain Deleted | LOW | A custom domain is removed |
API Keys
| Event | Display Name | Frequency | Trigger |
|---|---|---|---|
api_key.created |
API Key Created | LOW | A new API key is created |
api_key.revoked |
API Key Revoked | LOW | An API key is revoked or deleted |
Team Management
| Event | Display Name | Frequency | Trigger |
|---|---|---|---|
team.member_added |
Team Member Added | LOW | A team member accepts an invitation and joins the organization |
team.member_removed |
Team Member Removed | LOW | A team member is removed from the organization |
18.2 Webhook Configuration Fields
Click Create Webhook to open the creation form. All fields are described below.
Basic Information
| Field | Required | Description |
|---|---|---|
| Webhook Name | Yes | A display label for this webhook so you can identify it at a glance. |
| Description | No | Internal notes about what this webhook does and who maintains it. |
Endpoint Configuration
| Field | Required | Description |
|---|---|---|
| Webhook URL | Yes | The HTTPS endpoint on your server that receives POST requests from Linkzly. Must be a valid HTTPS URL. Use the Validate button to check the URL before saving. |
Events to Subscribe
Select one or more event types from the multi-select list. Events are grouped by category (Link Management, QR Code, Domain Management, API Keys, Team Management) and tagged with frequency badges to indicate expected volume.
Advanced Configuration
| Field | Required | Description |
|---|---|---|
| Retry Policy | No | How Linkzly retries failed deliveries: exponential (default), linear, immediate, or none. |
| Max Retries | No | Number of retry attempts. Accepted range: 0–10. Default: 3. |
| Timeout (seconds) | No | How many seconds Linkzly waits for a response from your server. Default: 30 seconds. |
Custom Headers
Key-value pairs added to every delivery request. Use these to pass authentication tokens or API keys required by your server. Click Add Header to add a new header. You may add up to 10 custom headers per webhook.
Note: The following reserved headers cannot be used as custom header names:
Content-Type,User-Agent, and any header beginning withX-Webhook-.
Webhook Status
Newly created webhooks are active by default. You can enable or disable a webhook at any time using the Enable / Disable action in the webhook's actions menu. A disabled webhook does not receive any events.
18.3 Payload Structure
Linkzly sends an HTTP POST request to your URL with a JSON body for every event.
Delivery Headers
Every delivery includes the following HTTP headers:
| Header | Description |
|---|---|
X-Webhook-Timestamp |
Unix timestamp in milliseconds of when the request was sent |
X-Webhook-Signature |
HMAC-SHA256 signature in the format sha256=<hex_signature> |
X-Webhook-Event |
The event type string (e.g., link.clicked) |
X-Webhook-Delivery |
Unique delivery identifier for this specific attempt |
X-Webhook-Attempt |
Attempt number (1 = first try, 2 = first retry, etc.) |
Content-Type |
application/json |
User-Agent |
Linkzly-Webhook/2.0 |
Any custom headers you configured are also included.
Standard Payload Fields (all events)
Every event payload includes these top-level fields:
{
"id": "evt_xxxxxxxxxxxxxxxx",
"event": "link.clicked",
"timestamp": "2024-06-15T12:34:56.789Z",
"organizationId": "org_xxxxxxxxxxxx",
"organization": {
"id": "org_xxxxxxxxxxxx",
"name": "Acme Corp"
},
"data": { ... }
}
| Field | Description |
|---|---|
id |
Unique event identifier with the prefix evt_ |
event |
The event type string (e.g., link.clicked, domain.verified) |
timestamp |
ISO 8601 UTC datetime when the event occurred |
organizationId |
Your organization ID |
organization |
Optional object containing id and name of the organization |
data |
Object containing event-specific fields (see below) |
Event-Specific Data Fields
link.clicked
| Field | Description |
|---|---|
linkId |
The ID of the short link that was clicked |
shortUrl |
The full short URL |
clickedAt |
ISO 8601 datetime of the click |
country |
Country name of the visitor |
countryCode |
ISO 3166-1 alpha-2 country code |
city |
City of the visitor (when available) |
device |
Device type: mobile, desktop, tablet, etc. |
browser |
Browser name |
os |
Operating system |
referrer |
Referring domain (if available) |
qr_code.scanned
| Field | Description |
|---|---|
qrCodeId |
The ID of the scanned QR code |
linkId |
The ID of the linked short link |
shortUrl |
The full short URL associated with the QR code |
scannedAt |
ISO 8601 datetime of the scan |
country |
Country name of the scanner |
device |
Device type |
browser |
Browser or scanning app |
os |
Operating system |
domain.verified
| Field | Description |
|---|---|
id |
The domain ID |
domain |
The domain name |
verifiedAt |
ISO 8601 datetime when verification succeeded |
sslStatus |
Current SSL certificate status |
sslExpiresAt |
ISO 8601 datetime when the SSL certificate expires |
team.member_added
| Field | Description |
|---|---|
memberId |
The new member's user ID |
email |
The new member's email address |
name |
The new member's full name |
role |
The role assigned to the new member |
permissions |
Array of specific permissions granted |
invitedBy |
User ID of the person who sent the invitation |
joinedAt |
ISO 8601 datetime when the member accepted |
18.4 Signature Verification
Every webhook delivery includes signature headers. You should always verify these headers to confirm the request came from Linkzly and was not tampered with in transit.
| Header | Description |
|---|---|
X-Webhook-Timestamp |
Unix timestamp in milliseconds of when the request was sent |
X-Webhook-Signature |
HMAC-SHA256 signature in the format sha256=<hex_signature> |
How to Verify
The signature is computed as:
HMAC-SHA256(webhookSecret, "{timestamp}.{rawBody}")
The X-Webhook-Signature header value includes the sha256= prefix as part of the string (for example: sha256=abc123def456...). Compute the HMAC on your server using your webhook secret and the {timestamp}.{rawBody} string, then compare the resulting hex digest — prepended with sha256= — against the full header value.
Always use a constant-time comparison function (such as crypto.timingSafeEqual) to prevent timing attacks.
Replay Protection
Reject any request where the X-Webhook-Timestamp is more than 5 minutes older than your server's current time. This prevents an attacker from capturing a valid request and replaying it later.
Example — JavaScript Verification
const crypto = require('crypto');
function verifyWebhookSignature(rawBody, timestamp, signature, secret) {
const expected = crypto
.createHmac('sha256', secret)
.update(`${timestamp}.${rawBody}`)
.digest('hex');
// The header value includes the 'sha256=' prefix
const headerValue = `sha256=${expected}`;
return crypto.timingSafeEqual(
Buffer.from(headerValue),
Buffer.from(signature)
);
}
Pass the raw request body (before any parsing), the X-Webhook-Timestamp header value, the full X-Webhook-Signature header value (including the sha256= prefix), and your webhook secret.
Rotating the Secret
Your webhook secret is shown once at creation time. If the secret is ever exposed, click Rotate Secret from the webhook's actions menu. Linkzly generates a new secret immediately. Update your server to use the new secret — deliveries signed with the old secret will fail verification.
Security Tools
The Security Tools dialog (accessible from the top-right of the Webhooks page) provides two interactive utilities to help during development:
- Generate Signature — Enter your webhook secret and a JSON payload to generate a valid signature and the expected delivery headers.
- Verify Signature — Enter your webhook secret, a JSON payload, a signature, and a timestamp to confirm whether the signature is valid.
Verify Utility Endpoint
Linkzly also provides an API endpoint to debug signature verification:
POST /api/webhooks/utilities/verify-signature
Send the signature, timestamp, and raw body, and the API will confirm whether the signature is valid.
18.5 Retry Logic
When your server returns a non-2xx response or times out, Linkzly retries the delivery according to the retry policy configured on the webhook.
| Retry Policy | Timing | Description |
|---|---|---|
| Exponential (default) | 1st retry after 2 seconds, 2nd after 4 seconds, 3rd after 8 seconds | Each retry waits twice as long as the previous. Best for most production use cases. |
| Linear | 5-second fixed delay between each retry | Every retry attempt waits the same fixed interval. |
| Immediate | 1-second delay between each retry | Retries are sent as quickly as possible with a minimal delay. |
| None | No retries | Failed deliveries are logged but not retried. |
Max retries is configurable from 0 to 10 on any retry policy. The default is 3 retries. If all retries are exhausted, the delivery is marked as failed in the delivery log.
Your server must return a 2xx HTTP status code to acknowledge successful receipt. Any other response code (including redirects) is treated as a failure.
Automatic Suspension (Circuit Breaker)
If a webhook accumulates 5 consecutive failures, Linkzly automatically suspends it to protect your server from continued traffic while it is unreachable. A suspended webhook appears with a Suspended status badge in the webhooks list.
To reactivate a suspended webhook, open its actions menu and select Enable. You can also manually suspend any active webhook from the same actions menu if you need to pause deliveries temporarily.
18.6 Delivery Logs
Every delivery attempt — successful or not — is recorded in the delivery log for each webhook. Click View Deliveries from a webhook's actions menu to open the Webhook Deliveries dialog.
Summary stats above the table show counts for Total, Success, Failed, and Pending deliveries.
| Column | Description |
|---|---|
| Status | Success (green), Failed (red), or Pending (yellow) |
| Event | The event type that triggered this delivery (e.g., link.clicked) |
| Sent At | The date and time when the delivery was attempted |
| Response | The HTTP status code your server returned |
| Duration | How long your server took to respond, in milliseconds |
| Attempt | Which attempt this was, displayed as a badge (e.g., #1, #2) |
| Actions | Retry icon (RefreshCw) for failed deliveries; View Details icon for all deliveries |
The retry action is only available for deliveries with a Failed status. Clicking it manually triggers a new delivery attempt immediately.
18.7 Webhook Analytics
Click View Analytics from a webhook's actions menu to open the Webhook Analytics dialog and see a health overview of its delivery performance.
| Metric | Description |
|---|---|
| Health Score | Percentage of successful deliveries. Rated as: Excellent (≥ 80%), Good (≥ 60%), Fair (≥ 40%), Poor (< 40%). |
| Total Deliveries | All-time count of delivery attempts for this webhook |
| Success Rate | Percentage of deliveries that received a 2xx response from your server |
| Avg Response | Mean response time in milliseconds across all deliveries |
| Failures | Current count of consecutive back-to-back failed deliveries. A high number here may indicate your endpoint is down. |
| Event Breakdown | Table showing delivery count and percentage per subscribed event type |
18.8 URL Validation
Use the Validate button on the Webhook URL field to check your endpoint before saving the configuration.
The following checks are performed:
- The URL must be a valid HTTPS URL (HTTP is not accepted, except for
localhostand127.0.0.1during development) - Non-standard port numbers generate a warning
- A root path (
/) generates a suggestion to use a more specific path - A security score is returned based on the results
If the URL is valid, the button area shows a "URL is valid" confirmation along with the security score. Errors and warnings are listed if any checks fail.
After saving, use the Test Webhook button from the actions menu to send a sample event payload to your endpoint and confirm it processes the delivery correctly.
18.9 Bulk Operations
You can manage multiple webhooks at once from the main Webhooks list.
- Select webhooks using the checkboxes on the left side of the table.
- Click the Bulk Actions dropdown that appears.
- Choose one of the available actions:
- Enable Selected — Activate all selected webhooks
- Disable Selected — Deactivate all selected webhooks without deleting them
- Delete Selected — Permanently remove all selected webhooks and their delivery logs
After completing a bulk action, Linkzly displays a confirmation toast showing how many webhooks were updated successfully.
18.10 Webhook Templates
Linkzly provides built-in templates to quickly create webhooks pre-configured for common integration patterns. Click the Templates button in the top-right of the Webhooks page to browse available templates.
Available templates include:
- Slack Notifications — Send Linkzly events to a Slack channel
- Discord Webhook — Post events to a Discord server
- Analytics Webhook — Forward events to an analytics platform
- Team Notifications — Notify on team membership changes
- Domain Management — Alert on domain verification events
- Complete Activity Stream — Subscribe to all available event types
Select a template and customize the URL and any other fields before saving.
Was this helpful?
Help us improve our documentation