Docs/Reference

createVerifier()

Verify Standard Webhooks signatures with WebCrypto. Zero dependencies.

import { createVerifier } from "@typedhook/verify";

function createVerifier(secret: string, opts?: { toleranceSec?: number }): Verifier;

interface Verifier {
  verify<T = unknown>(request: Request): Promise<VerifyResult<T>>;
  verifyPayload<T = unknown>(
    payload: string | Uint8Array,
    headers: Headers | Record<string, string | string[] | undefined>,
  ): Promise<VerifyResult<T>>;
}

Options

OptionDefaultWhat it does
toleranceSec300How far the webhook-timestamp may be from now, in either direction

Throws

createVerifier() throws a RangeError at startup when:

  • secret is empty, or starts with whsec_ without valid, non-empty base64 after it.
  • toleranceSec isn't a finite number of 0 or more.

verify() and verifyPayload() never throw.

Result

type VerifyResult<T> =
  | { ok: true; id: string; timestamp: Date; payload: T }
  | { ok: false; reason: VerifyFailure };

type VerifyFailure =
  | "missing_headers"
  | "invalid_id"
  | "invalid_timestamp"
  | "timestamp_out_of_tolerance"
  | "invalid_signature"
  | "invalid_secret"
  | "unreadable_body"
  | "invalid_json";
FieldWhat it is
idThe webhook-id header. Same on every retry, use it to dedupe
timestampThe webhook-timestamp header as a Date: when this attempt was signed
payloadThe parsed JSON body, typed as T but not validated

See Failure reasons for when each reason is returned.

verify() and verifyPayload()

MethodInputNotes
verify()A Fetch API RequestReads the body. Pass req.clone() to read it again
verifyPayload()The raw body as a string or Uint8Array, and the headersPlain header objects can use any casing

Both accept webhook-* headers and fall back to svix-*. Secrets that start with whsec_ are base64-decoded. Any other secret is used as plain text.

Edit on GitHubLast updated on October 14, 2026

On this page

For coding agents

Every page is available as Markdown. Point your agent at the full docs in one file.