hooksentinel

Supported Providers

All 9 built-in provider adapters — signature scheme, headers, and import for each.

Every provider adapter implements the same interface: verify the signature, check the timestamp tolerance, and parse the body into a typed event. Import the one you need from @hooksentinel/core and pass it to createWebhookHandler.

import { createWebhookHandler, stripe } from '@hooksentinel/core';

const handler = createWebhookHandler({
  provider: stripe({ secret: process.env.STRIPE_WEBHOOK_SECRET! }),
  onEvent: async (event) => { /* ... */ },
});

Provider table

ProviderImportSignature schemeHeader(s)Config
Stripestripe()HMAC-SHA256 over t=<ts>,v1=<body>Stripe-Signaturesecret
GitHubgithub()HMAC-SHA256 over raw bodyX-Hub-Signature-256secret
Shopifyshopify()HMAC-SHA256 (base64) over raw bodyX-Shopify-Hmac-Sha256secret
Standard WebhooksstandardWebhooks()HMAC-SHA256, standardwebhooks.com specwebhook-id, webhook-timestamp, webhook-signaturesecret
Slackslack()HMAC-SHA256 over v0:<ts>:<body>X-Slack-Signature, X-Slack-Request-TimestampsigningSecret
Discorddiscord()Ed25519 signatureX-Signature-Ed25519, X-Signature-TimestamppublicKey
Twiliotwilio()HMAC-SHA1 over URL + sorted params (base64)X-Twilio-SignatureauthToken, url
Paddlepaddle()HMAC-SHA256 over raw bodyPaddle-Signaturesecret
Genericgeneric()Configurable — HMAC-SHA256/SHA1 or Ed25519, custom header namesConfigurablesecret, headerName, algorithm

All HMAC-based providers default to a 5-minute (300s) timestamp tolerance where the provider includes a timestamp; override with the tolerance option in seconds. See timestamp_out_of_tolerance for what happens outside that window.

Stripe

import { stripe } from '@hooksentinel/core';

stripe({
  secret: process.env.STRIPE_WEBHOOK_SECRET!,
  tolerance: 300, // optional, seconds — default 300
});

Events are typed against Stripe's Event union. event.type narrows event.data.object.

GitHub

import { github } from '@hooksentinel/core';

github({
  secret: process.env.GITHUB_WEBHOOK_SECRET!,
});

Events are typed by the X-GitHub-Event header (push, pull_request, issues, workflow_run, ...). GitHub does not send a timestamp, so replay protection relies on deduplication rather than tolerance windows — see Idempotency.

Shopify

import { shopify } from '@hooksentinel/core';

shopify({
  secret: process.env.SHOPIFY_WEBHOOK_SECRET!,
});

Events are typed by the X-Shopify-Topic header (orders/create, orders/updated, app/uninstalled, ...).

Standard Webhooks

Implements the open Standard Webhooks specification used by a growing set of providers (Clerk, Resend, and others that adopt the spec).

import { standardWebhooks } from '@hooksentinel/core';

standardWebhooks({
  secret: process.env.WEBHOOK_SIGNING_SECRET!,
});

Slack

import { slack } from '@hooksentinel/core';

slack({
  signingSecret: process.env.SLACK_SIGNING_SECRET!,
});

Handles both Events API payloads and interactive component payloads, including the url_verification handshake challenge.

Discord

Discord signs with Ed25519 rather than HMAC — hooksentinel handles the different verification primitive transparently.

import { discord } from '@hooksentinel/core';

discord({
  publicKey: process.env.DISCORD_PUBLIC_KEY!,
});

hooksentinel also responds to Discord's PING interaction type automatically so your onEvent only sees real events.

Twilio

Twilio's signature is computed over the full request URL plus sorted form parameters, not a raw JSON body, so pass the externally-visible URL your webhook is registered at.

import { twilio } from '@hooksentinel/core';

twilio({
  authToken: process.env.TWILIO_AUTH_TOKEN!,
  url: 'https://api.example.com/webhooks/twilio',
});

Paddle

import { paddle } from '@hooksentinel/core';

paddle({
  secret: process.env.PADDLE_WEBHOOK_SECRET!,
});

Supports Paddle Billing's v2 signature format.

Generic

For providers without a built-in adapter, generic() covers the common signature shapes without writing a full adapter.

import { generic } from '@hooksentinel/core';

generic({
  secret: process.env.CUSTOM_WEBHOOK_SECRET!,
  headerName: 'X-Custom-Signature',
  algorithm: 'sha256', // 'sha256' | 'sha1' | 'ed25519'
});

generic() events are typed as unknown by default — narrow them yourself, or wrap the result with your own Zod schema in onEvent.

Last updated on

On this page