@safehook/safehook
v0.1.3
Published
Framework-agnostic webhook reliability primitives for TypeScript.
Downloads
88
Maintainers
Readme
SafeHook
Reliable webhook processing primitives for TypeScript.
SafeHook helps you verify, dedupe, track, and replay webhook deliveries without forcing you into a framework, queue, ORM, or storage stack.
Why SafeHook
Webhook handlers usually need the same reliability layer:
- verify provider signatures against the exact raw request body
- prevent duplicate execution during retries and redeliveries
- coordinate concurrent deliveries safely
- persist processing state for debugging and replay
- expose clean hooks for metrics and operations
SafeHook focuses on that layer and stays out of the rest of your application.
What It Is
SafeHook is:
- framework-agnostic
- storage-agnostic
- dependency-light
- built for exact raw-body verification
- designed for idempotent, replay-safe processing
SafeHook is not:
- a workflow engine
- a queue system
- an event bus
- a webhook SaaS
- an orchestration platform
- a backend framework
Installation
npm install @safehook/safehookSafeHook does not install Redis, PostgreSQL, or provider SDKs for you. If your application wants those integrations, your application owns those dependencies.
Quickstart
import { createSafeHook, memoryStore, stripe } from "@safehook/safehook";
const safehook = createSafeHook({
store: memoryStore(),
});
await safehook.process({
rawBody,
headers,
provider: stripe({
secret: process.env.STRIPE_WEBHOOK_SECRET!,
}),
onEvent: async (event, ctx) => {
console.log("processed", ctx.eventType, ctx.idempotencyKey);
},
});Core Model
You bring:
- the exact
rawBody - request
headers - provider configuration
- storage choice
- business logic
SafeHook provides:
- signature verification
- provider event parsing
- idempotency key resolution
- atomic duplicate prevention
- processing state tracking
- replay-safe execution
- lifecycle hooks for observability
Feature Overview
createSafeHook()for reusable process/replay orchestrationprocessWebhook()andreplayWebhook()core functions- built-in stores:
memoryStore()redisStore(client, { mode: "node-redis" })postgresStore(client, { mode: "pg" })
- built-in providers:
stripe()github()customProvider()
- HTTP helper:
handleWebhookHttp()
- framework adapter helpers:
- Express
- Fastify
- Hono
- Next route handler style
- metrics helpers:
- OpenTelemetry
- Prometheus
Reliability Defaults
SafeHook defaults are intentionally conservative:
- exact
rawBodyis always required for verification and parsing eventPayloadis stored by default so replay can workrawBodyis not stored unlessstoreRawBody: true- normalized
headersare not stored unlessstoreHeaders: true
SafeHook verifies and parses against the rawBody you provide. It does not reserialize or mutate the body before signature verification.
Minimal-Persistence Example
If you want replay support without persisting rawBody or normalized headers, start here:
HTTP Behavior
handleWebhookHttp() maps common SafeHook failures into deterministic HTTP responses:
- invalid signature ->
401 - parse or metadata problems ->
400 - store or infrastructure failures ->
500 - replay/conflict-style failures ->
409
This keeps invalid webhook traffic from surfacing as generic framework 500 errors.
Storage Model
SafeHook does not own infrastructure.
You can use:
- in-memory storage for local development and tests
- Redis for distributed claim coordination
- PostgreSQL for durable audit trails and failure review
- custom stores that implement the SafeHook store contract
Mode-specific typing is exported when you want stronger TypeScript support for concrete client shapes:
NodeRedisClientNodeRedisStoreOptionsPgClientPgStoreOptions
See docs/stores.md.
Provider Typing
SafeHook keeps provider payloads broad by default so upstream event changes do not get excluded by narrow package types.
For stronger typing on common payload families, SafeHook also exports helper types.
Stripe examples:
StripeCheckoutSessionEventStripeInvoiceEventStripePaymentIntentEvent
GitHub examples:
GitHubIssuesEventGitHubPullRequestEventGitHubPushEvent
Both stripe() and github() support generic typing when you want to narrow payloads intentionally.
See docs/providers.md.
Replay and Operations
SafeHook stores lifecycle state so operators can inspect failures and replay events safely.
Tracked statuses include:
receivedprocessingsucceededfailedduplicateexpired
Replay is handler-driven: SafeHook manages the reliability flow, and your application supplies the business handler.
See:
Real-World Examples
Release Docs
Current Scope
SafeHook is a focused reliability layer for incoming webhooks. That focus is deliberate. The package should stay small, predictable, and easy to compose into different application architectures.
