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) => booleanconst 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) => booleanmatchPath("/webhooks/*"); // matches /webhooks/stripe, not /webhooks/stripe/events
matchPath("/webhooks/**"); // matches /webhooks/stripe/events/123
matchPath("/api/*/webhooks"); // matches /api/v1/webhooksmatchQueryParam
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) => booleanmatchBodyPath("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) => booleanmatchBodySubset({
type: "checkout.session.completed",
data: { object: { status: "complete" } },
});matchContentType
Match the Content-Type header, ignoring charset and other parameters.
matchContentType(type: string): (request: Request) => booleanmatchContentType("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) => booleanmatchAll(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) => booleanmatchAny(matchMethod("PUT"), matchMethod("PATCH"));matchVerified
Match requests whose server-side signature verification succeeded.
matchVerified(): (request: Request) => booleanconst req = await client.requests.waitFor(slug, {
match: matchAll(matchMethod("POST"), matchVerified()),
timeout: "30s",
});
// req.signatureVerified === trueRequires 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) => booleanconst failed = await client.requests.waitFor(slug, {
match: matchUnverified(),
timeout: "10s",
});
console.log(failed.signatureError); // structured error JSONCombining 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";| Function | Match source | Event source |
|---|---|---|
isStripeWebhook(request) | stripe-signature | JSON type |
isGitHubWebhook(request) | x-github-event or x-hub-signature-256 | x-github-event |
isShopifyWebhook(request) | x-shopify-hmac-sha256 or x-shopify-topic | x-shopify-topic |
isSlackWebhook(request) | x-slack-signature | JSON type or form command |
isTwilioWebhook(request) | x-twilio-signature | none |
isPaddleWebhook(request) | paddle-signature | JSON event_type |
isLinearWebhook(request) | linear-signature | JSON action + type |
isSendGridWebhook(request) | JSON array with sg_event_id | first array item's event |
isClerkWebhook(request) | svix-id | JSON type |
isDiscordWebhook(request) | x-signature-ed25519 AND x-signature-timestamp | Discord interaction type |
isVercelWebhook(request) | x-vercel-signature | JSON type |
isGitLabWebhook(request) | x-gitlab-event or x-gitlab-token | normalized x-gitlab-event |
isTypeformWebhook(request) | typeform-signature | JSON event_type |
isStandardWebhook(request) | webhook-id AND webhook-timestamp AND webhook-signature | JSON type |
isMetaWebhook(request) | JSON object field | JSON object |
isLemonSqueezyWebhook(request) | JSON meta.event_name | JSON meta.event_name |
isCoinbaseCommerceWebhook(request) | x-cc-webhook-signature | JSON event.type |
isRazorpayWebhook(request) | x-razorpay-signature | JSON event |
isCalWebhook(request) | x-cal-signature-256 | JSON triggerEvent |
isIntercomWebhook(request) | x-hub-signature (sha1=) | JSON topic |
isTelegramWebhook(request) | x-telegram-bot-api-secret-token | none |
isSquareWebhook(request) | x-square-hmacsha256-signature | JSON type |
isHubSpotWebhook(request) | x-hubspot-signature-v3 | JSON 0.subscriptionType |
isMailgunWebhook(request) | JSON signature.token | JSON event-data.event |
isCalendlyWebhook(request) | calendly-webhook-signature | JSON event |
isMuxWebhook(request) | mux-signature | JSON type |
isSentryWebhook(request) | sentry-hook-signature | sentry-hook-resource |
isBitbucketWebhook(request) | x-event-key | x-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 | undefinedconst 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[]> | undefinedconst 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 | undefinedconst 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 | undefinedconst 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.