MCP Webhook Integration Guide
React to external events in real time by pairing MCP servers with signed incoming webhooks and a retrying outgoing webhook sender.
Quick Answer / TL;DR
Webhooks turn an MCP server from purely request-response into event-driven: an incoming webhook lets an external service notify the server the moment something changes, while an outgoing webhook lets the server notify other systems after a tool executes. Always verify incoming webhook signatures and retry outgoing deliveries with backoff.
Key Takeaways
- Verify every incoming webhook's signature with a timing-safe comparison before trusting its payload.
- Queue outgoing webhook deliveries and retry with backoff instead of sending them inline and dropping failures.
- Treat webhook payloads as untrusted input and validate them the same way you validate tool arguments.
Incoming webhooks
Verify the provider's HMAC signature with a timing-safe comparison before processing the payload, so a forged request cannot trigger downstream MCP notifications.
function verifySignature(payload: string, signature: string, secret: string): boolean {
const expected = crypto.createHmac("sha256", secret).update(payload).digest("hex");
return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(`sha256=${expected}`));
}
app.post("/webhook/github", async (req, res) => {
const signature = req.headers["x-hub-signature-256"] as string;
if (!verifySignature(JSON.stringify(req.body), signature, process.env.GITHUB_WEBHOOK_SECRET!)) {
return res.status(401).json({ error: "Invalid signature" });
}
if (req.body.action === "push") {
await notifyClients("github_push", { repo: req.body.repository.full_name });
}
res.status(200).json({ received: true });
});Outgoing webhooks
Queue deliveries rather than sending inline during a tool call, and retry failed deliveries with backoff so a slow or momentarily-down subscriber does not slow down or fail the original tool response.
class WebhookSender {
private queue: Array<{ url: string; payload: unknown }> = [];
private sending = false;
send(url: string, event: string, data: unknown) {
this.queue.push({ url, payload: { event, data, timestamp: new Date().toISOString() } });
if (!this.sending) this.drain();
}
private async drain() {
this.sending = true;
while (this.queue.length) {
const { url, payload } = this.queue.shift()!;
try {
await fetch(url, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(payload) });
} catch {
this.queue.push({ url, payload });
await new Promise((r) => setTimeout(r, 5000));
}
}
this.sending = false;
}
}MCP Webhook Integration Guide FAQs
Direct answers for developers, operators, and Indian teams evaluating MCP.