Skip to content

Matchers & Helpers

Filter, detect, and parse webhook requests with built-in matchers for method, header, and body matching. Includes provider detection helpers and JSON body parsing.

Updated May 2026

Matchers

Matchers are predicate functions that return (request) => boolean. Use them with waitFor, waitForAll, and subscribe to filter incoming requests by method, headers, path, body content, and more.

import {
  matchMethod,
  matchHeader,
  matchPath,
  matchQueryParam,
  matchBodyPath,
  matchBodySubset,
  matchContentType,
  matchAll,
  matchAny,
} from "@webhooks-cc/sdk";

matchMethod

Match by HTTP method. Comparison is case-insensitive.

matchMethod(method: string): (request: Request) => boolean
const req = await client.requests.waitFor(slug, {
  match: matchMethod("POST"),
});

matchHeader

Match by header presence, or by header name and exact value. Header names are matched case-insensitively. Values are matched with exact (case-sensitive) equality.

matchHeader(name: string, value?: string): (request: Request) => boolean
// Header exists (any value)
matchHeader("stripe-signature");
 
// Header exists with exact value
matchHeader("x-github-event", "push");

matchPath

Match request paths using glob-style wildcards. * matches a single path segment, ** matches multiple segments.

matchPath(pattern: string): (request: Request) => boolean
matchPath("/webhooks/*"); // matches /webhooks/stripe, not /webhooks/stripe/events
matchPath("/webhooks/**"); // matches /webhooks/stripe/events/123
matchPath("/api/*/webhooks"); // matches /api/v1/webhooks

matchQueryParam

Match query parameter presence or a specific value.

matchQueryParam(key: string, value?: string): (request: Request) => boolean
// Parameter exists (any value)
matchQueryParam("token");
 
// Parameter exists with exact value
matchQueryParam("token", "abc123");

matchBodyPath

Match a value at a dot-notation path into the parsed JSON body. Supports array indexing with numeric path segments.

matchBodyPath(path: string, value: unknown): (request: Request) => boolean
matchBodyPath("type", "checkout.session.completed");
matchBodyPath("data.object.id", "obj_123");
matchBodyPath("items.0.name", "Widget");

matchBodySubset

Deep partial match against the parsed JSON body. Returns true if every key/value in the subset exists in the body, recursively.

matchBodySubset(subset: Record<string, unknown>): (request: Request) => boolean
matchBodySubset({
  type: "checkout.session.completed",
  data: { object: { status: "complete" } },
});

matchContentType

Match the Content-Type header, ignoring charset and other parameters.

matchContentType(type: string): (request: Request) => boolean
matchContentType("application/json");
// matches "application/json; charset=utf-8"

matchAll

Logical AND -- returns true only when every matcher matches. Requires at least one matcher.

matchAll(...matchers: Array<(request: Request) => boolean>): (request: Request) => boolean
matchAll(matchMethod("POST"), matchHeader("content-type", "application/json"));

matchAny

Logical OR -- returns true when at least one matcher matches. Requires at least one matcher.

matchAny(...matchers: Array<(request: Request) => boolean>): (request: Request) => boolean
matchAny(matchMethod("PUT"), matchMethod("PATCH"));

matchVerified

Match requests whose server-side signature verification succeeded.

matchVerified(): (request: Request) => boolean
const req = await client.requests.waitFor(slug, {
  match: matchAll(matchMethod("POST"), matchVerified()),
  timeout: "30s",
});
// req.signatureVerified === true

Requires signature verification to be configured on the endpoint. Returns false for requests where verification was not configured (signatureVerified is null).

matchUnverified

Match requests whose server-side signature verification failed.

matchUnverified(): (request: Request) => boolean
const failed = await client.requests.waitFor(slug, {
  match: matchUnverified(),
  timeout: "10s",
});
console.log(failed.signatureError); // structured error JSON

Combining matchers

Compose matchers to build precise filters for waitFor:

const req = await client.requests.waitFor(slug, {
  timeout: "10s",
  match: matchAll(
    matchMethod("POST"),
    matchHeader("stripe-signature"),
    matchBodyPath("type", "checkout.session.completed")
  ),
});

Nest matchAll and matchAny for complex logic:

const req = await client.requests.waitFor(slug, {
  timeout: "30s",
  match: matchAll(
    matchMethod("POST"),
    matchAny(matchBodyPath("type", "invoice.paid"), matchBodyPath("type", "invoice.payment_failed"))
  ),
});

Provider detection

Detector functions identify common providers from headers and, for providers without a signature header (SendGrid, Meta, Lemon Squeezy, Mailgun), body shape. Use detectWebhookInfo when you want the provider and event/topic, or the boolean helpers when you only need to route one provider.

