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
| Event | Description |
|---|---|
url.created | New short URL created |
url.updated | Short URL updated |
url.deleted | Short URL deleted |
url.clicked | Short URL was clicked |
routing.rule_created | Routing rule created |
routing.rule_updated | Routing rule updated |
routing.rule_deleted | Routing rule deleted |
routing.rule_matched | Routing rule matched a visitor |
Creating Webhooks
Create a Webhook
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:
| Parameter | Description | Required | Default |
|---|---|---|---|
name | Webhook name (max 100 chars) | ✅ | - |
url | Target URL (public, HTTPS) | ✅ | - |
secret | Signing secret (max 255 chars) | ❌ | - |
events | Events to subscribe to | ✅ | - |
headers | Custom HTTP headers | ❌ | {} |
isActive | Enable webhook | ❌ | true |
Event Selection
Subscribe to multiple events:
{
"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=clickGet Webhook Details
GET /api/webhooks/{id}Update Webhook
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:
{
"event": "url.clicked",
"timestamp": "2025-01-15T10:30:00Z",
"data": { ... }
}url.created
{
"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
{
"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
{
"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
{
"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
| Header | Description |
|---|---|
X-Webhook-Signature | HMAC-SHA256 signature |
X-Webhook-Event | Event type |
X-Webhook-Delivery-ID | Unique delivery ID |
Signature Format
X-Webhook-Signature: sha256=<hash>Verification Steps
- Get the raw request body (JSON string)
- Compute HMAC-SHA256 using your secret
- Compare with the signature header
Node.js Example:
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:
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:
| Metric | Description |
|---|---|
totalSent | Total deliveries attempted |
totalSuccess | Successful deliveries |
totalFailed | Failed deliveries |
lastSentAt | Last delivery timestamp |
lastError | Last 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}/retryBehavior:
- 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
attemptnumber - 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:
| Status | Description |
|---|---|
| 200 | Retry completed (check isSuccess in response) |
| 400 | Cannot retry a successful delivery |
| 404 | Webhook or log not found |
| 429 | Rate 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=20Response:
{
"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}/testSends a test payload to verify your endpoint is working:
{
"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:
{
"events": ["url.clicked"]
}→ Send to Google Analytics, Mixpanel, etc.
Slack Notifications
Get notified of new URLs:
{
"events": ["url.created"],
"url": "https://hooks.slack.com/services/..."
}CRM Integration
Update customer records on clicks:
{
"events": ["url.clicked"],
"headers": {
"X-API-Key": "your-crm-key"
}
}Custom Dashboards
Feed data to your own analytics:
{
"events": ["url.clicked", "url.created", "url.deleted"]
}Rate Limits
| Operation | Limit |
|---|---|
| Webhook management | 5 requests/minute |
| Manual retry | 5 requests/minute |
| Webhook deliveries | No limit (async) |
Webhook Timeout
- Default timeout: 30 seconds
- Requests timing out are marked as failed
- Configure via
WEBHOOK_TIMEOUTenvironment variable
Next Steps
- API Keys - Authenticate API requests
- Analytics - Track performance
- Smart Routing - Conditional routing