Good Webhooks

Manage endpoints

Register, update, pause, resume, rotate, and remove endpoints with the combined client or the standalone PostgreSQL provider.

An endpoint is a URL in one scope that subscribes to some of your event types. Endpoint management stores it with its status and its encrypted signing secret. To let signed-in users manage their own endpoints over HTTP, use the Better Auth plugin instead.

Register an endpoint

The webhooks client from installation has an endpoints API. The root client works in application scope; forScope() selects a customer's scope.

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

const customer = webhooks.forScope({ type: 'organization', id: organizationId })

const { endpoint, secret } = await customer.endpoints.create({
  url: 'https://receiver.example/webhooks',
  description: 'Invoice notifications',
  eventTypes: ['invoice.paid'],
})

Both need the storage encryption key from installation. Every process that reads these endpoints needs the same key.

Hand over the signing secret

create() returns the plaintext signing secret once; no read returns it again. Show it to the customer once, or send it over a secure channel. Their receiver passes it to parseWebhook().

Change an endpoint

  1. activeReceives new events and sends queued deliveries.
  2. pausedStill subscribed. Queued deliveries wait for resume().
  3. deletedPermanent. Secrets cleared, queued deliveries cancelled.
Endpoint status
await customer.endpoints.update(id, { eventTypes: ['invoice.created', 'invoice.paid'] })
await customer.endpoints.pause(id)
await customer.endpoints.resume(id)
const { secret } = await customer.endpoints.rotateSecret(id, { graceMs: 3_600_000 })
await customer.endpoints.remove(id)
MethodEffect
updateChanges the URL, description, or subscriptions. A new URL is checked again
pauseKeeps subscriptions and queued deliveries. The worker holds them until resume or expiry
resumeChecks the URL again, then queued deliveries send
rotateSecretReturns a new signing secret. Both sign requests during the overlap (default and max 24 h)
removePermanent. Clears stored secrets, cancels queued deliveries, and keeps a deleted record

These calls return as soon as the record changes. A request the worker was already sending can still arrive after a pause or remove. See delivery guarantees.

Limits

LimitValue
Live endpoints per scope1,000
Subscribed event types1 to 100 per endpoint, each one of your configured names
URLUp to 2,048 characters, HTTPS, no credentials, no unsafe IP literals
DescriptionUp to 2,000 characters

The standalone provider also resolves the URL's DNS at registration.

When a scope owner leaves

Standalone management doesn't know about your users. Call removeScope(scope) from your own deletion workflow to remove every endpoint in that scope:

import { createPostgresManagement } from 'good-webhooks/management/postgres'

const management = createPostgresManagement({ database: pool, eventTypes, encryptionKey })
await management.removeScope({ type: 'organization', id: organizationId })

The combined client doesn't expose removeScope. Either build the provider separately as above, with the same database, schema, and key, or create it first, pass it to createWebhooks({ management }), and keep a reference.

Next steps

On this page