Skip to content

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 creation
  • user.updated - User information update
  • user.deleted - User deletion
  • user.login - User login
  • user.logout - User logout

Project Events

  • project.created - Project creation
  • project.updated - Project update
  • project.deleted - Project deletion
  • project.shared - Project sharing

File Events

  • file.uploaded - File upload
  • file.downloaded - File download
  • file.deleted - File deletion
  • file.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/webhook

Monitoring 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

  1. Check URL Accessibility

    bash
    curl -X POST https://your-app.com/webhook \
      -H "Content-Type: application/json" \
      -d '{"test": true}'
  2. Verify Firewall Settings

    • Ensure ports are open
    • Check IP whitelist configuration
  3. Check SSL Certificate

    bash
    openssl 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

  1. Check Response Status Code

    • Ensure 2xx status codes are returned
    • Avoid returning 5xx errors
  2. 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()
    });
  }
});

Your Ultimate AI-Powered IDE Learning Guide