# Webhooks 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 ```http POST /webhooks ``` **Headers:** ```http Authorization: Bearer jwt_token_here Content-Type: application/json ``` **Request Body:** ```json { "url": "https://your-app.com/webhook", "events": ["user.created", "message.created"], "description": "Production webhook", "active": true } ``` **Response:** ```json { "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: ```json { "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 ```http 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 ```python 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 ```javascript 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 ```python 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 ```http GET /webhooks ``` **Response:** ```json { "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 ```http PUT /webhooks/{webhook_id} ``` **Request Body:** ```json { "url": "https://your-app.com/new-webhook", "events": ["user.created"], "active": true } ``` ### Delete Webhook ```http DELETE /webhooks/{webhook_id} ``` ### Test Webhook ```http POST /webhooks/{webhook_id}/test ``` Sends a test event to verify your endpoint. ## Monitoring Webhooks ### View Delivery History ```http GET /webhooks/{webhook_id}/deliveries ``` **Response:** ```json { "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 ```http POST /webhooks/{webhook_id}/deliveries/{delivery_id}/redeliver ``` ## Best Practices ### 1. Idempotency Always handle duplicate deliveries: ```python 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: ```python 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: ```python 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 - **[API Reference](/docs/api-reference/introduction)** - Complete endpoint documentation - **[Best Practices](/docs/documentation/best-practices)** - Recommended patterns - **[Error Handling](/docs/documentation/error-handling)** - Comprehensive error management - **[Code Examples](/docs/documentation/examples)** - Practical implementations *Build real-time integrations with Freddy webhooks!*