Webhook Flow
title: "Webhook Flow" description: "Understand the complete webhook flow from event trigger to delivery, including registration, verification, delivery, acknowledgment, and retry." weight: 13 date: 2026-06-28 lastmod: 2026-06-28 tags: ["apis", "webhooks"]
The webhook flow describes the complete lifecycle of a webhook message, from event registration on the consumer side to successful delivery and acknowledgment.
## What You'll Learn
- Webhook registration process
- URL verification (challenge-response)
- Event triggering and payload creation
- Delivery and acknowledgment
- Retry and failure handling
## Why It Matters
Understanding the complete webhook flow helps you build reliable integrations. Each step has specific requirements and potential failure modes.
## Real-World Use
A payment processor's webhook flow includes: merchant registers webhook URL, processor sends verification challenge, merchant responds with challenge hash, and all subsequent payment events trigger signed webhook deliveries with automatic retries.
## Flow Chart
```mermaid
sequenceDiagram
participant C as Consumer
participant P as Provider
C->>P: Register webhook URL
P->>C: Verification challenge
C->>P: Challenge response
Note over C,P: Webhook verified
Note over P: Event occurs
P->>P: Create payload
P->>P: Sign payload
P->>C: POST /webhook
C->>C: Verify signature
C->>C: Process event
C->>P: 200 OK
Note over P: Delivery complete
Code Examples
Example 1: Webhook Registration with Verification
// Provider: webhook registration with challenge
app.post('/api/webhooks', async (req, res) => {
const { url, events } = req.body;
// Generate verification challenge
const challenge = crypto.randomBytes(32).toString('hex');
const webhookId = generateId();
// Send verification request to consumer
try {
const verificationResult = await axios.post(url, {
type: 'verification',
webhookId,
challenge,
}, {
headers: { 'Content-Type': 'application/json' },
timeout: 10000,
});
// Verify consumer responded with correct challenge
if (verificationResult.data.challenge === challenge) {
// Webhook verified successfully
saveWebhook(webhookId, url, events);
res.status(201).json({
id: webhookId,
status: 'verified',
secret: generateSecret(),
});
} else {
res.status(400).json({
error: 'Verification failed: challenge mismatch',
});
}
} catch (error) {
res.status(400).json({
error: 'Verification failed: cannot reach consumer URL',
});
}
});
// Consumer: handle verification
app.post('/webhooks/verify', (req, res) => {
const { type, webhookId, challenge } = req.body;
if (type === 'verification') {
// Respond with the challenge to verify
res.json({ challenge });
}
});
Expected output: Provider sends verification challenge to consumer URL, consumer responds with the challenge, and webhook is registered as verified.
Example 2: Complete Webhook Delivery
async function deliverWebhook(webhook, event) {
const payload = {
id: generateEventId(),
type: event.type,
created_at: new Date().toISOString(),
data: event.data,
};
const payloadStr = JSON.stringify(payload);
const signature = crypto
.createHmac('sha256', webhook.secret)
.update(payloadStr)
.digest('hex');
const delivery = {
webhookId: webhook.id,
eventId: payload.id,
url: webhook.url,
payload: payloadStr,
signature,
attempt: 1,
maxAttempts: 5,
status: 'pending',
};
for (let attempt = 1; attempt <= delivery.maxAttempts; attempt++) {
delivery.attempt = attempt;
try {
const response = await axios.post(webhook.url, payload, {
headers: {
'Content-Type': 'application/json',
'X-Webhook-ID': webhook.id,
'X-Event-ID': payload.id,
'X-Signature-256': signature,
'X-Delivery-Attempt': attempt,
},
timeout: 15000,
});
if (response.status >= 200 && response.status < 300) {
delivery.status = 'delivered';
logDelivery(delivery);
return { success: true, delivery };
}
} catch (error) {
delivery.lastError = error.message;
delivery.status = 'failed';
logDelivery(delivery);
if (attempt < delivery.maxAttempts) {
// Exponential backoff
await sleep(Math.min(1000 * Math.pow(2, attempt - 1), 60000));
}
}
}
delivery.status = 'permanently_failed';
logDelivery(delivery);
return { success: false, delivery };
}
function logDelivery(delivery) {
console.log(
`Delivery ${delivery.eventId} to ${delivery.url}: ` +
`${delivery.status} (attempt ${delivery.attempt})`
);
}
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
Expected output: Full webhook delivery with signature, retry logic with exponential backoff, and delivery logging.
Example 3: Consumer Webhook Processing Pipeline
from flask import Flask, request, jsonify
import hashlib
import hmac
import threading
from queue import Queue
app = Flask(__name__)
webhook_queue = Queue()
@app.route('/webhooks/handler', methods=['POST'])
def handle_webhook():
# Verify signature
signature = request.headers.get('X-Signature-256')
payload = request.get_data()
if not verify_signature(payload, signature):
return jsonify({"error": "Invalid signature"}), 401
# Acknowledge immediately
event_id = request.headers.get('X-Event-ID')
# Queue for async processing
webhook_queue.put({
'event_id': event_id,
'type': request.json.get('type'),
'data': request.json.get('data'),
'headers': dict(request.headers),
})
return jsonify({"status": "accepted", "event_id": event_id}), 200
def verify_signature(payload, signature):
if not signature:
return False
expected = hmac.new(
WEBHOOK_SECRET.encode(),
payload,
hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, signature)
def webhook_worker():
while True:
webhook = webhook_queue.get()
try:
process_event(webhook['type'], webhook['data'])
except Exception as e:
log_error(f"Failed to process {webhook['event_id']}: {e}")
finally:
webhook_queue.task_done()
# Start worker thread
threading.Thread(target=webhook_worker, daemon=True).start()
if __name__ == '__main__':
app.run(port=8080)
Expected output: Consumer validates signature, acknowledges immediately, and processes the webhook asynchronously.
Common Mistakes
| Mistake | Explanation |
|---|---|
| Processing synchronously | Long processing delays acknowledgment and may cause provider timeouts |
| Skipping URL verification | Without verification, webhooks may be sent to incorrect or malicious URLs |
| Not tracking delivery attempts | Without logging, you cannot debug delivery failures or measure reliability |
| Ignoring webhook IDempotency | Duplicate deliveries cause data corruption without idempotent handling |
| Not having a backup consumer | Single consumer URLs are a single point of failure for critical webhooks |
Practice Questions
- What is the purpose of webhook URL verification?
- How does the provider handle delivery failures?
- Why should consumers acknowledge webhooks immediately?
- What information should be in webhook headers?
- How do you handle duplicate webhook deliveries?
Challenge
Build a complete webhook system with registration, URL verification (challenge-response), signed delivery with retry logic, and a consumer pipeline that acknowledges, validates, and processes events asynchronously.
FAQ
Mini Project
Build a webhook flow simulator that demonstrates the complete lifecycle: registration, verification, event triggering, signed delivery, acknowledgment, retry on failure, and delivery logging with a web dashboard.
What's Next
Learn about webhook payload formats
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro