
Webhook Development
- 456 installs
- 305 repo stars
- Updated March 4, 2026
- aj-geddes/useful-ai-prompts
webhook-development is an AI prompt skill that implements inbound webhook endpoints with verification, idempotency, retries, and clear payloads for developers building event-driven billing, notification, or callback feat
About
webhook-development is a prompt skill from aj-geddes/useful-ai-prompts that walks developers through production-grade inbound webhook endpoints. It addresses signature or HMAC verification, idempotency keys for duplicate delivery, retry-safe handlers, and explicit JSON payload contracts for providers like Stripe, GitHub, or custom SaaS partners. Use webhook-development when adding event-driven features—subscription billing updates, outbound notification fan-out, or third-party sync—where dropped or double-processed events would cause revenue or data integrity bugs. The skill emphasizes handler design and operational edge cases, not just route scaffolding.
- Signature and auth verification
- Idempotent handler patterns
- Retry-safe payload design
- Structured error responses
- Event-driven API endpoints
Webhook Development by the numbers
- 456 all-time installs (skills.sh)
- Ranked #936 of 4,347 Backend & APIs skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/aj-geddes/useful-ai-prompts --skill webhook-developmentAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 456 |
|---|---|
| repo stars | ★ 305 |
| Last updated | March 4, 2026 |
| Repository | aj-geddes/useful-ai-prompts ↗ |
How do you build production webhook endpoints with idempotency?
Implement inbound webhook endpoints with verification, idempotency, retries, and clear payloads when building event-driven features for billing, notifications, or third-party callbacks.
Who is it for?
Backend developers adding Stripe, GitHub, or partner callback endpoints who need verification, idempotency, and retry patterns spelled out before coding.
Skip if: Skip webhook-development when you only need outbound HTTP client calls, GraphQL queries, or a cron job with no external push events.
When should I use this skill?
The user asks to implement webhooks, verify callback signatures, handle idempotent event processing, or design retry-safe inbound endpoints.
What you get
Webhook route handlers, signature verification middleware, idempotency store logic, retry documentation, and payload schema definitions.
- webhook route handlers
- idempotency middleware
- payload schema definitions
Files
Webhook Development
Table of Contents
Overview
Build reliable webhook systems with event delivery, signature verification, retry logic, and dead-letter handling for asynchronous integrations.
When to Use
- Sending real-time notifications to external systems
- Implementing event-driven architectures
- Integrating with third-party platforms
- Building audit trails and logging systems
- Triggering automated workflows
- Delivering payment or order notifications
Quick Start
Minimal working example:
{
"id": "evt_1234567890",
"timestamp": "2025-01-15T10:30:00Z",
"event": "order.created",
"version": "1.0",
"data": {
"orderId": "ORD-123456",
"customerId": "CUST-789",
"amount": 99.99,
"currency": "USD",
"items": [
{
"productId": "PROD-001",
"quantity": 2,
"price": 49.99
}
],
"status": "pending"
},
"attempt": 1,
"retryable": true
}Reference Guides
Detailed implementations in the references/ directory:
| Guide | Contents |
|---|---|
| Webhook Event Schema | Webhook Event Schema |
| Node.js Webhook Service | Node.js Webhook Service |
| Python Webhook Handler | Python Webhook Handler |
| Best Practices | Best Practices, Webhook Events |
Best Practices
✅ DO
- Follow established patterns and conventions
- Write clean, maintainable code
- Add appropriate documentation
- Test thoroughly before deploying
❌ DON'T
- Skip testing or validation
- Ignore error handling
- Hard-code configuration values
Best Practices
Best Practices
✅ DO:
- Sign all webhooks with HMAC
- Implement exponential backoff retries
- Use message queues for reliable delivery
- Track webhook deliveries for debugging
- Provide webhook test endpoints
- Document supported event types
- Use unique event IDs for deduplication
- Set appropriate timeouts (10s)
- Implement dead-letter queues
- Return 2xx quickly, process async
❌ DON'T:
- Send sensitive data without encryption
- Use weak signatures
- Synchronous webhook delivery
- Ignore signature verification
- Expose webhook URLs publicly
- Retry indefinitely
- Log webhook payloads with secrets
- Skip webhook authentication
- Forget to handle idempotency
- Send duplicate eventsWebhook Events
Standard Event Types:
- order.created
- order.updated
- order.cancelled
- payment.succeeded
- payment.failed
- user.registered
- user.updated
- invoice.issued
- shipment.created
- refund.processedNode.js Webhook Service
Node.js Webhook Service
const express = require("express");
const crypto = require("crypto");
const axios = require("axios");
const Bull = require("bull");
const app = express();
app.use(express.json());
const WEBHOOK_SECRET = process.env.WEBHOOK_SECRET;
const webhookQueue = new Bull("webhooks", {
redis: { host: "localhost", port: 6379 },
});
// Register webhook subscription
app.post("/api/webhooks/subscribe", async (req, res) => {
const { url, events, secret } = req.body;
// Validate URL
try {
new URL(url);
} catch {
return res.status(400).json({ error: "Invalid URL" });
}
const webhook = {
id: crypto.randomBytes(16).toString("hex"),
url,
events,
secret: secret || crypto.randomBytes(32).toString("hex"),
active: true,
createdAt: new Date(),
failureCount: 0,
};
// Save to database
await WebhookSubscription.create(webhook);
res.status(201).json({
id: webhook.id,
secret: webhook.secret,
message: "Webhook registered successfully",
});
});
// Send webhook event
const sendWebhookEvent = async (eventType, data) => {
const webhooks = await WebhookSubscription.find({
events: eventType,
active: true,
});
for (const webhook of webhooks) {
const event = {
id: `evt_${Date.now()}`,
timestamp: new Date().toISOString(),
event: eventType,
version: "1.0",
data,
attempt: 1,
retryable: true,
};
// Add to queue
await webhookQueue.add(
{ webhook, event },
{
attempts: 5,
backoff: {
type: "exponential",
delay: 2000,
},
removeOnComplete: true,
},
);
}
};
// Process webhook queue
webhookQueue.process(async (job) => {
const { webhook, event } = job.data;
try {
const signature = generateSignature(event, webhook.secret);
const response = await axios.post(webhook.url, event, {
headers: {
"Content-Type": "application/json",
"X-Webhook-Signature": signature,
"X-Webhook-ID": event.id,
"X-Webhook-Attempt": event.attempt,
},
timeout: 10000,
});
if (response.status >= 200 && response.status < 300) {
// Success
await WebhookDelivery.create({
webhookId: webhook.id,
eventId: event.id,
status: "delivered",
statusCode: response.status,
deliveredAt: new Date(),
});
return;
}
throw new Error(`HTTP ${response.status}`);
} catch (error) {
// Retry or dead letter
if (job.attemptsMade < 5) {
throw error; // Retry
} else {
// Dead letter
await DeadLetterQueue.create({
webhookId: webhook.id,
eventId: event.id,
event,
error: error.message,
failedAt: new Date(),
});
// Update failure count
webhook.failureCount++;
if (webhook.failureCount >= 10) {
webhook.active = false;
}
await webhook.save();
}
}
});
// Webhook endpoint (receiving webhooks)
app.post("/webhooks/:id", async (req, res) => {
const signature = req.headers["x-webhook-signature"];
const webhookId = req.params.id;
const event = req.body;
try {
const webhook = await WebhookSubscription.findOne({ id: webhookId });
if (!webhook) {
return res.status(404).json({ error: "Webhook not found" });
}
// Verify signature
const expectedSignature = generateSignature(event, webhook.secret);
if (signature !== expectedSignature) {
return res.status(401).json({ error: "Invalid signature" });
}
// Process event
console.log("Received webhook event:", event);
res.status(200).json({ received: true });
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// Signature generation
const generateSignature = (payload, secret) => {
const message = JSON.stringify(payload);
return crypto.createHmac("sha256", secret).update(message).digest("hex");
};
// List webhook subscriptions
app.get("/api/webhooks", async (req, res) => {
const webhooks = await WebhookSubscription.find({}, { secret: 0 });
res.json(webhooks);
});
// Test webhook delivery
app.post("/api/webhooks/:id/test", async (req, res) => {
const webhook = await WebhookSubscription.findOne({ id: req.params.id });
const testEvent = {
id: `evt_test_${Date.now()}`,
timestamp: new Date().toISOString(),
event: "webhook.test",
data: { message: "Test event" },
};
await webhookQueue.add({ webhook, event: testEvent });
res.json({ message: "Test event queued" });
});
// Retry failed deliveries
app.post("/api/webhooks/deliveries/:id/retry", async (req, res) => {
const delivery = await WebhookDelivery.findOne({ _id: req.params.id });
if (!delivery) {
return res.status(404).json({ error: "Delivery not found" });
}
const webhook = await WebhookSubscription.findOne({ id: delivery.webhookId });
const event = await Event.findOne({ id: delivery.eventId });
await webhookQueue.add({ webhook, event });
res.json({ message: "Retry queued" });
});
// List webhook deliveries
app.get("/api/webhooks/:id/deliveries", async (req, res) => {
const deliveries = await WebhookDelivery.find({
webhookId: req.params.id,
}).limit(100);
res.json(deliveries);
});
// Event trigger examples
app.post("/api/orders", async (req, res) => {
const order = await Order.create(req.body);
// Send webhook event
await sendWebhookEvent("order.created", {
orderId: order.id,
customerId: order.customerId,
amount: order.amount,
status: order.status,
});
res.status(201).json(order);
});
app.listen(3000, () => console.log("Server on port 3000"));Python Webhook Handler
Python Webhook Handler
from flask import Flask, request, jsonify
from datetime import datetime, timedelta
import hmac
import hashlib
import requests
import json
from celery import Celery
from sqlalchemy import Column, String, Boolean, DateTime, Integer
app = Flask(__name__)
celery = Celery(app.name, broker='redis://localhost:6379')
class WebhookSubscription:
id = Column(String(100), primary_key=True)
url = Column(String(500))
events = Column(String(500))
secret = Column(String(256))
active = Column(Boolean, default=True)
failure_count = Column(Integer, default=0)
created_at = Column(DateTime, default=datetime.utcnow)
def generate_signature(payload, secret):
message = json.dumps(payload, sort_keys=True)
return hmac.new(
secret.encode(),
message.encode(),
hashlib.sha256
).hexdigest()
@app.route('/api/webhooks/subscribe', methods=['POST'])
def subscribe_webhook():
data = request.get_json()
url = data.get('url')
events = data.get('events', [])
secret = data.get('secret', os.urandom(32).hex())
webhook = WebhookSubscription(
id=f"wh_{secrets.token_hex(8)}",
url=url,
events=','.join(events),
secret=secret,
active=True
)
db.session.add(webhook)
db.session.commit()
return jsonify({
'id': webhook.id,
'secret': webhook.secret,
'message': 'Webhook registered'
}), 201
@celery.task(bind=True, max_retries=5)
def deliver_webhook(self, webhook_id, event):
webhook = WebhookSubscription.query.get(webhook_id)
if not webhook:
return
signature = generate_signature(event, webhook.secret)
try:
response = requests.post(
webhook.url,
json=event,
headers={
'Content-Type': 'application/json',
'X-Webhook-Signature': signature,
'X-Webhook-ID': event['id'],
'X-Webhook-Attempt': str(event.get('attempt', 1))
},
timeout=10
)
if 200 <= response.status_code < 300:
WebhookDelivery.create(
webhook_id=webhook_id,
event_id=event['id'],
status='delivered',
status_code=response.status_code
)
return
raise Exception(f"HTTP {response.status_code}")
except Exception as exc:
retry_delay = 2 ** self.request.retries
raise self.retry(exc=exc, countdown=retry_delay)
@app.route('/webhooks/<webhook_id>', methods=['POST'])
def receive_webhook(webhook_id):
signature = request.headers.get('X-Webhook-Signature')
event = request.get_json()
webhook = WebhookSubscription.query.get(webhook_id)
if not webhook:
return jsonify({'error': 'Not found'}), 404
expected_signature = generate_signature(event, webhook.secret)
if signature != expected_signature:
return jsonify({'error': 'Invalid signature'}), 401
return jsonify({'received': True}), 200
@app.route('/api/orders', methods=['POST'])
def create_order():
order = Order.create(request.get_json())
# Queue webhook delivery
event = {
'id': f"evt_{datetime.utcnow().timestamp()}",
'timestamp': datetime.utcnow().isoformat(),
'event': 'order.created',
'data': order.to_dict()
}
webhooks = WebhookSubscription.query.filter(
WebhookSubscription.events.contains('order.created'),
WebhookSubscription.active == True
).all()
for webhook in webhooks:
deliver_webhook.delay(webhook.id, event)
return jsonify(order.to_dict()), 201
if __name__ == '__main__':
app.run(debug=False, port=3000)Webhook Event Schema
Webhook Event Schema
{
"id": "evt_1234567890",
"timestamp": "2025-01-15T10:30:00Z",
"event": "order.created",
"version": "1.0",
"data": {
"orderId": "ORD-123456",
"customerId": "CUST-789",
"amount": 99.99,
"currency": "USD",
"items": [
{
"productId": "PROD-001",
"quantity": 2,
"price": 49.99
}
],
"status": "pending"
},
"attempt": 1,
"retryable": true
}#!/bin/bash
# validate-schema.sh - Validate database schema
# Usage: ./validate-schema.sh <schema_file>
set -euo pipefail
SCHEMA_FILE="${{1:?Usage: $0 <schema_file>}}"
echo "Validating schema: $SCHEMA_FILE"
# TODO: Add schema validation
# - Check SQL syntax
# - Verify foreign key references
# - Check index definitions
# - Validate naming conventions
# - Check for missing constraints
echo "Schema validation complete."
-- Migration: [description]
-- Created: [date]
-- TODO: Customize for your migration framework
BEGIN;
-- Up migration
-- TODO: Add schema changes
-- CREATE TABLE IF NOT EXISTS ...
-- ALTER TABLE ...
-- Down migration (rollback)
-- TODO: Add rollback statements
-- DROP TABLE IF EXISTS ...
COMMIT;
Related skills
FAQ
What does webhook-development cover?
webhook-development guides inbound webhook endpoint implementation with signature verification, idempotency keys, retry semantics, and explicit payload schemas. The skill targets billing, notification, and third-party callback integrations where duplicate or unverified events cau
When should you use webhook-development?
webhook-development fits backend developers adding push-based integrations from Stripe, GitHub, or partner SaaS products. Use it when events must be verified, processed exactly once, and retried safely—not for outbound-only HTTP clients or scheduled cron jobs.