Skip to content
Last updated

Set up real-time notifications and event-driven integrations with Freddy webhooks.

Overview

Webhooks allow you to receive HTTP callbacks when specific events occur in your Freddy account. Instead of polling for changes, webhooks push data to your application in real-time.

Supported Events

User Events

  • user.created - New user registered
  • user.updated - User profile updated
  • user.deleted - User account deleted
  • user.verified - Email verification completed

Message Events

  • message.created - New message sent
  • message.updated - Message edited
  • message.deleted - Message deleted

Thread Events

  • thread.created - New conversation thread created
  • thread.updated - Thread metadata updated
  • thread.deleted - Thread deleted

File Events

  • file.uploaded - File upload completed
  • file.deleted - File deleted
  • file.processed - File processing completed

Usage Events

  • usage.threshold - Usage limit threshold reached
  • usage.limit - Usage limit exceeded

Billing Events

  • invoice.created - New invoice generated
  • invoice.paid - Invoice payment completed
  • invoice.failed - Invoice payment failed

Setting Up Webhooks

Create Webhook Endpoint

POST /webhooks

Headers:

Authorization: Bearer jwt_token_here
Content-Type: application/json

Request Body:

{
 "url": "https://your-app.com/webhook",
 "events": ["user.created", "message.created"],
 "description": "Production webhook",
 "active": true
}

Response:

{
 "status": "success",
 "data": {
 "webhook_id": "wh_abc123",
 "url": "https://your-app.com/webhook",
 "secret": "whsec_xyz789...",
 "events": ["user.created", "message.created"],
 "active": true,
 "created_at": "2024-01-01T00:00:00Z"
 }
}

Important: Save the secret - it's only shown once and used to verify webhook signatures.

Webhook Payload Format

All webhooks follow this structure:

{
 "event": "user.created",
 "data": {
 "user_id": "user_abc123",
 "email": "user@example.com",
 "created_at": "2024-01-01T00:00:00Z"
 },
 "timestamp": "2024-01-01T00:00:00Z",
 "webhook_id": "wh_abc123",
 "delivery_id": "del_xyz789"
}

Webhook Headers

Content-Type: application/json
X-Freddy-Event: user.created
X-Freddy-Signature: sha256=abc123...
X-Freddy-Delivery: del_xyz789
User-Agent: Freddy-Webhook/1.0

Verifying Webhook Signatures

Always verify webhook signatures to ensure authenticity:

Python Example

import hmac
import hashlib

def verify_webhook_signature(payload, signature, secret):
 """Verify webhook signature using HMAC SHA256."""
 expected_signature = hmac.new(
 secret.encode(),
 payload,
 hashlib.sha256
 ).hexdigest()

 # Extract signature from header (format: "sha256=...")
 sig_parts = signature.split('=')
 if len(sig_parts) != 2 or sig_parts[0] != 'sha256':
 return False

 received_signature = sig_parts[1]

 # Constant-time comparison to prevent timing attacks
 return hmac.compare_digest(expected_signature, received_signature)

# Flask example
from flask import Flask, request, jsonify

app = Flask(__name__)
WEBHOOK_SECRET = "whsec_your_secret_here"

@app.route('/webhook', methods=['POST'])
def handle_webhook():
 # Get signature from header
 signature = request.headers.get('X-Freddy-Signature')
 if not signature:
 return jsonify({"error": "Missing signature"}), 401

 # Verify signature
 if not verify_webhook_signature(request.data, signature, WEBHOOK_SECRET):
 return jsonify({"error": "Invalid signature"}), 401

 # Process webhook
 payload = request.json
 event_type = payload.get('event')
 event_data = payload.get('data')

 # Handle event
 if event_type == 'user.created':
 handle_user_created(event_data)
 elif event_type == 'message.created':
 handle_message_created(event_data)

 return jsonify({"status": "received"}), 200

Node.js Example

const crypto = require('crypto');
const express = require('express');

const app = express();
const WEBHOOK_SECRET = 'whsec_your_secret_here';

// Parse raw body for signature verification
app.use(express.json({
 verify: (req, res, buf) => {
 req.rawBody = buf.toString();
 }
}));

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

 const receivedSignature = signature.split('=')[1];

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

app.post('/webhook', (req, res) => {
 const signature = req.headers['x-freddy-signature'];

 if (!signature) {
 return res.status(401).json({ error: 'Missing signature' });
 }

 if (!verifyWebhookSignature(req.rawBody, signature, WEBHOOK_SECRET)) {
 return res.status(401).json({ error: 'Invalid signature' });
 }

 const { event, data } = req.body;

 // Handle event
 switch (event) {
 case 'user.created':
 handleUserCreated(data);
 break;
 case 'message.created':
 handleMessageCreated(data);
 break;
 }

 res.json({ status: 'received' });
});

app.listen(3000, () => console.log('Webhook server running'));

Retry Logic

