Agent skill
webhook-receiver-hardener
Secures webhook receivers with signature verification, retry handling, deduplication, idempotency keys, and error responses. Provides verification code, dedupe storage strategy, runbook for incidents. Use when implementing "webhooks", "webhook security", "event receivers", or "third-party integrations".
Install this agent skill to your Project
npx add-skill https://github.com/patricio0312rev/skills/tree/main/backend/webhook-receiver-hardener
SKILL.md
Webhook Receiver Hardener
Build secure, reliable webhook endpoints that handle failures gracefully.
Core Security
Signature Verification: HMAC validation before processing Deduplication: Track processed webhook IDs Idempotency: Safe to process same webhook multiple times Retries: Handle provider retry attempts Rate Limiting: Prevent abuse
Signature Verification
import crypto from "crypto";
export const verifyWebhookSignature = (
payload: string,
signature: string,
secret: string
): boolean => {
const hmac = crypto
.createHmac("sha256", secret)
.update(payload)
.digest("hex");
return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(hmac));
};
// Stripe example
router.post(
"/webhooks/stripe",
express.raw({ type: "application/json" }),
async (req, res) => {
const sig = req.headers["stripe-signature"];
try {
const event = stripe.webhooks.constructEvent(
req.body,
sig,
process.env.STRIPE_WEBHOOK_SECRET
);
await processStripeEvent(event);
res.json({ received: true });
} catch (err) {
return res.status(400).send(`Webhook Error: ${err.message}`);
}
}
);
Deduplication
// Redis-based dedupe
const WEBHOOK_TTL = 60 * 60 * 24; // 24 hours
export const isDuplicate = async (webhookId: string): Promise<boolean> => {
const key = `webhook:${webhookId}`;
const exists = await redis.exists(key);
if (exists) return true;
await redis.setex(key, WEBHOOK_TTL, "1");
return false;
};
// Usage
if (await isDuplicate(webhook.id)) {
return res.status(200).json({ received: true }); // Already processed
}
Idempotent Processing
export const processWebhook = async (webhook: Webhook) => {
// Use database transaction with unique constraint
try {
await db.transaction(async (trx) => {
// Insert webhook record (unique constraint on webhook_id)
await trx("processed_webhooks").insert({
webhook_id: webhook.id,
processed_at: new Date(),
});
// Do actual processing
await performWebhookAction(webhook, trx);
});
} catch (err) {
if (err.code === "23505") {
// Unique violation
console.log("Webhook already processed");
return; // Idempotent - already processed
}
throw err;
}
};
Retry Handling
// Acknowledge immediately, process async
router.post("/webhooks/provider", async (req, res) => {
// Verify signature
if (!verifySignature(req.body, req.headers["signature"])) {
return res.status(401).send("Invalid signature");
}
// Return 200 immediately
res.status(200).json({ received: true });
// Process async
processWebhookAsync(req.body).catch((err) => {
console.error("Webhook processing failed:", err);
// Will be retried by provider
});
});
// Exponential backoff for provider retries
// Attempt 1: immediate
// Attempt 2: +5 minutes
// Attempt 3: +15 minutes
// Attempt 4: +1 hour
// Attempt 5: +6 hours
Error Responses
// Return appropriate status codes
const webhookHandler = async (req, res) => {
// 400: Malformed payload (won't retry)
if (!isValidPayload(req.body)) {
return res.status(400).json({ error: "Invalid payload" });
}
// 401: Invalid signature (won't retry)
if (!verifySignature(req.body, req.headers["signature"])) {
return res.status(401).json({ error: "Invalid signature" });
}
// 200: Already processed (idempotent)
if (await isDuplicate(req.body.id)) {
return res.status(200).json({ received: true });
}
// 500: Processing error (will retry)
try {
await processWebhook(req.body);
return res.status(200).json({ received: true });
} catch (err) {
console.error("Processing error:", err);
return res.status(500).json({ error: "Processing failed" });
}
};
Monitoring & Runbook
## Webhook Incidents
### High Error Rate
1. Check provider status page
2. Review recent code deploys
3. Check signature secret rotation
4. Verify database connectivity
### Missing Webhooks
1. Check provider sending (their dashboard)
2. Verify endpoint is accessible
3. Check rate limiting rules
4. Review dedupe cache TTL
### Duplicate Processing
1. Check dedupe cache connectivity
2. Verify unique constraints
3. Review idempotency logic
Best Practices
- Verify signature BEFORE any processing
- Return 200 quickly, process async
- Dedupe with Redis + database constraints
- Log all webhook attempts
- Monitor processing latency
- Set up alerts for failures
- Document expected payload schemas
Output Checklist
- Signature verification
- Deduplication mechanism
- Idempotent processing
- Async processing pattern
- Proper status codes
- Error logging
- Monitoring/alerts
- Incident runbook
Recommended Agent Skills
Expand your agent's capabilities with these related and highly-rated skills.
rate-limiting-abuse-protection
Implements rate limiting and abuse prevention with per-route policies, IP/user-based limits, sliding windows, safe error responses, and observability. Use when adding "rate limiting", "API protection", "abuse prevention", or "DDoS protection".
rbac-permissions-builder
Implements role-based access control with permission matrix, route guards, policy functions, and UI permission hints. Provides middleware/guards, helper utilities, test suggestions, and permission checking patterns. Use when building "RBAC", "permissions", "access control", or "authorization".
websocket-realtime-builder
Implements real-time features using WebSockets with Socket.io, rooms, authentication, and reconnection handling. Use when users request "real-time updates", "WebSocket", "Socket.io", "live chat", or "push notifications".
auth-module-builder
Implements secure authentication patterns including login/registration, session management, JWT tokens, password hashing, cookie settings, and CSRF protection. Provides auth routes, middleware, security configurations, and threat model documentation. Use when building "authentication", "login system", "JWT auth", or "session management".
rest-to-graphql-migrator
Migrates REST APIs to GraphQL incrementally with schema stitching, REST datasources, and gradual endpoint migration. Use when users request "migrate to GraphQL", "REST to GraphQL", "GraphQL wrapper", or "API modernization".
queue-job-processor
Implements background job processing with BullMQ/Redis including job queues, workers, scheduling, retries, and monitoring. Use when users request "background jobs", "queue processing", "async tasks", "BullMQ", or "job scheduler".
Didn't find tool you were looking for?