JSPM

  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 4
  • Score
    100M100P100Q34839F
  • License MIT

Production-grade webhook engine with retry, idempotency, local dev tools, and logging.

Package Exports

  • hook-engine

Readme

πŸ”Œ hook-engine

Production-grade webhook engine with:

  • βœ… Signature verification (Stripe-style)
  • βœ… Retry + exponential backoff
  • βœ… Deduping (SQLite)
  • βœ… Local replay CLI
  • βœ… JSON event logs
  • βœ… Safe for serverless & edge runtimes

πŸš€ Why hook-engine?

Stripe (and others) send webhooks β€” but most apps:

  • πŸ’₯ Fail silently in serverless
  • πŸ” Have no retry engine
  • ⚠️ Lack idempotency logic
  • πŸ” Can’t replay/test webhooks locally
  • 😡 Lose trace of webhook history

hook-engine solves all that with:

πŸ› οΈ Drop-in devtool-grade library and CLI for real-world webhook infra.


πŸ“¦ Install

pnpm add hook-engine

Or globally for CLI usage:

pnpm add -g hook-engine

πŸ“‚ Project Structure (Example)

πŸ“ your-app/
β”‚
β”œβ”€ πŸ“¦ db/
β”‚   └─ seen.sqlite          # Event ID deduping
β”‚
β”œβ”€ πŸ“ lib/
β”‚   β”œβ”€ config.ts            # Webhook secret & source
β”‚   β”œβ”€ handler.ts           # Your business logic
β”‚   └─ dedupe.ts            # Event deduplication store
β”‚
β”œβ”€ πŸ“ pages/
β”‚   └─ api/
β”‚       └─ webhooks/
β”‚           └─ stripe.ts    # Webhook entrypoint

πŸ”§ Usage (Next.js or Express)

// pages/api/webhooks/stripe.ts
import type { NextApiRequest, NextApiResponse } from "next";
import { receiveWebhook, retry } from "hook-engine";
import { config } from "../../../lib/config";
import { processEvent } from "../../../lib/handler";
import { isDuplicate, markSeen } from "../../../lib/dedupe";

export const configRuntime = {
  api: {
    bodyParser: false, // Required for raw body access
  },
};

export default async function handler(req: NextApiRequest, res: NextApiResponse) {
  try {
    const event = await receiveWebhook(req, config);

    if (isDuplicate(event.id)) {
      console.warn(`⚠️ Duplicate skipped: ${event.id}`);
      return res.status(200).send("duplicate");
    }

    markSeen(event.id);
    await retry(event, processEvent);

    res.status(200).send("ok");
  } catch (err: any) {
    console.error("❌ Webhook error:", err.message);
    res.status(400).send("webhook error");
  }
}

🧠 Your Business Logic

// lib/handler.ts
export async function processEvent(event: any) {
  console.log(`🎯 Received ${event.type} for ${event.id}`);

  if (event.type === "invoice.paid") {
    const customer = event.payload.customer;
    console.log(`πŸŽ‰ Grant premium access to ${customer}`);
    // do something: DB, email, billing...
  }

  if (event.type === "invoice.failed") {
    throw new Error("πŸ”₯ Simulated failure for retry");
  }
}

🧱 Deduping Store (SQLite)

// lib/dedupe.ts
import Database from "better-sqlite3";
import path from "path";
import fs from "fs";

const DB_PATH = path.resolve(process.cwd(), "db/seen.sqlite");
fs.mkdirSync(path.dirname(DB_PATH), { recursive: true });
const db = new Database(DB_PATH);

db.exec(`CREATE TABLE IF NOT EXISTS seen_events (
  id TEXT PRIMARY KEY,
  seen_at INTEGER
);`);

export function isDuplicate(id: string): boolean {
  return !!db.prepare("SELECT 1 FROM seen_events WHERE id = ?").get(id);
}

export function markSeen(id: string) {
  db.prepare("INSERT OR IGNORE INTO seen_events (id, seen_at) VALUES (?, ?)").run(id, Date.now());
}

πŸ” Config Example

// lib/config.ts
import { WebhookConfig } from "hook-engine";

export const config: WebhookConfig = {
  source: "stripe",
  secret: process.env.STRIPE_WEBHOOK_SECRET!,
};

πŸ’» CLI Commands

# Replay from file
webhook-gateway replay ./mock/invoice-paid.json \
  --target http://localhost:3000/api/webhooks/stripe \
  --secret $STRIPE_WEBHOOK_SECRET

# View logged events
webhook-gateway logs

# JSON mode
webhook-gateway logs --json

# Replay by ID
webhook-gateway logs --replay evt_123456 \
  --target http://localhost:3000/api/webhooks/stripe \
  --secret $STRIPE_WEBHOOK_SECRET

All events are logged into .webhook-engine/events.log in newline-delimited JSON.


πŸ’‘ Dev Workflow

  1. Start your server:

    pnpm dev
  2. In another terminal:

    stripe listen --forward-to localhost:3000/api/webhooks/stripe
  3. Trigger an event from Stripe Dashboard or CLI

  4. Replay failed events like a boss:

    webhook-gateway logs --replay evt_123 --target http://localhost:3000/api/webhooks/stripe

πŸ§ͺ Supported Providers

  • βœ… Stripe (first-class)
  • 🟑 GitHub (soon)
  • 🟑 Clerk/Auth0 (soon)
  • 🟒 Any JSON webhook provider (via adapters)

πŸ›‘οΈ Features

Feature Status
Signature verification βœ…
Retry engine βœ…
Deduplication store βœ…
Local replay CLI βœ…
JSONL event logs βœ…
SQLite support βœ…
Serverless safe βœ…
Extendable adapters πŸ› οΈ coming

πŸ§ͺ Example Repos

Coming soon...


πŸ“œ License

MIT β€” by Naol Ketema


Star this repo if you love solid webhook tooling 🌟 Tweet it if it saved your weekend πŸ”