protocolTechArticle

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.

typescript
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.

typescript
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.

M
MCPserver Team

MCP documentation and protocol implementation team

Published: 2026-07-21
Updated: 2026-07-21

References & Technical Specifications