# Installation

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

Source: https://good-webhooks.dev/docs/installation



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 [#install]

**PostgreSQL**

<CodeBlockTabs defaultValue="npm">
  <CodeBlockTabsList>
    <CodeBlockTabsTrigger value="npm">
      npm
    </CodeBlockTabsTrigger>

    <CodeBlockTabsTrigger value="pnpm">
      pnpm
    </CodeBlockTabsTrigger>

    <CodeBlockTabsTrigger value="yarn">
      yarn
    </CodeBlockTabsTrigger>

    <CodeBlockTabsTrigger value="bun">
      bun
    </CodeBlockTabsTrigger>
  </CodeBlockTabsList>

  <CodeBlockTab value="npm">
    ```bash
    npm install good-webhooks@beta pg zod
    ```
  </CodeBlockTab>

  <CodeBlockTab value="pnpm">
    ```bash
    pnpm add good-webhooks@beta pg zod
    ```
  </CodeBlockTab>

  <CodeBlockTab value="yarn">
    ```bash
    yarn add good-webhooks@beta pg zod
    ```
  </CodeBlockTab>

  <CodeBlockTab value="bun">
    ```bash
    bun add good-webhooks@beta pg zod
    ```
  </CodeBlockTab>
</CodeBlockTabs>

<CodeBlockTabs defaultValue="npm">
  <CodeBlockTabsList>
    <CodeBlockTabsTrigger value="npm">
      npm
    </CodeBlockTabsTrigger>

    <CodeBlockTabsTrigger value="pnpm">
      pnpm
    </CodeBlockTabsTrigger>

    <CodeBlockTabsTrigger value="yarn">
      yarn
    </CodeBlockTabsTrigger>

    <CodeBlockTabsTrigger value="bun">
      bun
    </CodeBlockTabsTrigger>
  </CodeBlockTabsList>

  <CodeBlockTab value="npm">
    ```bash
    npm install --save-dev @types/pg
    ```
  </CodeBlockTab>

  <CodeBlockTab value="pnpm">
    ```bash
    pnpm add --save-dev @types/pg
    ```
  </CodeBlockTab>

  <CodeBlockTab value="yarn">
    ```bash
    yarn add --dev @types/pg
    ```
  </CodeBlockTab>

  <CodeBlockTab value="bun">
    ```bash
    bun add --dev @types/pg
    ```
  </CodeBlockTab>
</CodeBlockTabs>

**Better Auth**

<CodeBlockTabs defaultValue="npm">
  <CodeBlockTabsList>
    <CodeBlockTabsTrigger value="npm">
      npm
    </CodeBlockTabsTrigger>

    <CodeBlockTabsTrigger value="pnpm">
      pnpm
    </CodeBlockTabsTrigger>

    <CodeBlockTabsTrigger value="yarn">
      yarn
    </CodeBlockTabsTrigger>

    <CodeBlockTabsTrigger value="bun">
      bun
    </CodeBlockTabsTrigger>
  </CodeBlockTabsList>

  <CodeBlockTab value="npm">
    ```bash
    npm install good-webhooks@beta better-auth@1.7.7
    ```
  </CodeBlockTab>

  <CodeBlockTab value="pnpm">
    ```bash
    pnpm add good-webhooks@beta better-auth@1.7.7
    ```
  </CodeBlockTab>

  <CodeBlockTab value="yarn">
    ```bash
    yarn add good-webhooks@beta better-auth@1.7.7
    ```
  </CodeBlockTab>

  <CodeBlockTab value="bun">
    ```bash
    bun add good-webhooks@beta better-auth@1.7.7
    ```
  </CodeBlockTab>
</CodeBlockTabs>

Add `pg zod` too if the same app will [send events](https://good-webhooks.dev/docs/better-auth/delivery.md). Supported range: `better-auth >=1.7.7 <1.8.0`. Then follow the [Better Auth plugin](https://good-webhooks.dev/docs/better-auth.md) guide; the steps below are for the PostgreSQL setup.

**Receiver only**

You only receive webhooks from a Good Webhooks sender:

<CodeBlockTabs defaultValue="npm">
  <CodeBlockTabsList>
    <CodeBlockTabsTrigger value="npm">
      npm
    </CodeBlockTabsTrigger>

    <CodeBlockTabsTrigger value="pnpm">
      pnpm
    </CodeBlockTabsTrigger>

    <CodeBlockTabsTrigger value="yarn">
      yarn
    </CodeBlockTabsTrigger>

    <CodeBlockTabsTrigger value="bun">
      bun
    </CodeBlockTabsTrigger>
  </CodeBlockTabsList>

  <CodeBlockTab value="npm">
    ```bash
    npm install good-webhooks@beta zod
    ```
  </CodeBlockTab>

  <CodeBlockTab value="pnpm">
    ```bash
    pnpm add good-webhooks@beta zod
    ```
  </CodeBlockTab>

  <CodeBlockTab value="yarn">
    ```bash
    yarn add good-webhooks@beta zod
    ```
  </CodeBlockTab>

  <CodeBlockTab value="bun">
    ```bash
    bun add good-webhooks@beta zod
    ```
  </CodeBlockTab>
</CodeBlockTabs>

Skip the steps below and go to [receive webhooks](https://good-webhooks.dev/docs/guides/receive.md). `good-webhooks/verify` has no database dependency.

The `beta` tag installs the latest beta. Commit your lockfile. Payload schemas can use any [Standard Schema](https://standardschema.dev) library; these docs use Zod.

### Create a storage key [#create-a-storage-key]

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

```sh
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 [#create-the-tables]

Write the SQL to a migration file:

```sh
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`:

```sh
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](https://good-webhooks.dev/docs/operations/migrations.md).

### Create the client [#create-the-client]

```ts title="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:

```ts
await webhooks.check()
```

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

- [1. Publish events](https://good-webhooks.dev/docs/guides/publish.md): Define your event catalog and publish from your app.
- [2. Run a worker](https://good-webhooks.dev/docs/operations/workers.md): Start the process that sends and retries.
- [3. Manage endpoints](https://good-webhooks.dev/docs/guides/endpoints.md): Register where events go.
- [4. Receive webhooks](https://good-webhooks.dev/docs/guides/receive.md): Give your customers a verified receiver.

## Entry points [#entry-points]

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

| Import                              | Use it for                                                           |
| ----------------------------------- | -------------------------------------------------------------------- |
| `good-webhooks`                     | `createWebhooks()`: management and delivery together on PostgreSQL   |
| `good-webhooks/delivery`            | `createDelivery()`: the sender alone, fed by any `EndpointSource`    |
| `good-webhooks/management`          | The management contracts and `createManagement()` for custom storage |
| `good-webhooks/management/postgres` | Standalone endpoint management without delivery tables               |
| `good-webhooks/better-auth`         | The Better Auth server plugin and trusted management provider        |
| `good-webhooks/better-auth/client`  | The Better Auth client plugin for browsers                           |
| `good-webhooks/verify`              | `parseWebhook()` and `verifyWebhook()` for receivers                 |
| `good-webhooks/migrations`          | `getPostgresMigration()` 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.
