MP-301e · Module 1

Webhook Bridges

3 min read

Most SaaS platforms do not offer event streams — they offer webhooks. A webhook bridge converts incoming HTTP POST callbacks from external services into MCP subscription notifications. When Jira fires a webhook for an issue update, Salesforce notifies on an opportunity change, or Stripe signals a payment event, the bridge receives the HTTP callback, maps it to the affected MCP resource URI, and triggers a notification to subscribed clients.

Building a reliable webhook bridge requires handling three challenges: verification, mapping, and resilience. Verification ensures incoming webhooks are authentic — check HMAC signatures, validate source IPs, or verify webhook secrets. Mapping translates the webhook payload to MCP resource URIs — a Jira webhook for issue PROJECT-123 maps to jira://PROJECT-123. Resilience handles webhook delivery failures — the bridge must be idempotent (processing the same webhook twice produces no duplicate notifications) and must handle out-of-order delivery gracefully.

import express from "express";
import crypto from "crypto";

const app = express();

// Webhook receiver with HMAC verification
app.post("/webhooks/jira", express.json(), (req, res) => {
  // Step 1: Verify webhook signature
  const signature = req.headers["x-hub-signature-256"] as string;
  const expected = crypto
    .createHmac("sha256", process.env.JIRA_WEBHOOK_SECRET!)
    .update(JSON.stringify(req.body))
    .digest("hex");
  if (signature !== `sha256=${expected}`) {
    return res.status(401).send("Invalid signature");
  }

  // Step 2: Map webhook to MCP resource URIs
  const { issue, webhookEvent } = req.body;
  const affectedUris: string[] = [];

  if (issue?.key) {
    affectedUris.push(`jira://${issue.key}`);
    affectedUris.push(`jira://project/${issue.fields?.project?.key}`);
  }

  // Step 3: Idempotency check — deduplicate by webhook ID
  const webhookId = req.headers["x-atlassian-webhook-identifier"];
  if (webhookId && processedWebhooks.has(webhookId as string)) {
    return res.status(200).send("Already processed");
  }
  processedWebhooks.add(webhookId as string);

  // Step 4: Trigger MCP notifications
  for (const uri of affectedUris) {
    subscriptionManager.onChange(uri);
  }

  res.status(200).send("OK");
});

// Sliding window deduplication
const processedWebhooks = new Set<string>();
setInterval(() => processedWebhooks.clear(), 3600_000); // Clear hourly
  1. Register webhooks with external services Configure each SaaS platform to POST to your bridge endpoint. Use HTTPS, set webhook secrets, and subscribe only to the event types you need — not every event.
  2. Implement signature verification Verify HMAC signatures, API keys, or source IP ranges for every incoming webhook. Reject unverified payloads immediately — webhook endpoints are public attack surfaces.
  3. Map payloads to URIs Build a mapping function for each service that extracts the affected resource URIs from the webhook payload. Test with real webhook samples — the payload structure often differs from documentation.