Good Webhooks

Installation

Add Good Webhooks to an existing application. Install, create a key and the tables, and connect.

Install the dependencies for your setup below. Better Auth endpoint management uses your existing database adapter. Built-in delivery and standalone PostgreSQL management require PostgreSQL 16 or 17.

Requirements: Node.js 24 or later. TypeScript consumers need version 5.9.3 or later with moduleResolution set to NodeNext or Bundler. Edge runtimes are not supported.

Install

npm install good-webhooks@beta pg zod
npm install --save-dev @types/pg

The beta tag installs the latest beta. Commit your lockfile. Payload schemas can use any Standard Schema library; these docs use Zod.

Create a storage key

Endpoint signing secrets are encrypted in your database with this key. Generate it once:

node -e 'import("good-webhooks").then(m => console.log(m.generateEncryptionKey()))'

Store the output in your secret manager or .env as WEBHOOK_ENCRYPTION_KEY. Every process that sends or manages webhooks needs the same key.

Create the tables

Write the SQL to a migration file:

node -e 'import("good-webhooks/migrations").then(m => process.stdout.write(m.getPostgresMigration()))' > 001-webhooks.sql

Apply it with the migration tool you already use, or with psql:

psql "$DATABASE_URL" --single-transaction -v ON_ERROR_STOP=1 -f 001-webhooks.sql

The tables are prefixed webhook_ and go in public. To use another schema, see migrations.

Create the client

webhooks.ts
import { createWebhooks } from 'good-webhooks'
import { Pool } from 'pg'
import { z } from 'zod'

export const pool = new Pool({ connectionString: process.env.DATABASE_URL })

export const webhooks = createWebhooks({
  database: pool,
  encryptionKey: process.env.WEBHOOK_ENCRYPTION_KEY!,
  events: {
    'invoice.paid': z.object({ invoiceId: z.string(), amount: z.number().int() }),
  },
})

Check the setup once at startup. It throws SCHEMA_MISMATCH if the tables are missing or outdated:

await webhooks.check()

You're set up. Nothing is sent yet: that takes a published event and a running worker.

Entry points

Import only what a process needs. A receiver never loads database code.

ImportUse it for
good-webhookscreateWebhooks(): management and delivery together on PostgreSQL
good-webhooks/deliverycreateDelivery(): the sender alone, fed by any EndpointSource
good-webhooks/managementThe management contracts and createManagement() for custom storage
good-webhooks/management/postgresStandalone endpoint management without delivery tables
good-webhooks/better-authThe Better Auth server plugin and trusted management provider
good-webhooks/better-auth/clientThe Better Auth client plugin for browsers
good-webhooks/verifyparseWebhook() and verifyWebhook() for receivers
good-webhooks/migrationsgetPostgresMigration() and the shipped SQL files

The package ships ESM and also loads through CommonJS require() on Node.js 24. If you use the Better Auth plugin, enable skipLibCheck for its upstream declarations; your own types stay fully checked.

On this page