
Marketing and engineering teams deal with siloed click data. Reports arrive hours late. Manual exports introduce errors. Decisions get made on stale information.
Rebrandly webhooks deliver click event data to your systems within 10 seconds of each interaction. Analytics update in real-time. Automations trigger while user intent is active. Teams see complete campaign context without logging into multiple platforms.
Compared to polling: Instead of repeatedly checking for new clicks, webhooks deliver events as they happen; fewer API calls, no polling delays, lower infrastructure costs.
Compared to batch exports: Manual exports mean remembering to pull data, converting file formats, and uploading to destination systems. Webhooks automate this workflow, cutting manual data entry by 95%.
This guide covers implementation, security, and integration patterns for developers building automated workflows with Rebrandly's webhook infrastructure.
Every time someone clicks your branded link, Rebrandly sends an HTTP POST to your endpoint with this JSON payload:
{
"data": [
{
"timestamp": "2025-10-25T02:36:47.132Z",
"client": {
"agent": {
"browser": {
"name": "chrome-mobile",
"version": "121.0.0.0"
},
"os": {
"name": "android",
"version": "android-10"
},
"device": {
"name": "smartphone"
}
},
"geo": {
"country": "US",
"region": "CA",
"city": "San Francisco"
}
},
"route": {
"id": "12345abcde",
"slashtag": "product-launch",
"createdAt": "2023-08-31T04:13:48.385",
"domain": {
"id": "8f104cc5b6ee4a4ba7897b06ac2ddcfb",
"raw": "brand.link",
"zone": "link.brand"
},
"destination": {
"raw": "https://example.com/landing-page",
"protocol": "https",
"hostname": "example.com",
"path": "/landing-page"
}
}
}
],
"key": "userId_32f5164718384fcda6503f550295885e-configId_db2a7cdeb91f4a2b9"
}
Request headers:
application/jsonWhat's in the payload:
Here's a minimal webhook endpoint that validates the secret and stores events:
# Python/Flask example
from flask import Flask, request, jsonify
import os
app = Flask(__name__)
@app.route('/webhook', methods=['POST'])
def webhook_handler():
# 1. Validate the secret key
expected_secret = os.environ.get('apikey')
received_secret = request.headers.get('apikey')
if expected_secret != received_secret:
return jsonify({'error': 'Unauthorized'}), 401
# 2. Get the payload
event_data = request.get_json()
# 3. Process it (store, forward, trigger workflows)
for event in event_data['data']:
print(f"Click at {event['timestamp']}")
print(f"Link: {event['route']['slashtag']}")
print(f"Location: {event['client']['geo']['city']}")
# Save to database, call other APIs, etc.
# 4. Return success quickly (within 30 seconds)
return jsonify({'status': 'received'}), 200
if __name__ == '__main__':
app.run(port=5000)
// Node.js/Express example
const express = require('express');
const app = express();
app.use(express.json());
app.post('/webhook', (req, res) => {
// 1. Validate the secret key
const expectedSecret = process.env.REBRANDLY_SECRET_KEY;
const receivedSecret = req.headers['apikey'];
if (expectedSecret !== receivedSecret) {
return res.status(401).json({ error: 'Unauthorized' });
}
// 2. Get the payload
const eventData = req.body;
// 3. Process it
eventData.data.forEach(event => {
console.log(`Click at ${event.timestamp}`);
console.log(`Link: ${event.route.slashtag}`);
console.log(`Location: ${event.client.geo.city}`);
// Save to database, call other APIs, etc.
});
// 4. Return success quickly
res.status(200).json({ status: 'received' });
});
app.listen(5000, () => {
console.log('Webhook listener running on port 5000');
});
Your endpoint requirements:
We don't retry failed deliveries. Each event is sent once. If your endpoint is down or returns an error, that event is gone.
Events may arrive out of order. Network conditions can cause later clicks to arrive before earlier ones. Use the timestamp field to order events correctly.
Design for reliability:
Don't process events synchronously in your webhook handler. Accept the event, queue it, return success immediately, then process in the background:
# Python with queue-based processing
from flask import Flask, request, jsonify
import queue
import threading
app = Flask(__name__)
event_queue = queue.Queue()
@app.route('/webhook', methods=['POST'])
def webhook_handler():
# Validate secret key
if not validate_webhook(request):
return jsonify({'error': 'Unauthorized'}), 401
# Queue event immediately, don't process synchronously
event_data = request.get_json()
event_queue.put(event_data)
# Return success before processing
return jsonify({'status': 'received'}), 200
def process_events():
while True:
event = event_queue.get()
# Process event: save to database, call APIs, etc.
handle_event(event)
event_queue.task_done()
# Start background processor
processor = threading.Thread(target=process_events, daemon=True)
processor.start()
This keeps your endpoint fast (responding in under a second), reducing timeout risk while still processing events thoroughly.
Always validate the secret key. When you create a webhook in the Rebrandly dashboard, we generate a random secret. We send this in the apikey header. Check it on every request.
Secret key format: Your secret key is a standard UUID (128-bit identifier) in the format of 8-4-4-4-12 hexadecimal characters (e.g., 550e8400-e29b-41d4-a716-446655440000). This follows the UUID v4 standard.
If the secret is rotated and a user is using the UUID in an external system, they'll need to update it. Note that this key is not required to call webhooks.
Store secrets properly:
Treat webhooks like any external API:
Delivery speed: Events typically arrive within a few seconds to a couple of minutes after the click. Batching is managed directly by AWS when we store the clicks—it will trigger batches or single clicks depending on volume.
Success rate: We maintain 99.9% successful send rate (Note: This measurement doesn't include customer endpoint failures—only successful sends from Rebrandly's side).
No rate limits on events: Your endpoint receives events as fast as people click your links—from occasional traffic to high-volume bursts during major campaigns. There are no limits set up right now. Events are queued on our side, so your endpoint receives them as quickly as it can process them.
Plan-based webhook limits (these are configuration limits, not event limits):
If you hit the webhook limit, you can delete old webhooks you don't need. There are warnings when you approach the limit.
Design your webhook listener to handle traffic spikes. Use load balancing, auto-scaling, and connection pooling for high-traffic campaigns.
1. Set up a test endpoint
Use a service like webhook.site create a public URL for local testing.
2. Create the webhook in Rebrandly
Go to Account Settings > Webhooks:
3. Send a test event
Click "Send test event" in the Rebrandly dashboard. Check your endpoint logs to verify:
Test event format: Test events use synthetic data but match the exact structure of production payloads. Not all parameters will be populated—test events are designed to verify connectivity and authentication, not to represent actual click data.
4. Trigger a real click
Click one of your branded links. Within 10 seconds, you should receive a webhook with real click data.
5. Test failure scenarios
from google.cloud import bigquery
from flask import Flask, request, jsonify
app = Flask(__name__)
client = bigquery.Client()
table_id = "project.dataset.clicks"
@app.route('/webhook', methods=['POST'])
def webhook_handler():
if not validate_webhook(request):
return jsonify({'error': 'Unauthorized'}), 401
event_data = request.get_json()
rows = []
for event in event_data['data']:
rows.append({
'timestamp': event['timestamp'],
'slashtag': event['route']['slashtag'],
'country': event['client']['geo']['country'],
'city': event['client']['geo']['city'],
'device': event['client']['agent']['device']['name'],
'destination': event['route']['destination']['raw']
})
# Insert rows into BigQuery
errors = client.insert_rows_json(table_id, rows)
if errors:
print(f"Errors: {errors}")
return jsonify({'status': 'error'}), 500
return jsonify({'status': 'received'}), 200
const express = require('express');
const axios = require('axios');
app.post('/webhook', async (req, res) => {
if (!validateWebhook(req)) {
return res.status(401).json({ error: 'Unauthorized' });
}
const eventData = req.body;
for (const event of eventData.data) {
// Update contact in HubSpot based on link clicked
const linkClicked = event.route.slashtag;
if (linkClicked === 'pricing') {
await axios.post('https://api.hubapi.com/contacts/v1/contact/email/{email}/profile', {
properties: [
{
property: 'viewed_pricing',
value: 'true'
},
{
property: 'last_pricing_view',
value: event.timestamp
}
]
}, {
headers: { 'Authorization': `Bearer ${process.env.HUBSPOT_API_KEY}` }
});
}
}
res.status(200).json({ status: 'received' });
});
import requests
def send_to_ga4(event):
# GA4 Measurement Protocol
ga4_endpoint = f"https://www.google-analytics.com/mp/collect?measurement_id={GA4_MEASUREMENT_ID}&api_secret={GA4_API_SECRET}"
payload = {
'client_id': event['key'],
'events': [{
'name': 'link_click',
'params': {
'slashtag': event['route']['slashtag'],
'destination': event['route']['destination']['raw'],
'country': event['client']['geo']['country'],
'city': event['client']['geo']['city'],
'device': event['client']['agent']['device']['name']
}
}]
}
response = requests.post(ga4_endpoint, json=payload)
return response.status_code == 204
@app.route('/webhook', methods=['POST'])
def webhook_handler():
if not validate_webhook(request):
return jsonify({'error': 'Unauthorized'}), 401
event_data = request.get_json()
for event in event_data['data']:
send_to_ga4(event)
return jsonify({'status': 'received'}), 200
app.post('/webhook', async (req, res) => {
if (!validateWebhook(req)) {
return res.status(401).json({ error: 'Unauthorized' });
}
const eventData = req.body;
for (const event of eventData.data) {
// Alert team when specific links get clicked
const linkClicked = event.route.slashtag;
const location = `${event.client.geo.city}, ${event.client.geo.country}`;
if (linkClicked === 'product-demo') {
await axios.post(process.env.SLACK_WEBHOOK_URL, {
text: `🎯 Product demo link clicked!\nLocation: ${location}\nTime: ${event.timestamp}`
});
}
}
res.status(200).json({ status: 'received' });
});
Problem: Events aren't arriving
Check:
Problem: Authentication failing
Check:
Problem: Events arriving out of order
This is expected. Use the timestamp field to sort events correctly. Make your database operations idempotent so processing the same event twice doesn't cause problems.
Problem: Missing events during downtime
We don't retry failed deliveries. If your endpoint was down, those events are lost. Options:
Problem: Timeout errors
Your endpoint is taking longer than 30 seconds to respond. Solution:
Can I filter which events I receive?
Not yet. Webhooks send all click events from the configured workspace. Filter on your end based on slashtag, destination URL, or other payload fields.
What event types are supported?
Just click events right now. We're exploring link creation, link updates, and workspace activity events for future releases.
Can I use the same endpoint for multiple workspaces?
Yes, but you'll need to create a separate webhook configuration for each workspace. Use the key field in the payload to identify which webhook sent the event.
Do webhooks work with QR codes?
Yes. QR code scans trigger the same click events as direct link clicks.
Are events retried if my endpoint fails?
No. We don't retry events. Each is sent once. Design your endpoint for reliability.
Can I see delivery history?
Yes. In the Rebrandly dashboard, open your webhook details to see recent delivery attempts, success rates, and response codes. The last 30 days of history is stored.
Real-time analytics: Stream click events directly into Google Analytics 4, Segment, or your data warehouse. See campaign performance alongside other metrics without switching platforms.
Automated workflows: Trigger email sequences when prospects click pricing pages. Update lead scores in your CRM based on content engagement. Send Slack notifications when campaigns hit traffic thresholds.
Custom business logic: Route click data to internal tools, trigger complex multi-step workflows, or combine click events with other data sources for custom attribution models.
Webhooks automate data flow that previously required manual exports. Compared to batch exports, webhooks cut manual data entry by 95%. Compared to polling, webhooks deliver events as they happen—fewer API calls, no delays, lower infrastructure costs.
Start simple: build an endpoint that logs events. Then add the business logic you need—database storage, API calls, workflow triggers.
Questions? Contact Rebrandly support or visit our API documentation.