Skip to content

Webhooks ​

Receive real-time notifications when events occur in your short URL system.

Overview ​

Webhooks allow you to integrate Open Short URL with external systems by sending HTTP requests when specific events occur, such as URL clicks or creation.

Webhook Lifecycle ​

Supported Events ​

EventDescription
url.createdNew short URL created
url.updatedShort URL updated
url.deletedShort URL deleted
url.clickedShort URL was clicked
routing.rule_createdRouting rule created
routing.rule_updatedRouting rule updated
routing.rule_deletedRouting rule deleted
routing.rule_matchedRouting rule matched a visitor

Creating Webhooks ​

Create a Webhook ​

json
POST /api/webhooks

{
  "name": "Click Notifications",
  "url": "https://your-server.com/webhooks/clicks",
  "secret": "your-webhook-secret",
  "events": ["url.clicked"],
  "headers": {
    "Authorization": "Bearer your-token"
  },
  "isActive": true
}

Parameters:

ParameterDescriptionRequiredDefault
nameWebhook name (max 100 chars)✅-
urlTarget URL (public, HTTPS)✅-
secretSigning secret (max 255 chars)❌-
eventsEvents to subscribe to✅-
headersCustom HTTP headers❌{}
isActiveEnable webhook❌true

Event Selection ​

Subscribe to multiple events:

json
{
  "events": ["url.created", "url.clicked", "url.deleted"]
}

WARNING

You must specify at least one event.

Managing Webhooks ​

List Webhooks ​

GET /api/webhooks?page=1&pageSize=10&search=click

Get Webhook Details ​

GET /api/webhooks/{id}

Update Webhook ​

json
PUT /api/webhooks/{id}

{
  "name": "Updated Name",
  "events": ["url.clicked", "url.created"],
  "isActive": true
}

Delete Webhook ​

DELETE /api/webhooks/{id}

Payload Structure ​

Common Fields ​

All webhook payloads include:

json
{
  "event": "url.clicked",
  "timestamp": "2025-01-15T10:30:00Z",
  "data": { ... }
}

url.created ​

json
{
  "event": "url.created",
  "timestamp": "2025-01-15T10:30:00Z",
  "data": {
    "id": "url_123",
    "slug": "my-link",
    "originalUrl": "https://example.com/page",
    "title": "My Link",
    "userId": "user_456",
    "createdAt": "2025-01-15T10:30:00Z"
  }
}

url.updated ​

json
{
  "event": "url.updated",
  "timestamp": "2025-01-15T10:35:00Z",
  "data": {
    "id": "url_123",
    "slug": "my-link",
    "changes": {
      "title": {
        "old": "Old Title",
        "new": "New Title"
      }
    }
  }
}

url.clicked ​

json
{
  "event": "url.clicked",
  "timestamp": "2025-01-15T10:40:00Z",
  "data": {
    "urlId": "url_123",
    "variantId": "var_789",
    "clickData": {
      "ip": "192.168.1.1",
      "userAgent": "Mozilla/5.0...",
      "referer": "https://google.com",
      "country": "Taiwan",
      "city": "Taipei",
      "device": "Mobile",
      "os": "iOS",
      "browser": "Safari",
      "utmSource": "newsletter",
      "utmMedium": "email",
      "utmCampaign": "summer",
      "utmTerm": null,
      "utmContent": null,
      "utmId": null,
      "utmSourcePlatform": null
    }
  }
}

routing.rule_matched ​

json
{
  "event": "routing.rule_matched",
  "timestamp": "2025-01-15T10:45:00Z",
  "data": {
    "urlId": "url_123",
    "ruleId": "rule_456",
    "ruleName": "iOS Users",
    "targetUrl": "https://apps.apple.com/app/myapp",
    "clickData": { ... }
  }
}

Signature Verification ​

Verify webhook authenticity using the signature header.

Headers Sent ​

HeaderDescription
X-Webhook-SignatureHMAC-SHA256 signature
X-Webhook-EventEvent type
X-Webhook-Delivery-IDUnique delivery ID

Signature Format ​

X-Webhook-Signature: sha256=<hash>

Verification Steps ​

  1. Get the raw request body (JSON string)
  2. Compute HMAC-SHA256 using your secret
  3. Compare with the signature header

Node.js Example:

javascript
const crypto = require('crypto');

function verifyWebhookSignature(payload, signature, secret) {
  const expectedSignature =
    'sha256=' +
    crypto.createHmac('sha256', secret).update(payload).digest('hex');

  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expectedSignature)
  );
}

