Webhooks
Receive real-time notifications on FluenzR events.
Webhooks
Webhooks allow you to receive real-time notifications when events occur in FluenzR.
Info Webhooks are only available on the Pro plan.
How it works
- You configure a webhook URL in FluenzR
- When an event occurs, FluenzR sends an HTTP POST request to your URL
- Your server processes the event and responds with a 2xx code
Available events
Contacts
| Event | Description |
|---|---|
contact.created | New contact created |
contact.updated | Contact updated |
contact.deleted | Contact deleted |
Campaigns
| Event | Description |
|---|---|
campaign.started | Campaign started |
campaign.paused | Campaign paused |
campaign.completed | Campaign completed |
Emails
| Event | Description |
|---|---|
email.sent | Email sent |
email.delivered | Email delivered |
email.opened | Email opened |
email.clicked | Link clicked |
email.replied | Reply received |
email.bounced | Bounce detected |
email.unsubscribed | Unsubscribe |
Create a webhook
Via the interface
- Go to Settings > Webhooks
- Click + New webhook
- Configure the URL and events
- Save
Via the API
POST /webhooks
{
"url": "https://your-server.com/webhooks/fluenzr",
"events": [
"email.sent",
"email.opened",
"email.replied"
],
"secret": "your_webhook_secret"
}
Example
curl -X POST "https://api.fluenzr.co/v1/webhooks" \
-H "Authorization: Bearer flz_sk_xxx" \
-H "Content-Type: application/json" \
-d '{
"url": "https://your-server.com/webhooks/fluenzr",
"events": ["email.opened", "email.replied"],
"secret": "whsec_abc123"
}'
Response
{
"success": true,
"data": {
"id": "whk_abc123",
"url": "https://your-server.com/webhooks/fluenzr",
"events": ["email.opened", "email.replied"],
"status": "active",
"createdAt": "2025-01-15T10:30:00Z"
}
}
Payload format
General structure
{
"id": "evt_abc123",
"type": "email.opened",
"timestamp": "2025-01-15T14:32:00Z",
"data": {
// Event-specific data
}
}
email.sent
{
"id": "evt_abc123",
"type": "email.sent",
"timestamp": "2025-01-15T10:00:00Z",
"data": {
"campaignId": "cmp_xyz789",
"campaignName": "Q1 Prospecting",
"contactId": "con_def456",
"contactEmail": "john@example.com",
"emailNodeId": "node_2",
"emailSubject": "John, quick question"
}
}
email.opened
{
"id": "evt_abc124",
"type": "email.opened",
"timestamp": "2025-01-15T14:32:00Z",
"data": {
"campaignId": "cmp_xyz789",
"contactId": "con_def456",
"contactEmail": "john@example.com",
"emailNodeId": "node_2",
"openCount": 1,
"firstOpen": true,
"userAgent": "Mozilla/5.0...",
"ip": "192.168.1.1"
}
}
email.clicked
{
"id": "evt_abc125",
"type": "email.clicked",
"timestamp": "2025-01-15T14:35:00Z",
"data": {
"campaignId": "cmp_xyz789",
"contactId": "con_def456",
"contactEmail": "john@example.com",
"emailNodeId": "node_2",
"linkUrl": "https://fluenzr.co/demo",
"clickCount": 1
}
}
email.replied
{
"id": "evt_abc126",
"type": "email.replied",
"timestamp": "2025-01-15T15:10:00Z",
"data": {
"campaignId": "cmp_xyz789",
"contactId": "con_def456",
"contactEmail": "john@example.com",
"emailNodeId": "node_2",
"replySubject": "Re: John, quick question",
"replyPreview": "Hello, I would be interested in..."
}
}
email.bounced
{
"id": "evt_abc127",
"type": "email.bounced",
"timestamp": "2025-01-15T10:05:00Z",
"data": {
"campaignId": "cmp_xyz789",
"contactId": "con_def456",
"contactEmail": "john@example.com",
"bounceType": "hard",
"bounceReason": "User unknown"
}
}
contact.created
{
"id": "evt_abc128",
"type": "contact.created",
"timestamp": "2025-01-15T09:00:00Z",
"data": {
"contact": {
"id": "con_def456",
"email": "john@example.com",
"firstName": "John",
"lastName": "Smith",
"company": "Acme Corp"
}
}
}
Signature verification
Each webhook includes a signature to verify its authenticity.
Headers
X-FluenzR-Signature: sha256=abc123...
X-FluenzR-Timestamp: 1640000000
Verification (Node.js)
const crypto = require('crypto');
function verifyWebhookSignature(payload, signature, timestamp, secret) {
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(`${timestamp}.${payload}`)
.digest('hex');
return `sha256=${expectedSignature}` === signature;
}
// In your endpoint
app.post('/webhooks/fluenzr', (req, res) => {
const signature = req.headers['x-fluenzr-signature'];
const timestamp = req.headers['x-fluenzr-timestamp'];
const payload = JSON.stringify(req.body);
if (!verifyWebhookSignature(payload, signature, timestamp, WEBHOOK_SECRET)) {
return res.status(401).send('Invalid signature');
}
// Process the event
const event = req.body;
console.log(`Event received: ${event.type}`);
res.status(200).send('OK');
});
Verification (Python)
import hmac
import hashlib
def verify_webhook_signature(payload, signature, timestamp, secret):
expected = hmac.new(
secret.encode(),
f"{timestamp}.{payload}".encode(),
hashlib.sha256
).hexdigest()
return f"sha256={expected}" == signature
# In your Flask endpoint
@app.route('/webhooks/fluenzr', methods=['POST'])
def handle_webhook():
signature = request.headers.get('X-FluenzR-Signature')
timestamp = request.headers.get('X-FluenzR-Timestamp')
payload = request.get_data(as_text=True)
if not verify_webhook_signature(payload, signature, timestamp, WEBHOOK_SECRET):
return 'Invalid signature', 401
event = request.get_json()
print(f"Event received: {event['type']}")
return 'OK', 200
Retry handling
FluenzR automatically retries failed webhooks:
| Attempt | Delay |
|---|---|
| 1 | Immediate |
| 2 | 1 minute |
| 3 | 5 minutes |
| 4 | 30 minutes |
| 5 | 2 hours |
After 5 failures, the webhook is marked as "failed".
Success codes
Your server must respond with an HTTP 2xx code within 30 seconds.
| Code | Result |
|---|---|
| 200-299 | Success |
| 3xx | Retry |
| 4xx | Permanent failure |
| 5xx | Retry |
| Timeout | Retry |
Manage webhooks
List webhooks
GET /webhooks
Update a webhook
PUT /webhooks/:id
{
"events": ["email.opened", "email.replied", "email.clicked"]
}
Delete a webhook
DELETE /webhooks/:id
Test a webhook
POST /webhooks/:id/test
Sends a test event to your URL.
View logs
GET /webhooks/:id/logs
Retrieves delivery attempt history.
{
"success": true,
"data": [
{
"id": "log_abc123",
"eventId": "evt_xyz789",
"eventType": "email.opened",
"status": "delivered",
"statusCode": 200,
"responseTime": 145,
"timestamp": "2025-01-15T14:32:00Z"
}
]
}
Best practices
Respond quickly
Your endpoint must respond in less than 30 seconds. Process in the background if needed.
Idempotence
Your code must handle duplicate events. Use the event ID to deduplicate.
Monitoring
Monitor your webhooks:
- Success rate
- Response time
- Frequent errors
Security
- Always verify the signature
- Use HTTPS only
- Validate the timestamp (reject if > 5 minutes)
