# Introduction

> Good Webhooks is a TypeScript library that adds outbound webhooks to your application, using your PostgreSQL database.

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



Good Webhooks lets your application send webhooks to your customers. You publish an event; it handles the rest: who should receive it, signing, retries, history, and replay. It runs inside your app and stores everything in your PostgreSQL database. There is no extra service to host.

The life of one event:

1. **Publish** (your app): `publish()` validates the payload and finds subscribed endpoints.
2. **Store** (PostgreSQL): The event and one delivery per endpoint commit with your data.
3. **Deliver** (worker): A worker signs each request, sends it, and retries failures.
4. **Verify** (receiver): The receiver checks the signature and gets a typed event.

## In code [#in-code]

**Publish**

```ts title="app.ts"
import { createWebhooks } from 'good-webhooks'
import { Pool } from 'pg'
import { z } from 'zod'

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

await webhooks.publish({
  type: 'invoice.paid',
  data: { invoiceId: 'inv_123', amount: 4200 }, // type-checked against the schema
})
```

**Deliver**

```ts title="worker.ts"
import { webhooks } from './app.js'

const stop = new AbortController()
process.once('SIGTERM', () => stop.abort())

// Sends due deliveries, retries failures, and stops cleanly on SIGTERM.
await webhooks.worker.run({ signal: stop.signal })
```

**Receive**

```ts title="receiver.ts"
import { parseWebhook } from 'good-webhooks/verify'
import { events } from './events.js' // the same event map the sender uses

const event = await parseWebhook({
  body: rawBody, // the request body exactly as received
  headers: request.headers,
  secret: process.env.WEBHOOK_SECRET!,
  events,
})
event.data.amount // number
```

## What it handles for you [#what-it-handles-for-you]

Most teams start with a `fetch()` in a request handler. Then a customer's server goes down and events are lost, a deploy sends duplicates, or a URL points at an internal service. Good Webhooks covers those cases:

| Good Webhooks handles                                  | You decide                                    |
| ------------------------------------------------------ | --------------------------------------------- |
| Endpoint storage, encrypted secrets, URL safety checks | Who may manage which endpoints                |
| Payload validation, idempotency, recipient selection   | When to publish, and in which transaction     |
| Signing, retries, attempt history, replay              | Where the worker runs and how it's supervised |
| Migration SQL and schema checks                        | When migrations run                           |
| Signature verification and typed parsing for receivers | What the receiver does with the event         |

Nothing starts on its own: creating the client opens no connections, runs no migrations, and starts no workers.

> **Note: Beta.** The public API is frozen except for correctness and security fixes. Pin the exact version and read
> the [stability policy](https://good-webhooks.dev/docs/operations/compatibility.md#stability-policy) and the [release
> notes](https://github.com/pplytas/good-webhooks/releases) before upgrading.

## Where to go next [#where-to-go-next]

- [Try it in five minutes](https://good-webhooks.dev/docs/quick-start.md): One file sends a signed webhook to a local receiver.
- [Add it to your app](https://good-webhooks.dev/docs/installation.md): Pick your setup and install what it needs.
- [Use Better Auth?](https://good-webhooks.dev/docs/better-auth.md): Let signed-in users manage their own endpoints.
- [Understand the model](https://good-webhooks.dev/docs/concepts/how-it-works.md): Management, delivery, scopes, and guarantees.