// In your webhook handler
app.post('/webhook', (req, res) => {
  const signature = req.headers['x-webhook-signature'];
  const payload = JSON.stringify(req.body);

  if (!verifyWebhookSignature(payload, signature, 'your-secret')) {
    return res.status(401).send('Invalid signature');
  }

  // Process webhook...
  res.status(200).send('OK');
});

Python Example:

python
import hmac
import hashlib

def verify_webhook_signature(payload, signature, secret):
    expected = 'sha256=' + hmac.new(
        secret.encode(),
        payload.encode(),
        hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(signature, expected)

Delivery & Retries ​

Delivery Statistics ​

Each webhook tracks:

MetricDescription
totalSentTotal deliveries attempted
totalSuccessSuccessful deliveries
totalFailedFailed deliveries
lastSentAtLast delivery timestamp
lastErrorLast error message

Automatic Retry ​

Failed deliveries are retried automatically:

  • Exponential backoff
  • Maximum 3 retry attempts
  • Logged for debugging

Manual Retry ​

You can manually retry a failed delivery from the webhook logs:

POST /api/webhooks/{webhookId}/logs/{logId}/retry

Behavior:

  • Resends the original payload using the current webhook configuration
  • Only failed deliveries can be retried (retrying a successful delivery returns 400)
  • Creates a new log entry with an incremented attempt number
  • Updates webhook delivery statistics (totalSent, totalSuccess/totalFailed)
  • DNS/SSRF validation is performed before each retry
  • Rate limited to 5 requests per minute

Response: Returns the new WebhookLog entry for the retry attempt.

Error Codes:

StatusDescription
200Retry completed (check isSuccess in response)
400Cannot retry a successful delivery
404Webhook or log not found
429Rate limit exceeded

TIP

In the UI, failed deliveries show a Retry button in the webhook logs dialog.

Webhook Logs ​

View delivery history:

GET /api/webhooks/{id}/logs?page=1&pageSize=20

Response:

json
{
  "logs": [
    {
      "id": "log_123",
      "event": "url.clicked",
      "status": "success",
      "statusCode": 200,
      "responseBody": "OK",
      "attempt": 1,
      "sentAt": "2025-01-15T10:40:00Z"
    },
    {
      "id": "log_124",
      "event": "url.clicked",
      "status": "failed",
      "statusCode": 500,
      "responseBody": "Internal Server Error",
      "attempt": 1,
      "sentAt": "2025-01-15T10:45:00Z"
    }
  ]
}

Testing Webhooks ​

Send Test Payload ​

POST /api/webhooks/{id}/test

Sends a test payload to verify your endpoint is working:

json
{
  "event": "webhook.test",
  "timestamp": "2025-01-15T10:50:00Z",
  "data": {
    "message": "This is a test webhook delivery"
  }
}

Best Practices ​

1. Always Verify Signatures ​

Never trust webhook payloads without verification:

  • Always set a webhook secret
  • Verify every incoming request
  • Reject invalid signatures

2. Respond Quickly ​

Return 200 OK as fast as possible:

  • Process webhooks asynchronously
  • Use message queues for heavy processing
  • Respond before doing work

3. Handle Idempotency ​

Webhooks may be delivered multiple times:

  • Store processed delivery IDs
  • Skip duplicate deliveries
  • Design handlers to be idempotent

4. Use HTTPS ​

Always use HTTPS endpoints:

  • Protects payload data
  • Required for signature verification
  • Prevents man-in-the-middle attacks

5. Monitor Failures ​

Watch for delivery issues:

  • Check webhook logs regularly
  • Set up alerts for failures
  • Investigate persistent errors

Use Cases ​

Real-Time Analytics ​

Track clicks in external systems:

json
{
  "events": ["url.clicked"]
}

→ Send to Google Analytics, Mixpanel, etc.

Slack Notifications ​

Get notified of new URLs:

json
{
  "events": ["url.created"],
  "url": "https://hooks.slack.com/services/..."
}

CRM Integration ​

Update customer records on clicks:

json
{
  "events": ["url.clicked"],
  "headers": {
    "X-API-Key": "your-crm-key"
  }
}

Custom Dashboards ​

Feed data to your own analytics:

json
{
  "events": ["url.clicked", "url.created", "url.deleted"]
}

Rate Limits ​

OperationLimit
Webhook management5 requests/minute
Manual retry5 requests/minute
Webhook deliveriesNo limit (async)

Webhook Timeout ​

  • Default timeout: 30 seconds
  • Requests timing out are marked as failed
  • Configure via WEBHOOK_TIMEOUT environment variable

Next Steps ​

Released under the MIT License.