Good Webhooks

Core

Compose endpoint management and PostgreSQL delivery.

The root entry point combines endpoint management and PostgreSQL delivery in one client.

import {
  createWebhooks,
  createDelivery,
  generateEncryptionKey,
  verifyWebhook,
  WebhookError,
} from 'good-webhooks'
import type {
  WebhookOptions,
  Webhooks,
  WebhookClient,
  EndpointInput,
  EndpointPatch,
  WebhookErrorCode,
} from 'good-webhooks'

The root also exports the shared types listed under other root exports. For the step-by-step setup, see installation.

createWebhooks

createWebhooks<const E extends EventDefinitions>(options: WebhookOptions<E>): Webhooks<E>
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() }),
  },
})

WebhookOptions<E> accepts every delivery option except source, plus these fields:

FieldTypeMeaning
encryptionKeystring | Uint8ArrayRequired for the default PostgreSQL management provider. Base64-encoded 32 bytes or a 32-byte array.
managementEndpointManagementOptional existing provider. Its source supplies recipients and current endpoint state.

Construction does not open connections, apply migrations, or start a worker.

  • Without management, the constructor creates standalone PostgreSQL management from database, schema, the configured event names, encryptionKey, and allowLocalhost. schema then applies to both the management and delivery tables.
  • With management, that provider keeps its own database, schema, encryption, and URL-validation configuration. Core options do not reconfigure it, and encryptionKey is unused.

Invalid options throw INVALID_CONFIG: a missing event map, a malformed key, a database without query() and connect(), or any delivery option out of range.

Returned client

The client exposes publish, deliveries, deliverySettings, and worker from the delivery engine, plus these members:

MemberBehavior
endpoints.create(input)Creates an endpoint and returns { endpoint, secret }.
endpoints.list()Returns all nondeleted endpoints in application scope.
endpoints.get(id)Returns one endpoint, including its deleted state.
endpoints.update(id, patch)Changes URL, description, or subscribed event names.
endpoints.pause(id)Records paused state.
endpoints.resume(id)Records active state after URL validation.
endpoints.remove(id)Records permanent deletion and clears stored signing secrets.
endpoints.rotateSecret(id, { graceMs? })Returns a new signing secret and the endpoint.
check()Checks the default management provider, if constructed here, then delivery schema compatibility.
forScope(scope)Returns the same publication, history, settings, and endpoint methods bound to one named scope.

endpoints

import { webhooks } from './webhooks.js'

const { endpoint, secret } = await webhooks.endpoints.create({
  url: 'https://receiver.example/webhooks',
  eventTypes: ['invoice.paid'],
})
await webhooks.endpoints.rotateSecret(endpoint.id, { graceMs: 60 * 60 * 1000 })

Endpoint methods follow the management contract and limits, including its errors. EndpointInput<E> and EndpointPatch<E> restrict subscriptions to event names in E. removeScope and reencrypt are direct provider operations, not members of this client. The endpoints guide covers the lifecycle.

forScope

import { webhooks } from './webhooks.js'

const customer = webhooks.forScope({ type: 'organization', id: 'org_123' })
await customer.publish({ type: 'invoice.paid', data: { invoiceId: 'inv_1', amount: 4200 } })
await customer.endpoints.list()

forScope validates and snapshots its scope, so later changes to the argument have no effect. An invalid scope throws INVALID_INPUT. It does not authorize a caller. See scopes.

The scoped client has no worker or check: those operate at installation level. WebhookClient<E> is its type.

check

import { webhooks } from './webhooks.js'

await webhooks.check() // Throws SCHEMA_MISMATCH if tables are missing or outdated.

check() never applies migrations. When a provider is injected, it does not call that provider's optional health checks. The host stays responsible for checking its management storage.

generateEncryptionKey

generateEncryptionKey(): string
import { generateEncryptionKey } from 'good-webhooks'

console.log(generateEncryptionKey()) // Store once in your secret manager.

Returns 32 cryptographically random bytes encoded as base64. The key is the storage encryption key for standalone management signing secrets. It is distinct from the endpoint's whsec_ signing secret. Keep it outside the database.

Other root exports

ExportDocumented at
createDeliveryDelivery entry point
verifyWebhookReceiver entry point
WebhookErrorErrors

parseWebhook is available only from good-webhooks/verify.

The root also exports these types:

GroupTypes
EventsEventDefinitions, EventName, JsonValue, PublishInput, PublishOptions, PublishResult
DatabaseSqlClient, DatabaseClient, Database
EndpointsEndpoint, EndpointStatus, EndpointWithSecret, EndpointInput, EndpointPatch
DeliveryDelivery, DeliveryDetail, DeliveryStatus, DeliveryQuery, Attempt, Page, WorkerResult, DeliveryOptions, DeliveryEngine
CompositionWebhookOptions, Webhooks, WebhookClient, EndpointManagement, EndpointSource, EndpointResolution, ManagementScope, Scope
ErrorsWebhookErrorCode

On this page