import {
  detectWebhookInfo,
  detectWebhookProvider,
  isStripeWebhook,
  isGitHubWebhook,
  isShopifyWebhook,
  isSlackWebhook,
  isTwilioWebhook,
  isPaddleWebhook,
  isLinearWebhook,
  isSendGridWebhook,
  isClerkWebhook,
  isDiscordWebhook,
  isVercelWebhook,
  isGitLabWebhook,
  isTypeformWebhook,
  isStandardWebhook,
  isMetaWebhook,
  isLemonSqueezyWebhook,
  isCoinbaseCommerceWebhook,
  isRazorpayWebhook,
  isCalWebhook,
  isIntercomWebhook,
  isTelegramWebhook,
  isSquareWebhook,
  isHubSpotWebhook,
  isMailgunWebhook,
  isCalendlyWebhook,
  isMuxWebhook,
  isSentryWebhook,
  isBitbucketWebhook,
} from "@webhooks-cc/sdk";
FunctionMatch sourceEvent source
isStripeWebhook(request)stripe-signatureJSON type
isGitHubWebhook(request)x-github-event or x-hub-signature-256x-github-event
isShopifyWebhook(request)x-shopify-hmac-sha256 or x-shopify-topicx-shopify-topic
isSlackWebhook(request)x-slack-signatureJSON type or form command
isTwilioWebhook(request)x-twilio-signaturenone
isPaddleWebhook(request)paddle-signatureJSON event_type
isLinearWebhook(request)linear-signatureJSON action + type
isSendGridWebhook(request)JSON array with sg_event_idfirst array item's event
isClerkWebhook(request)svix-idJSON type
isDiscordWebhook(request)x-signature-ed25519 AND x-signature-timestampDiscord interaction type
isVercelWebhook(request)x-vercel-signatureJSON type
isGitLabWebhook(request)x-gitlab-event or x-gitlab-tokennormalized x-gitlab-event
isTypeformWebhook(request)typeform-signatureJSON event_type
isStandardWebhook(request)webhook-id AND webhook-timestamp AND webhook-signatureJSON type
isMetaWebhook(request)JSON object fieldJSON object
isLemonSqueezyWebhook(request)JSON meta.event_nameJSON meta.event_name
isCoinbaseCommerceWebhook(request)x-cc-webhook-signatureJSON event.type
isRazorpayWebhook(request)x-razorpay-signatureJSON event
isCalWebhook(request)x-cal-signature-256JSON triggerEvent
isIntercomWebhook(request)x-hub-signature (sha1=)JSON topic
isTelegramWebhook(request)x-telegram-bot-api-secret-tokennone
isSquareWebhook(request)x-square-hmacsha256-signatureJSON type
isHubSpotWebhook(request)x-hubspot-signature-v3JSON 0.subscriptionType
isMailgunWebhook(request)JSON signature.tokenJSON event-data.event
isCalendlyWebhook(request)calendly-webhook-signatureJSON event
isMuxWebhook(request)mux-signatureJSON type
isSentryWebhook(request)sentry-hook-signaturesentry-hook-resource
isBitbucketWebhook(request)x-event-keyx-event-key

All header comparisons are case-insensitive. Discord and Standard Webhooks require multiple headers to be present simultaneously.

Provider + event example

const info = detectWebhookInfo(request);
 
if (info?.provider === "typeform") {
  console.log(info.event); // "form_response"
}
 
console.log(detectWebhookProvider(request)); // "typeform"

Router example

const request = await client.requests.waitFor(slug, { timeout: "10s" });
 
if (isStripeWebhook(request)) {
  // Verify Stripe signature and handle event
} else if (isGitHubWebhook(request)) {
  // Verify GitHub signature and handle event
} else if (isStandardWebhook(request)) {
  // Standard Webhooks request (Polar, Svix, Clerk, Resend)
} else {
  console.log("Unknown provider");
}

Body parsing

Four utilities for extracting structured data from captured request bodies.

import { parseJsonBody, parseFormBody, parseBody, extractJsonField } from "@webhooks-cc/sdk";

parseJsonBody

Safely parse a JSON request body. Returns undefined if the body is empty or not valid JSON. Never throws.

parseJsonBody(request: Request): unknown | undefined
const body = parseJsonBody(request);
if (body) {
  console.log(body);
}

parseFormBody

Parse an application/x-www-form-urlencoded body. Returns undefined if the content type does not match. Repeated keys are collected into arrays.

parseFormBody(request: Request): Record<string, string | string[]> | undefined
const form = parseFormBody(request);
if (form) {
  console.log(form.email); // "[email protected]"
  console.log(form.tags); // ["a", "b"] if repeated
}

parseBody

Auto-detect content type and parse accordingly. JSON and form-encoded bodies are decoded; other content types are returned as raw text.

parseBody(request: Request): unknown | Record<string, string | string[]> | string | undefined
const parsed = parseBody(request);

extractJsonField

Extract a nested field from the JSON body using dot-notation. Supports array indexing. Returns undefined if the body is missing, invalid JSON, or the path does not exist.

extractJsonField<T>(request: Request, path: string): T | undefined
const email = extractJsonField<string>(request, "data.customer.email");
const firstItem = extractJsonField<string>(request, "items.0.name");

matchJsonField

A convenience matcher that checks whether a top-level JSON field equals an expected value. Works like matchBodyPath but limited to top-level fields.

import { matchJsonField } from "@webhooks-cc/sdk";
 
const req = await client.requests.waitFor(slug, {
  match: matchJsonField("type", "checkout.session.completed"),
});

Learn more

API Reference

Complete method reference for the SDK.

Signature Verification

Verify webhook signatures for 27 signed providers.

Testing

CI/CD integration and testing patterns.