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