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

  1. You configure a webhook URL in FluenzR
  2. When an event occurs, FluenzR sends an HTTP POST request to your URL
  3. Your server processes the event and responds with a 2xx code

Available events

Contacts

EventDescription
contact.createdNew contact created
contact.updatedContact updated
contact.deletedContact deleted

Campaigns

EventDescription
campaign.startedCampaign started
campaign.pausedCampaign paused
campaign.completedCampaign completed

Emails

EventDescription
email.sentEmail sent
email.deliveredEmail delivered
email.openedEmail opened
email.clickedLink clicked
email.repliedReply received
email.bouncedBounce detected
email.unsubscribedUnsubscribe

Create a webhook

Via the interface

  1. Go to Settings > Webhooks
  2. Click + New webhook
  3. Configure the URL and events
  4. 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:

AttemptDelay
1Immediate
21 minute
35 minutes
430 minutes
52 hours

After 5 failures, the webhook is marked as "failed".

Success codes

Your server must respond with an HTTP 2xx code within 30 seconds.

CodeResult
200-299Success
3xxRetry
4xxPermanent failure
5xxRetry
TimeoutRetry

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)