Freddy Retry Behavior

  • Initial attempt: Immediate delivery
  • 1st retry: After 1 minute
  • 2nd retry: After 5 minutes
  • 3rd retry: After 15 minutes
  • 4th retry: After 1 hour
  • 5th retry: After 6 hours
  • Max attempts: 6 total attempts over 24 hours

Expected Response

Your webhook endpoint should:

  • Respond within 30 seconds
  • Return HTTP 2xx status code for success
  • Return HTTP 410 to disable the webhook
  • Return other status codes to trigger retry

Handling Retries

from flask import Flask, request, jsonify
import redis

app = Flask(__name__)
redis_client = redis.Redis(host='localhost', port=6379, db=0)

@app.route('/webhook', methods=['POST'])
def handle_webhook():
 delivery_id = request.headers.get('X-Freddy-Delivery')

 # Check if already processed (idempotency)
 if redis_client.get(f"webhook:{delivery_id}"):
 return jsonify({"status": "already_processed"}), 200

 # Verify signature
 # ... verification code ...

 try:
 # Process webhook
 payload = request.json
 process_event(payload)

 # Mark as processed (expires after 7 days)
 redis_client.setex(f"webhook:{delivery_id}", 604800, "1")

 return jsonify({"status": "processed"}), 200

 except Exception as e:
 # Log error and return 5xx to trigger retry
 print(f"Webhook processing error: {e}")
 return jsonify({"error": "Internal error"}), 500

Webhook Management

List Webhooks

GET /webhooks

Response:

{
 "status": "success",
 "data": [
 {
 "webhook_id": "wh_abc123",
 "url": "https://your-app.com/webhook",
 "events": ["user.created", "message.created"],
 "active": true,
 "created_at": "2024-01-01T00:00:00Z",
 "last_delivery": "2024-01-02T00:00:00Z"
 }
 ]
}

Update Webhook

PUT /webhooks/{webhook_id}

Request Body:

{
 "url": "https://your-app.com/new-webhook",
 "events": ["user.created"],
 "active": true
}

Delete Webhook

DELETE /webhooks/{webhook_id}

Test Webhook

POST /webhooks/{webhook_id}/test

Sends a test event to verify your endpoint.

Monitoring Webhooks

View Delivery History

GET /webhooks/{webhook_id}/deliveries

Response:

{
 "status": "success",
 "data": [
 {
 "delivery_id": "del_xyz789",
 "event": "user.created",
 "status": "success",
 "response_code": 200,
 "attempts": 1,
 "delivered_at": "2024-01-01T00:00:00Z"
 },
 {
 "delivery_id": "del_abc456",
 "event": "message.created",
 "status": "failed",
 "response_code": 500,
 "attempts": 3,
 "last_attempt": "2024-01-01T00:15:00Z",
 "next_retry": "2024-01-01T01:00:00Z"
 }
 ]
}

Redeliver Failed Webhook

POST /webhooks/{webhook_id}/deliveries/{delivery_id}/redeliver

Best Practices

1. Idempotency

Always handle duplicate deliveries:

def process_webhook_idempotently(delivery_id, event_data):
 # Check if already processed
 if is_processed(delivery_id):
 return {"status": "already_processed"}

 # Process event
 result = process_event(event_data)

 # Mark as processed
 mark_as_processed(delivery_id)

 return result

2. Async Processing

Process webhooks asynchronously:

from celery import Celery

celery_app = Celery('webhook_processor')

@app.route('/webhook', methods=['POST'])
def handle_webhook():
 # Verify signature quickly
 verify_signature(request)

 # Queue for async processing
 process_webhook_async.delay(request.json)

 # Return immediately
 return jsonify({"status": "queued"}), 200

@celery_app.task
def process_webhook_async(payload):
 # Time-consuming processing here
 process_event(payload)

3. Error Handling

Log failures for debugging:

import logging

logger = logging.getLogger(__name__)

@app.route('/webhook', methods=['POST'])
def handle_webhook():
 try:
 payload = request.json
 process_event(payload)
 return jsonify({"status": "success"}), 200
 except Exception as e:
 logger.error(f"Webhook processing failed", extra={
 'delivery_id': request.headers.get('X-Freddy-Delivery'),
 'event': payload.get('event'),
 'error': str(e)
 })
 return jsonify({"error": "Processing failed"}), 500

4. Security

  • Always verify signatures
  • Use HTTPS endpoints only
  • Keep webhook secrets secure
  • Implement rate limiting
  • Validate event data

Troubleshooting

Common Issues

Webhook Not Receiving Events:

  • Verify webhook is active
  • Check firewall/security rules
  • Ensure endpoint is publicly accessible
  • Verify SSL certificate is valid

Signature Verification Failing:

  • Check you're using the correct secret
  • Verify you're hashing the raw request body
  • Ensure constant-time comparison

Delivery Failures:

  • Check endpoint responds within 30 seconds
  • Return 2xx status code
  • Review server logs for errors

Next Steps


Build real-time integrations with Freddy webhooks!