Overview
Webhook endpoints are publicly reachable HTTP endpoints — which means attackers can probe them. A poorly secured webhook endpoint can be exploited to:
- Inject forged email payloads that trigger unintended actions in your application
- Execute denial-of-service attacks by flooding the endpoint with requests
- Exploit processing bugs by crafting malicious payload content
- Leak information through error responses that reveal application internals
A complete webhook security posture uses multiple layers:
- HMAC-SHA256 signature verification — authenticate that requests come from JsonHook
- TLS enforcement — encrypt traffic in transit
- Rate limiting — prevent request flooding
- Input validation — reject malformed payloads early
- Secrets management — protect webhook secrets from leakage
- Minimal response bodies — do not leak internal details in error responses
Prerequisites
Security prerequisites:
- A valid TLS certificate from a recognized CA for your webhook endpoint domain
- A rate limiting layer (application-level middleware, API gateway, or reverse proxy like nginx)
- A secrets management solution (AWS Secrets Manager, HashiCorp Vault, environment variables with restricted access)
- Input validation library for your application framework
Secure Your Email Webhook Pipeline
HMAC-SHA256 on every delivery. Your endpoint, locked down tight.
Get Free API KeyStep-by-Step Instructions
Implement each security layer:
- Enforce HMAC-SHA256 verification as the first middleware step — reject before any processing.
- Enforce HTTPS only:
app.use((req, res, next) => { if (req.protocol !== "https") return res.status(301).redirect(`https://${req.hostname}${req.url}`); next(); }); - Add rate limiting using express-rate-limit or equivalent:
import rateLimit from "express-rate-limit"; const webhookLimiter = rateLimit({ windowMs: 60_000, max: 200 }); app.use("/webhooks", webhookLimiter); - Validate payload structure after signature verification — check required fields are present and have expected types.
- Return minimal error responses — never include stack traces or internal details in 4xx/5xx responses from webhook endpoints.
- Rotate secrets regularly — use the JsonHook secret rotation API and update your environment variables promptly.
Code Example
Complete secure webhook middleware stack:
import express from "express";
import crypto from "crypto";
import rateLimit from "express-rate-limit";
const app = express();
// 1. Rate limiting — before signature verification to limit cost
const webhookLimiter = rateLimit({
windowMs: 60_000,
max: 500,
standardHeaders: true,
legacyHeaders: false,
message: "Too many requests",
});
// 2. Raw body parsing for HMAC
app.use("/webhooks", express.raw({ type: "application/json", limit: "10mb" }));
app.use("/webhooks", webhookLimiter);
// 3. Signature verification middleware
function requireJsonHookSignature(req: express.Request, res: express.Response, next: express.NextFunction) {
const sig = req.headers["x-jsonhook-signature"];
if (!sig || typeof sig !== "string") return res.sendStatus(401);
const expected = crypto
.createHmac("sha256", process.env.JSONHOOK_SECRET!)
.update(req.body)
.digest("hex");
if (!crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected))) {
return res.sendStatus(401); // No detail in response body
}
next();
}
// 4. Payload schema validation
function validatePayload(req: express.Request, res: express.Response, next: express.NextFunction) {
try {
const payload = JSON.parse(req.body.toString());
if (!payload.email?.from || !payload.deliveryId) return res.sendStatus(400);
req.body = payload; // replace buffer with parsed object
next();
} catch {
res.sendStatus(400);
}
}
app.post("/webhooks/email",
requireJsonHookSignature,
validatePayload,
async (req: any, res) => {
// Safe to process req.body as a validated, authenticated payload
res.sendStatus(200);
}
);Common Pitfalls
Security mistakes to avoid:
- Secrets in environment variables accessible to all processes. Use per-service secrets — the JsonHook webhook secret should only be accessible to the process that runs the webhook handler, not to every service in your application.
- No rate limiting on unauthenticated requests. Rate limiting should apply before signature verification so attackers cannot exhaust your server resources by sending many unsigned requests that get processed before rejection.
- Verbose error responses. Returning
{"error": "HMAC mismatch: expected abc123 got xyz789"}leaks your expected hash. Return only status codes for security-related errors. - Not renewing TLS certificates. An expired certificate causes all webhook deliveries to fail and triggers a JsonHook alert. Automate certificate renewal (Let's Encrypt + certbot) or use a managed TLS service.
- Assuming internal-only endpoints are safe. Even if your webhook endpoint is behind a VPN or firewall, implement HMAC verification. Defense in depth means you do not rely solely on network-level controls.