February 18, 2025 32174b4 Edit this page

Working with Webhooks 🗄️ Archived

Set up and handle webhooks for real-time event notifications

Webhooks allow your application to receive real-time notifications when events occur in our system.

What are Webhooks?

Webhooks are HTTP callbacks that deliver event data to your application when specific events occur. Instead of polling for changes, webhooks push notifications to your server immediately.

Setting Up Webhooks

1. Create a Webhook Endpoint

Your endpoint should:

  • Accept POST requests
  • Respond quickly (within 5 seconds)
  • Return a 2xx status code
  • Verify the webhook signature

2. Register Your Webhook

curl -X POST https://api.example.com/v1/webhooks \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://your-app.com/webhooks",
    "events": [
      "resource.created",
      "resource.updated",
      "resource.deleted"
    ],
    "secret": "your_webhook_secret"
  }'

Available Events

EventDescription
resource.createdNew resource created
resource.updatedResource updated
resource.deletedResource deleted
user.createdNew user registered
user.updatedUser profile updated
payment.succeededPayment completed
payment.failedPayment failed

Webhook Payload

All webhooks include:

{
  "id": "evt_abc123",
  "type": "resource.created",
  "created_at": "2025-02-18T10:30:00Z",
  "data": {
    "object": {
      "id": "res_xyz789",
      "name": "New Resource",
      "status": "active"
    }
  },
  "previous_data": null
}

For update events, previous_data contains the old values:

{
  "id": "evt_def456",
  "type": "resource.updated",
  "created_at": "2025-02-18T10:35:00Z",
  "data": {
    "object": {
      "id": "res_xyz789",
      "name": "Updated Resource",
      "status": "active"
    }
  },
  "previous_data": {
    "name": "Old Resource Name"
  }
}

Verifying Webhook Signatures

Always verify that webhooks come from our service:

Python

import hmac
import hashlib

def verify_webhook_signature(payload, signature, secret):
    """Verify the webhook signature"""
    expected_signature = hmac.new(
        secret.encode('utf-8'),
        payload.encode('utf-8'),
        hashlib.sha256
    ).hexdigest()
    
    return hmac.compare_digest(signature, expected_signature)

# In your webhook handler
from flask import Flask, request, jsonify

app = Flask(__name__)

@app.route('/webhooks', methods=['POST'])
def handle_webhook():
    payload = request.get_data(as_text=True)
    signature = request.headers.get('X-Webhook-Signature')
    
    if not verify_webhook_signature(payload, signature, WEBHOOK_SECRET):
        return jsonify({'error': 'Invalid signature'}), 401
    
    event = request.json
    
    # Handle the event
    if event['type'] == 'resource.created':
        handle_resource_created(event['data']['object'])
    elif event['type'] == 'resource.updated':
        handle_resource_updated(event['data']['object'])
    
    return jsonify({'status': 'received'}), 200

Node.js

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

const app = express();
app.use(express.raw({ type: 'application/json' }));

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

app.post('/webhooks', (req, res) => {
  const signature = req.headers['x-webhook-signature'];
  const payload = req.body.toString();
  
  if (!verifyWebhookSignature(payload, signature, WEBHOOK_SECRET)) {
    return res.status(401).json({ error: 'Invalid signature' });
  }
  
  const event = JSON.parse(payload);
  
  // Handle the event
  switch (event.type) {
    case 'resource.created':
      handleResourceCreated(event.data.object);
      break;
    case 'resource.updated':
      handleResourceUpdated(event.data.object);
      break;
  }
  
  res.json({ status: 'received' });
});

Handling Webhook Events

Asynchronous Processing

Process webhooks asynchronously to respond quickly:

from celery import Celery

app = Celery('tasks', broker='redis://localhost:6379')

@app.task
def process_webhook_event(event):
    """Process webhook event asynchronously"""
    if event['type'] == 'resource.created':
        # Do time-consuming work here
        send_notification(event['data']['object'])
        update_analytics(event['data']['object'])

@app.route('/webhooks', methods=['POST'])
def handle_webhook():
    # Verify signature...
    
    event = request.json
    
    # Queue for async processing
    process_webhook_event.delay(event)
    
    # Respond immediately
    return jsonify({'status': 'received'}), 200

Idempotency

Handle duplicate webhook deliveries:

from redis import Redis

redis_client = Redis()

def process_webhook_with_idempotency(event):
    event_id = event['id']
    
    # Check if we've already processed this event
    if redis_client.exists(f'webhook:{event_id}'):
        return  # Already processed
    
    # Process the event
    handle_event(event)
    
    # Mark as processed (expire after 24 hours)
    redis_client.setex(f'webhook:{event_id}', 86400, '1')

Testing Webhooks

Local Development with ngrok

# Install ngrok
brew install ngrok  # macOS

# Start your local server
python app.py

# Expose your local server
ngrok http 5000

Use the ngrok URL when registering your webhook.

Manual Testing

Trigger a test webhook:

curl -X POST https://api.example.com/v1/webhooks/test \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "webhook_id": "wh_abc123",
    "event_type": "resource.created"
  }'

Webhook Retry Logic

If your endpoint fails, we’ll retry:

  • Immediately
  • After 1 minute
  • After 5 minutes
  • After 30 minutes
  • After 2 hours

After 5 failed attempts, the webhook is disabled.

Managing Webhooks

List All Webhooks

curl https://api.example.com/v1/webhooks \
  -H "Authorization: Bearer YOUR_TOKEN"

Update a Webhook

curl -X PATCH https://api.example.com/v1/webhooks/wh_abc123 \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "events": ["resource.created", "resource.updated"]
  }'

Delete a Webhook

curl -X DELETE https://api.example.com/v1/webhooks/wh_abc123 \
  -H "Authorization: Bearer YOUR_TOKEN"

Best Practices

  1. Always verify signatures to ensure authenticity
  2. Respond quickly (within 5 seconds)
  3. Process asynchronously for time-consuming tasks
  4. Handle idempotency to avoid duplicate processing
  5. Log webhook events for debugging
  6. Monitor webhook health and failures
  7. Use HTTPS endpoints for security
  8. Implement rate limiting to prevent abuse