Webhooks
Subscribe to real-time HTTP callbacks for link lifecycle events. Flyn signs every webhook delivery with HMAC-SHA256 so you can verify authenticity.
Available Events
| Event | Description |
|---|---|
| link.click | A short link was clicked. Includes geo, device, browser, and referrer data. |
| link.create | A new link was created. Includes the full link object. |
| link.update | A link was updated. Includes the updated link object. |
| link.delete | A link was deleted. Includes the link data as it was before deletion. |
| link.expired | A link has expired based on its expiresAt date. |
| domain.verified | A custom domain was successfully verified. |
Webhook Payload
Every webhook delivery is an HTTP POST with a JSON body:
Payload Structure
{
"event": "link.click",
"timestamp": 1711929600000,
"data": {
// Event-specific payload (link object, click data, etc.)
}
}Delivery Headers
| Field | Type | Description |
|---|---|---|
| Content-Type | string | Always "application/json" |
| X-Flyn-Signature | string | HMAC-SHA256 signature: t={timestamp},v1={hex_signature} |
| X-Flyn-Event | string | The event type (e.g. "link.click") |
| User-Agent | string | Always "Flyn-Webhook/1.0" |
Verifying Signatures
Always verify webhook signatures to ensure the request came from Flyn and was not tampered with. The signature is computed as:
Signature Algorithm
HMAC-SHA256(secret, "{timestamp}.{body}")
// Where:
// - secret = your webhook secret (whsec_...)
// - timestamp = the "t" value from X-Flyn-Signature header
// - body = the raw JSON request bodyNode.js Verification Example
import { createHmac } from 'crypto';
function verifyWebhook(req, secret) {
const sigHeader = req.headers['x-flyn-signature'];
const [tPart, vPart] = sigHeader.split(',');
const timestamp = tPart.replace('t=', '');
const receivedSig = vPart.replace('v1=', '');
// Reject requests older than 5 minutes (replay protection)
const age = Date.now() - parseInt(timestamp);
if (age > 5 * 60 * 1000) {
throw new Error('Webhook timestamp too old');
}
const body = JSON.stringify(req.body);
const expectedSig = createHmac('sha256', secret)
.update(`${timestamp}.${body}`)
.digest('hex');
if (receivedSig !== expectedSig) {
throw new Error('Invalid webhook signature');
}
return req.body; // Verified!
}Limits & Reliability
- Maximum 10 webhooks per account
- Delivery timeout: 10 seconds, your endpoint must respond within 10s
- On failure, the
failure_countis incremented - Webhooks are automatically disabled after repeated failures
- Reactivating a webhook resets its failure count
- Webhook URLs must be public HTTPS/HTTP endpoints (no private/internal IPs)
Create a Webhook
POST/api/webhooks
Request Body
| Parameter | Type | Required | Description |
|---|---|---|---|
| url | string | Yes | The HTTPS endpoint URL to receive webhook events |
| events | string[] | Yes | Array of event types to subscribe to |
Request Example
curl -X POST https://www.flyn.to/api/webhooks \
-H "Authorization: Bearer flyn_sk_live_..." \
-H "Content-Type: application/json" \
-d '{
"url": "https://api.yoursite.com/webhooks/flyn",
"events": ["link.click", "link.create", "link.delete"]
}'Response (201)
Response
{
"webhook": {
"id": "webhook-uuid",
"url": "https://api.yoursite.com/webhooks/flyn",
"events": ["link.click", "link.create", "link.delete"],
"secret": "whsec_a1b2c3d4e5f6...",
"status": "active",
"createdAt": 1711929600000,
"failureCount": 0
}
}Save the secret immediately. It is shown in full only on creation. Use it to verify webhook signatures.
List Webhooks
GET/api/webhooks
Response Fields
| Field | Type | Description |
|---|---|---|
| webhooks[].id | string | Webhook UUID |
| webhooks[].url | string | Endpoint URL |
| webhooks[].events | string[] | Subscribed events |
| webhooks[].secret | string | Masked secret (first 8 chars visible) |
| webhooks[].status | string | "active" or "inactive" |
| webhooks[].createdAt | number | Unix timestamp (ms) |
| webhooks[].lastTriggeredAt | number | null | Last successful delivery timestamp |
| webhooks[].failureCount | number | Consecutive delivery failures |
Update a Webhook
PATCH/api/webhooks/:id
Request Body
| Parameter | Type | Required | Description |
|---|---|---|---|
| url | string | No | New endpoint URL |
| events | string[] | No | Replace subscribed events |
| status | string | No | "active" or "inactive". Reactivating resets failure count. |
Send Test Ping
Sends a test webhook.test event to your endpoint to verify it is receiving and processing deliveries correctly.
POST/api/webhooks/:id
Response (200)
{ "success": true, "status": 200 }If delivery fails:
{ "success": false, "status": 500, "statusText": "Internal Server Error" }Delete a Webhook
DELETE/api/webhooks/:id
Response (200)
{ "success": true }Was this page helpful? Spotted something wrong?