Webhooks and Observability for MCP Billing

Your MCP server is live, customers are paying, keys are being validated. Now you need to know when things happen — in real time. Here's how to wire up webhooks and observability.

Two layers of observability

Vend gives you two complementary ways to monitor your MCP billing:

Webhooks are for reacting to billing events. SDK hooks are for operational observability. Most production setups use both.

Webhooks

Available events

Vend fires webhooks for these events:

Webhook payload

Every webhook POST includes a JSON body with the event type and relevant data:

{
  "event": "key.created",
  "data": {
    "key_id": "uuid",
    "project_id": "uuid",
    "customer_email": "user@example.com",
    "tier": "pro",
    "created_at": "2026-04-10T12:00:00Z"
  }
}

Verifying signatures

Every webhook includes an x-vend-signature header containing an HMAC-SHA256 signature of the request body, signed with your webhook secret. Always verify this before processing:

import crypto from 'crypto';

function verifyWebhook(body: string, signature: string, secret: string) {
  const expected = crypto
    .createHmac('sha256', secret)
    .update(body)
    .digest('hex');

  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expected),
  );
}

Use timingSafeEqual to prevent timing attacks. Never compare signatures with ===.

Retry behavior

If your endpoint returns a non-2xx status, Vend retries up to 3 times with exponential backoff. This means your webhook handler should be idempotent — processing the same event twice should be safe.

SDK hooks

SDK hooks fire inside your MCP server process. They're callbacks you pass to vendAuth():

const guard = vendAuth({
  apiKey: process.env.VEND_API_KEY!,
  gates: { /* ... */ },

  onValidate(result, fromCache) {
    // Fires on every key validation
    // result.valid, result.tier, result.reason
    // fromCache: true if served from cache
  },

  onActivate(result) {
    // Fires when a key is activated on this instance
    // result.success, result.activationId
  },

  onUsage(toolName, success) {
    // Fires after every recordUsage() call
    // toolName: which tool was invoked
    // success: whether recording succeeded
  },

  onError(operation, error) {
    // Fires on any SDK error
    // operation: 'validate' | 'activate' | 'usage'
    // error: the Error object
  },
});

Practical patterns

Send key events to Slack

Set up a webhook endpoint that forwards events to a Slack channel. This gives your team instant visibility into new customers and cancellations:

// POST /api/vend-webhook
export async function POST(req: Request) {
  const body = await req.text();
  const sig = req.headers.get('x-vend-signature')!;

  if (!verifyWebhook(body, sig, process.env.VEND_WEBHOOK_SECRET!)) {
    return new Response('Invalid signature', { status: 401 });
  }

  const event = JSON.parse(body);

  const messages: Record<string, string> = {
    'key.created': `New customer: ${event.data.customer_email} (${event.data.tier})`,
    'subscription.cancelled': `Churn: ${event.data.customer_email} cancelled`,
    'usage.limit_reached': `${event.data.customer_email} hit their limit`,
  };

  const message = messages[event.event];
  if (message) {
    await fetch(process.env.SLACK_WEBHOOK_URL!, {
      method: 'POST',
      body: JSON.stringify({ text: message }),
    });
  }

  return new Response('OK');
}

Push metrics to Datadog / Grafana

Use SDK hooks to emit custom metrics. This lets you monitor validation latency, cache hit rates, and error rates alongside your existing infrastructure metrics:

import { StatsD } from 'hot-shots';

const statsd = new StatsD();

const guard = vendAuth({
  apiKey: process.env.VEND_API_KEY!,
  gates: { /* ... */ },

  onValidate(result, fromCache) {
    statsd.increment('vend.validation', {
      valid: String(result.valid),
      cached: String(fromCache),
      tier: result.tier || 'none',
    });
  },

  onUsage(toolName, success) {
    statsd.increment('vend.usage', {
      tool: toolName,
      success: String(success),
    });
  },

  onError(op, err) {
    statsd.increment('vend.error', { operation: op });
  },
});

Alert on unusual patterns

Combine webhook events to detect patterns that matter:

Dashboard vs. hooks

The Vend dashboard shows you aggregated data: top tools, revenue totals, customer lists. Webhooks and SDK hooks give you real-time, event-level data. Use the dashboard for business questions (“how's revenue trending?”) and hooks for operational questions (“is validation failing right now?”).

Neither replaces the other. A mature MCP server uses all three: the dashboard for high-level metrics, webhooks for external integrations, and SDK hooks for in-process monitoring.


Ready to add observability to your MCP billing? Read the full webhook docs or create a project and start integrating.