Set up real-time notifications and event-driven integrations with Freddy webhooks.
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.
user.created- New user registereduser.updated- User profile updateduser.deleted- User account deleteduser.verified- Email verification completed
message.created- New message sentmessage.updated- Message editedmessage.deleted- Message deleted
thread.created- New conversation thread createdthread.updated- Thread metadata updatedthread.deleted- Thread deleted
file.uploaded- File upload completedfile.deleted- File deletedfile.processed- File processing completed
usage.threshold- Usage limit threshold reachedusage.limit- Usage limit exceeded
invoice.created- New invoice generatedinvoice.paid- Invoice payment completedinvoice.failed- Invoice payment failed
POST /webhooksHeaders:
Authorization: Bearer jwt_token_here
Content-Type: application/jsonRequest 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.
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"
}Content-Type: application/json
X-Freddy-Event: user.created
X-Freddy-Signature: sha256=abc123...
X-Freddy-Delivery: del_xyz789
User-Agent: Freddy-Webhook/1.0Always verify webhook signatures to ensure authenticity:
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"}), 200const 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'));- 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
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
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"}), 500GET /webhooksResponse:
{
"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"
}
]
}PUT /webhooks/{webhook_id}Request Body:
{
"url": "https://your-app.com/new-webhook",
"events": ["user.created"],
"active": true
}DELETE /webhooks/{webhook_id}POST /webhooks/{webhook_id}/testSends a test event to verify your endpoint.
GET /webhooks/{webhook_id}/deliveriesResponse:
{
"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"
}
]
}POST /webhooks/{webhook_id}/deliveries/{delivery_id}/redeliverAlways 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 resultProcess 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)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- Always verify signatures
- Use HTTPS endpoints only
- Keep webhook secrets secure
- Implement rate limiting
- Validate event data
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
- API Reference - Complete endpoint documentation
- Best Practices - Recommended patterns
- Error Handling - Comprehensive error management
- Code Examples - Practical implementations
Build real-time integrations with Freddy webhooks!