Webhooks

Webhooks let you receive real-time HTTP notifications when events happen in your MCP Gateway workspace. Instead of polling the API, MCP Gateway pushes event data to your server as it occurs.

How it works

  1. Create a webhook endpoint -- register a URL and select which events you want to receive
  2. Receive events -- MCP Gateway sends a POST request to your URL with event data
  3. Verify the signature -- validate the HMAC signature to ensure the payload is authentic
  4. Return 2xx -- respond with a 2xx status code to acknowledge receipt

Webhooks require the webhooks entitlement on your plan, and the number of endpoints per workspace is capped by your plan's webhook endpoint limit.

Creating a webhook endpoint

In the app (workspace owners only):

  1. Click Webhooks in the sidebar under the Integrate section
  2. Click Add Endpoint
  3. Enter the HTTPS URL, select the events you want to receive, and save

You can also manage endpoints programmatically via the REST API (/api/v1/webhook_endpoints, webhooks:manage scope) or the MCP server tools.

About the signing secret: every endpoint gets an HMAC signing secret, generated at creation. When you create an endpoint via the API or MCP, the secret is included in the create response only — it is never returned by list or get. In the web UI, workspace owners can reveal and copy the secret from the endpoint's row on the Webhooks page.

Event types

Event Trigger
member.invited An invitation to join the workspace is sent
member.joined Someone accepts an invitation and becomes a member
member.removed A member is removed from the workspace
subscription.updated The workspace's subscription changes (plan, status, or renewal)

These four events cover the chassis. As you build your own features, add your domain's event types to the allowed list and fire them through the same dispatch pipeline.

Payload format

Every delivery is a JSON POST with the same envelope: event, timestamp, data, and a unique webhook_id you can use for deduplication.

member.inviteddata describes the invitation:

{
  "event": "member.invited",
  "timestamp": "2026-02-09T12:00:00Z",
  "data": {
    "id": "01234567-89ab-cdef-0123-456789abcdef",
    "email": "jane@example.com",
    "role": "editor",
    "status": "pending",
    "created_at": "2026-02-09T12:00:00Z"
  },
  "webhook_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
}

member.joined and member.removeddata describes the membership:

{
  "event": "member.joined",
  "timestamp": "2026-02-09T12:05:00Z",
  "data": {
    "id": "11111111-2222-3333-4444-555555555555",
    "email": "jane@example.com",
    "role": "editor",
    "status": "active",
    "joined_at": "2026-02-09T12:05:00Z",
    "created_at": "2026-02-09T12:05:00Z"
  },
  "webhook_id": "b2c3d4e5-f6a7-8901-bcde-f12345678901"
}

subscription.updateddata describes the subscription:

{
  "event": "subscription.updated",
  "timestamp": "2026-02-09T12:10:00Z",
  "data": {
    "id": "22222222-3333-4444-5555-666666666666",
    "status": "active",
    "plan_name": "Pro",
    "current_period_end": "2026-03-09T12:10:00Z",
    "created_at": "2026-01-26T09:00:00Z",
    "updated_at": "2026-02-09T12:10:00Z"
  },
  "webhook_id": "c3d4e5f6-a7b8-9012-cdef-123456789012"
}

HTTP headers

Each delivery includes these headers:

Header Description
Content-Type application/json
X-Mcp-Gateway-Signature HMAC-SHA256 signature of the request body, formatted sha256=<hex digest>
X-MCP Gateway-Event The event type (e.g., member.joined)
X-MCP Gateway-Delivery Unique delivery ID (UUID)

Verifying signatures

Every payload is signed with HMAC-SHA256 using your endpoint's signing secret: the signature is the hex digest of the raw request body, sent in the X-Mcp-Gateway-Signature header as sha256=<hex_digest>. Always verify it before trusting a payload, and use a constant-time comparison.

Ruby

def verify_webhook(request, secret)
  payload = request.body.read
  signature = request.headers["X-Mcp-Gateway-Signature"]
  expected = "sha256=" + OpenSSL::HMAC.hexdigest("SHA256", secret, payload)

  unless ActiveSupport::SecurityUtils.secure_compare(signature, expected)
    raise "Invalid webhook signature"
  end

  JSON.parse(payload)
end

Python

import hmac
import hashlib
import json

def verify_webhook(request, secret):
    payload = request.body
    signature = request.headers.get("X-Mcp-Gateway-Signature", "")
    expected = "sha256=" + hmac.new(
        secret.encode(), payload, hashlib.sha256
    ).hexdigest()

    if not hmac.compare_digest(signature, expected):
        raise ValueError("Invalid webhook signature")

    return json.loads(payload)

JavaScript (Node.js)

const crypto = require("crypto");

function verifyWebhook(req, secret) {
  const payload = req.body; // raw body string
  const signature = req.headers["x-mcp-gateway-signature"];
  const expected =
    "sha256=" + crypto.createHmac("sha256", secret).update(payload).digest("hex");

  if (
    !crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))
  ) {
    throw new Error("Invalid webhook signature");
  }

  return JSON.parse(payload);
}

Retry policy

A delivery succeeds when your endpoint responds with any 2xx status. Otherwise MCP Gateway retries with exponential backoff, up to 6 attempts in total:

After failed attempt Next attempt in
1st 5 seconds
2nd 30 seconds
3rd 2 minutes
4th 15 minutes
5th 1 hour

If the 6th attempt also fails, the delivery is marked permanently failed. You can inspect delivery history — status, response codes, and attempt counts — on the endpoint's page in the app or via GET /api/v1/webhook_endpoints/:id.

Connection timeouts

  • Connect timeout: 10 seconds
  • Read timeout: 30 seconds

Auto-disable

Endpoints that fail consistently are disabled automatically. Every failed attempt increments the endpoint's failure count (a successful delivery resets it to zero). Once the count reaches 15 and the endpoint has gone at least 3 days without a successful delivery, it is disabled and stops receiving events. Re-enable it from the Webhooks page, or set enabled: true via the API or MCP.

URL requirements

  • Webhook URLs must use HTTPS in production
  • URLs resolving to private or internal IP ranges (10.x, 172.16.x, 192.168.x, loopback, link-local) are rejected as SSRF protection

Testing webhooks

Click Test on any endpoint on the Webhooks page, or call POST /api/v1/webhook_endpoints/:id/test, to send a ping event and see the result — including the HTTP status your server returned. The ping payload uses the standard envelope with "event": "ping" and "data": {"message": "Test ping from MCP Gateway"}, and is signed like any other delivery.

Best practices

  • Always verify signatures -- never trust unverified payloads
  • Respond quickly -- return 2xx within 30 seconds and process the event asynchronously
  • Be idempotent -- use webhook_id to deduplicate, since retries resend the same payload
  • Monitor delivery health -- watch the failure count and recent deliveries so an endpoint doesn't drift into auto-disable

Next steps