Documentation

Ship webhook monitoring in a single commit

Install the SDK, wrap your handler, and you're watching traffic in under 60 seconds. Pick your language below.

Installation

Install the Outworx Hooks SDK using your preferred package manager.

npm
npm install outworx-hooks
yarn
yarn add outworx-hooks
pnpm
pnpm add outworx-hooks

Quick Start

Wrap your existing webhook handler with the Outworx monitoring layer. Choose your language and framework below.

// app/api/webhooks/stripe/route.ts
import { init } from 'outworx-hooks';
import { withWebhookMonitoring } from 'outworx-hooks/nextjs';

init({ apiKey: process.env.OUTWORX_HOOKS_API_KEY! });

export const POST = withWebhookMonitoring(
  { provider: 'stripe' },
  async (req) => {
    const body = await req.json();
    // handle webhook event
    return Response.json({ received: true });
  }
);

Configuration

Configure the SDK with your API key and optional settings. Your API key is verified with Outworx on first use — invalid or unknown keys are rejected, so make sure to register to get a valid key.

import { init } from 'outworx-hooks';

init({
  // Required: your Outworx API key
  apiKey: process.env.OUTWORX_HOOKS_API_KEY!,

  // Optional: custom endpoint (for self-hosted / enterprise)
  endpoint: 'https://ingest.outworx.io',

  // Optional: enable debug logging
  debug: false,

  // Optional: timeout for telemetry sends (ms)
  timeout: 5000,

  // Optional: error callback for failed ingest attempts
  // onError: (err) => console.error(err),
});

Track Options

Customize tracking per handler with these options.

withWebhookMonitoring({
  // Required: identify the webhook provider
  provider: 'stripe',

  // Optional: override how event_type is derived
  eventTypeHeader: 'stripe-event-type',
  eventTypeField: 'type',

  // Optional: capture the full request + response bodies
  // (off by default — payloads often contain customer data)
  captureBody: true,

  // Optional: capture request headers (on by default; sensitive
  // values like Authorization / Cookie are always redacted)
  captureHeaders: true,

  // Optional: custom metadata attached to every event
  metadata: {
    environment: 'production',
    service: 'billing',
  },
}, handler);
OptionTypeDefaultDescription
providerstringrequiredWebhook provider name (stripe, shopify, github, etc.)
eventTypeHeaderstringautoHeader name to extract event_type from. Auto-detected for known providers.
eventTypeFieldstringautoBody field to extract event_type from (e.g., "type" for Stripe).
captureBodybooleanfalseCapture request + response bodies. Off by default — payloads often contain customer PII.
captureHeadersbooleantrueCapture request headers. Sensitive values (Authorization, Cookie, etc.) are always redacted.
metadataobject{}Custom key-value pairs attached to every event.

Signature Verification

The SDK verifies webhook signatures for you — just pass the signing secret. Supports Stripe, GitHub, Shopify, Svix, Clerk, and Slack. Invalid signatures are rejected with 401 before your handler runs, and every verification result is reported to your dashboard for audit.

import { init } from 'outworx-hooks';
import { withWebhookMonitoring } from 'outworx-hooks/nextjs';

init({ apiKey: process.env.OUTWORX_HOOKS_API_KEY! });

export const POST = withWebhookMonitoring(
  {
    provider: 'stripe',
    signatureSecret: process.env.STRIPE_WEBHOOK_SECRET!,
    // Optional:
    // rejectInvalidSignatures: false,  // default true — set false to let handler decide
    // signatureTolerance: 300,         // default 300s — replay-attack window
  },
  async (req) => {
    // Signature is already verified — handle the event
    const body = await req.json();
    return Response.json({ received: true });
  }
);

Custom verifier

For providers we don't support natively, pass your own function:

withWebhookMonitoring(
  {
    provider: 'my-service',
    signatureVerifier: async (rawBody, headers) => {
      return myCheck(rawBody, headers['x-my-signature']);
    },
  },
  handler
);

Standalone helpers

Import and use the verifiers directly:

import { verifyStripeSignature } from 'outworx-hooks/security';

const valid = verifyStripeSignature({
  rawBody,
  header: req.headers['stripe-signature'] as string,
  secret: process.env.STRIPE_WEBHOOK_SECRET!,
});
Express & Fastify: signature verification needs the raw request body. Use express.json({ verify }) or the fastify-raw-body plugin. See the SDK README for setup examples.

Idempotency

Webhook providers retry deliveries when your handler times out or returns a non-2xx response — which can cause double-charged customers, duplicate emails, and double-provisioned resources. Pass an idempotencyKey function and the SDK short-circuits retries with the cached response from the first successful delivery.

import { init } from 'outworx-hooks';
import { withWebhookMonitoring } from 'outworx-hooks/nextjs';

init({ apiKey: process.env.OUTWORX_HOOKS_API_KEY! });

export const POST = withWebhookMonitoring(
  {
    provider: 'stripe',
    signatureSecret: process.env.STRIPE_WEBHOOK_SECRET!,
    // Dedupe on the Stripe event ID. Retries of the same event will
    // return the cached response without re-invoking the handler.
    idempotencyKey: (_req, body) => (body as any).id,
    // Optional: cache window (default 24h, max 7d)
    // idempotencyTtl: 3600,
  },
  async (req) => {
    const event = await req.json();
    await chargeCustomer(event);
    return Response.json({ received: true });
  }
);

Recommended keys per provider

// Stripe — event ID in the body
idempotencyKey: (_req, body) => (body as any).id

// GitHub — delivery ID header
idempotencyKey: (_req, _body, headers) => headers['x-github-delivery']

// Shopify — webhook ID header
idempotencyKey: (_req, _body, headers) => headers['x-shopify-webhook-id']

// Svix / Clerk — message ID header
idempotencyKey: (_req, _body, headers) => headers['svix-id']
Fail-safe: if our backend is unreachable, the check fails open — your handler runs as normal. Idempotency failures never block webhook delivery. Stale reservations older than 30 seconds are automatically cleared, so crashed handlers can be retried.

Environment Variables

The SDK supports configuration via environment variables as an alternative to the init function. Works the same for both JavaScript and Python.

.env
# Required
OUTWORX_HOOKS_API_KEY=ohk_your_api_key_here

# Optional
OUTWORX_HOOKS_ENDPOINT=https://ingest.outworx.io
OUTWORX_HOOKS_DEBUG=false
OUTWORX_HOOKS_CAPTURE_PAYLOADS=false
OUTWORX_HOOKS_TIMEOUT=5000

When using environment variables, you can initialize without arguments:

// No init() call needed — the SDK reads
// OUTWORX_HOOKS_API_KEY from your environment
// automatically when your handler runs.