hooksentinel

Getting Started

Install @hooksentinel/core and wire up a verified Stripe webhook handler in Express in under 60 seconds.

Install

npm install @hooksentinel/core

No peer dependencies are required for the core package. Framework adapters (Express, Fastify, NestJS, Next.js, Hono, Lambda) are exported from subpaths so you only pull in what you use — see Bundle size.

60-second quickstart: Stripe + Express

1. Get your webhook signing secret

From the Stripe dashboard, or via the CLI for local testing:

stripe listen --forward-to localhost:3000/webhooks/stripe

Stripe prints a whsec_... secret — put it in your environment.

.env
STRIPE_WEBHOOK_SECRET=whsec_...

2. Create the handler

src/webhooks/stripe.ts
import { createWebhookHandler, stripe } from '@hooksentinel/core';
import { memoryStore } from '@hooksentinel/core/stores';

export const stripeWebhook = createWebhookHandler({
  provider: stripe({ secret: process.env.STRIPE_WEBHOOK_SECRET! }),
  idempotency: memoryStore(),
  onEvent: async (event) => {
    switch (event.type) {
      case 'checkout.session.completed':
        await fulfillOrder(event.data.object.id);
        break;
      case 'invoice.payment_failed':
        await notifyDunning(event.data.object.customer as string);
        break;
    }
  },
});

async function fulfillOrder(sessionId: string) {
  // your business logic
}

async function notifyDunning(customerId: string) {
  // your business logic
}

event is typed to Stripe's event union — event.type narrows event.data.object automatically.

3. Mount it in Express

The only thing you must get right yourself: the route needs the raw request body, not JSON-parsed. hooksentinel verifies the signature against the raw bytes.

src/server.ts
import express from 'express';
import { toExpressHandler } from '@hooksentinel/core/express';
import { stripeWebhook } from './webhooks/stripe';

const app = express();

// raw body parser scoped to this route only — must run before express.json()
app.post(
  '/webhooks/stripe',
  express.raw({ type: 'application/json' }),
  toExpressHandler(stripeWebhook),
);

// JSON parsing for everything else can go after, safely
app.use(express.json());

app.listen(3000);

That's the whole integration. Compare it to what you'd write by hand in the landing page comparison — this is roughly 8 lines instead of 40, and it covers signature verification, timestamp tolerance, and deduplication that hand-rolled versions usually skip.

What just happened

  • stripe({ secret }) configures the Stripe signature scheme (HMAC-SHA256 over timestamp.body, read from the Stripe-Signature header).
  • memoryStore() deduplicates by event ID using an in-process Map. Fine for local dev and single-instance deployments; swap for redisStore() or prismaStore() in production — see Idempotency.
  • toExpressHandler adapts the framework-agnostic handler to an Express (req, res) signature, reading req.body as the raw Buffer that express.raw() produced.
  • If verification fails, hooksentinel responds with the correct status code and never calls onEvent. See Errors for the full list of failure modes and how to handle each one.

Next steps

  • Supported providers — swap stripe() for github(), shopify(), or any of the other 9 built-in adapters.
  • Frameworks — guides for Fastify, NestJS, Next.js, Hono, and Lambda.
  • Idempotency — production-grade deduplication with Redis or Prisma.

Last updated on

On this page