Webhook Guide
Overview
Webhooks are HTTP callback mechanisms that allow applications to automatically send HTTP requests to specified URLs when specific events occur. This guide will help you understand how to configure, use, and manage webhooks.
Quick Start
Basic Concepts
- Webhook URL: The endpoint that receives webhook events
- Event Types: Specific actions that trigger webhooks
- Payload: Data sent to the webhook URL
- Signature: Security mechanism to verify webhook authenticity
Creating a Webhook
javascript
// Create webhook configuration
const webhook = {
url: 'https://your-app.com/webhook',
events: ['user.created', 'user.updated', 'user.deleted'],
secret: 'your-webhook-secret',
active: true
};
// Register webhook
const response = await fetch('/api/webhooks', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer your-api-token'
},
body: JSON.stringify(webhook)
});Supported Event Types
User Events
user.created- User creationuser.updated- User information updateuser.deleted- User deletionuser.login- User loginuser.logout- User logout
Project Events
project.created- Project creationproject.updated- Project updateproject.deleted- Project deletionproject.shared- Project sharing
File Events
file.uploaded- File uploadfile.downloaded- File downloadfile.deleted- File deletionfile.shared- File sharing
Webhook Payload Format
Standard Payload Structure
json
{
"id": "evt_1234567890",
"type": "user.created",
"created": 1640995200,
"data": {
"object": {
"id": "user_123",
"email": "user@example.com",
"name": "John Doe",
"created_at": "2023-01-01T00:00:00Z"
}
},
"request": {
"id": "req_1234567890",
"idempotency_key": null
}
}Event-Specific Payloads
User Creation Event
json
{
"type": "user.created",
"data": {
"object": {
"id": "user_123",
"email": "user@example.com",
"name": "John Doe",
"role": "user",
"created_at": "2023-01-01T00:00:00Z",
"metadata": {
"source": "registration_form"
}
}
}
}Project Update Event
json
{
"type": "project.updated",
"data": {
"object": {
"id": "proj_456",
"name": "My Project",
"description": "Updated description",
"updated_at": "2023-01-01T12:00:00Z",
"changes": {
"description": {
"from": "Old description",
"to": "Updated description"
}
}
}
}
}Receiving and Processing Webhooks
Node.js Example
javascript
const express = require('express');
const crypto = require('crypto');
const app = express();
// Middleware: Verify webhook signature
function verifyWebhookSignature(req, res, next) {
const signature = req.headers['x-webhook-signature'];
const payload = JSON.stringify(req.body);
const secret = process.env.WEBHOOK_SECRET;
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
if (signature !== `sha256=${expectedSignature}`) {
return res.status(401).json({ error: 'Invalid signature' });
}
next();
}
// Webhook endpoint
app.post('/webhook', express.json(), verifyWebhookSignature, (req, res) => {
const { type, data } = req.body;
switch (type) {
case 'user.created':
handleUserCreated(data.object);
break;
case 'user.updated':
handleUserUpdated(data.object);
break;
case 'project.created':
handleProjectCreated(data.object);
break;
default:
console.log(`Unhandled event type: ${type}`);
}
res.status(200).json({ received: true });
});
// Event handler functions
function handleUserCreated(user) {
console.log('New user created:', user.email);
// Send welcome email
sendWelcomeEmail(user.email);
}
function handleUserUpdated(user) {
console.log('User updated:', user.id);
// Sync user data to other systems
syncUserData(user);
}
function handleProjectCreated(project) {
console.log('New project created:', project.name);
// Initialize project resources
initializeProjectResources(project);
}
app.listen(3000, () => {
console.log('Webhook server running on port 3000');
});Python Flask 示例
python
import hashlib
import hmac
import json
from flask import Flask, request, jsonify
app = Flask(__name__)
WEBHOOK_SECRET = 'your-webhook-secret'
def verify_signature(payload, signature):
"""Verify webhook signature"""
expected_signature = hmac.new(
WEBHOOK_SECRET.encode('utf-8'),
payload,
hashlib.sha256
).hexdigest()
return hmac.compare_digest(f'sha256={expected_signature}', signature)
@app.route('/webhook', methods=['POST'])
def webhook():
# Get raw payload and signature
payload = request.get_data()
signature = request.headers.get('X-Webhook-Signature')
# Verify signature
if not verify_signature(payload, signature):
return jsonify({'error': 'Invalid signature'}), 401
# Parse event data
event = request.get_json()
event_type = event.get('type')
data = event.get('data', {})
# Handle different event types
if event_type == 'user.created':
handle_user_created(data.get('object'))
elif event_type == 'user.updated':
handle_user_updated(data.get('object'))
elif event_type == 'project.created':
handle_project_created(data.get('object'))
else:
print(f'Unhandled event type: {event_type}')
return jsonify({'received': True})
def handle_user_created(user):
print(f'New user created: {user.get("email")}')
# Handle user creation logic
def handle_user_updated(user):
print(f'User updated: {user.get("id")}')
# Handle user update logic
def handle_project_created(project):
print(f'New project created: {project.get("name")}')
# Handle project creation logic
if __name__ == '__main__':
app.run(debug=True, port=3000)Security
Signature Verification
All webhook requests include a signature header to verify the authenticity of the request:
javascript
// Generic function to verify signature
function verifyWebhookSignature(payload, signature, secret) {
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(payload, 'utf8')
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature, 'hex'),
Buffer.from(expectedSignature, 'hex')
);
}HTTPS Requirements
- All webhook URLs must use HTTPS
- Self-signed certificates are not supported
- Certificates must be issued by a trusted CA
IP Whitelist
javascript
// Configure allowed IP address ranges
const allowedIPs = [
'192.168.1.0/24',
'10.0.0.0/8',
'172.16.0.0/12'
];
function isIPAllowed(ip) {
// Check if IP is within allowed ranges
return allowedIPs.some(range => ipInRange(ip, range));
}Retry Mechanism
Automatic Retry
- Initial retry: Immediate retry
- Subsequent retries: Exponential backoff (1s, 2s, 4s, 8s, 16s)
- Maximum retry attempts: 5 times
- Retry conditions: HTTP status code >= 500 or network errors
Retry Configuration
javascript
// Configure retry strategy
const retryConfig = {
maxRetries: 5,
initialDelay: 1000, // 1 second
maxDelay: 30000, // 30 seconds
backoffFactor: 2,
retryConditions: [
(response) => response.status >= 500,
(error) => error.code === 'ECONNRESET'
]
};Managing Webhooks
List Webhooks
javascript
// Get all webhooks
const response = await fetch('/api/webhooks', {
headers: {
'Authorization': 'Bearer your-api-token'
}
});
const webhooks = await response.json();Update Webhook
javascript
// Update webhook configuration
const updatedWebhook = {
url: 'https://new-endpoint.com/webhook',
events: ['user.created', 'project.updated'],
active: true
};
const response = await fetch('/api/webhooks/webhook_id', {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer your-api-token'
},
body: JSON.stringify(updatedWebhook)
});Delete Webhook
javascript
// Delete webhook
const response = await fetch('/api/webhooks/webhook_id', {
method: 'DELETE',
headers: {
'Authorization': 'Bearer your-api-token'
}
});Testing Webhooks
Send Test Event
javascript
// Send test webhook
const testEvent = {
type: 'user.created',
data: {
object: {
id: 'test_user_123',
email: 'test@example.com',
name: 'Test User'
}
}
};
const response = await fetch('/api/webhooks/webhook_id/test', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer your-api-token'
},
body: JSON.stringify(testEvent)
});Local Testing Tools
Using ngrok for local testing:
bash
# Install ngrok
npm install -g ngrok
# Start local server
node webhook-server.js
# Start ngrok in another terminal
ngrok http 3000
# Use the ngrok provided URL as webhook URL
# Example: https://abc123.ngrok.io/webhookMonitoring and Logging
Event Logs
javascript
// Get webhook event logs
const response = await fetch('/api/webhooks/webhook_id/events', {
headers: {
'Authorization': 'Bearer your-api-token'
}
});
const events = await response.json();
console.log('Recent webhook events:', events);Performance Monitoring
javascript
// Monitor webhook performance
const stats = await fetch('/api/webhooks/webhook_id/stats', {
headers: {
'Authorization': 'Bearer your-api-token'
}
}).then(res => res.json());
console.log('Webhook statistics:', {
successRate: stats.success_rate,
averageResponseTime: stats.avg_response_time,
totalEvents: stats.total_events,
failedEvents: stats.failed_events
});Troubleshooting
Common Issues
Webhook Not Received
Check URL Accessibility
bashcurl -X POST https://your-app.com/webhook \ -H "Content-Type: application/json" \ -d '{"test": true}'Verify Firewall Settings
- Ensure ports are open
- Check IP whitelist configuration
Check SSL Certificate
bashopenssl s_client -connect your-app.com:443 -servername your-app.com
Signature Verification Failed
javascript
// Debug signature verification
function debugSignatureVerification(payload, receivedSignature, secret) {
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(payload, 'utf8')
.digest('hex');
console.log('Received signature:', receivedSignature);
console.log('Expected signature:', `sha256=${expectedSignature}`);
console.log('Payload:', payload);
console.log('Secret length:', secret.length);
}Too Many Retries
Check Response Status Code
- Ensure 2xx status codes are returned
- Avoid returning 5xx errors
Optimize Response Time
javascript// Asynchronous processing, quick response app.post('/webhook', (req, res) => { // Immediate response res.status(200).json({ received: true }); // Asynchronous event processing setImmediate(() => { processWebhookEvent(req.body); }); });
Debugging Tools
Webhook Debugger
javascript
// Simple webhook debugger
const express = require('express');
const app = express();
app.use(express.json());
app.post('/debug-webhook', (req, res) => {
console.log('=== Webhook Debug Info ===');
console.log('Headers:', req.headers);
console.log('Body:', JSON.stringify(req.body, null, 2));
console.log('Timestamp:', new Date().toISOString());
console.log('========================');
res.status(200).json({
received: true,
timestamp: new Date().toISOString()
});
});
app.listen(3001, () => {
console.log('Webhook debugger running on port 3001');
});Best Practices
Idempotency
javascript
// Implement idempotent processing
const processedEvents = new Set();
app.post('/webhook', (req, res) => {
const eventId = req.body.id;
// Check if event has been processed
if (processedEvents.has(eventId)) {
return res.status(200).json({ received: true, duplicate: true });
}
// Process event
processEvent(req.body);
// Record processed event
processedEvents.add(eventId);
res.status(200).json({ received: true });
});Asynchronous Processing
javascript
// Use queue for asynchronous processing
const Queue = require('bull');
const webhookQueue = new Queue('webhook processing');
app.post('/webhook', (req, res) => {
// Immediate response
res.status(200).json({ received: true });
// Add to queue for asynchronous processing
webhookQueue.add('process-event', req.body, {
attempts: 3,
backoff: 'exponential',
delay: 1000
});
});
// Process events in queue
webhookQueue.process('process-event', async (job) => {
const event = job.data;
await processWebhookEvent(event);
});Error Handling
javascript
// Comprehensive error handling
app.post('/webhook', async (req, res) => {
try {
// Verify signature
if (!verifySignature(req)) {
return res.status(401).json({ error: 'Invalid signature' });
}
// Validate payload
const event = validateEvent(req.body);
if (!event.valid) {
return res.status(400).json({ error: event.error });
}
// Process event
await processEvent(req.body);
res.status(200).json({ received: true });
} catch (error) {
console.error('Webhook processing error:', error);
// Return 5xx error to trigger retry
res.status(500).json({
error: 'Internal server error',
id: generateErrorId()
});
}
});