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
- activeReceives new events and sends queued deliveries.
- pausedStill subscribed. Queued deliveries wait for
resume(). - deletedPermanent. Secrets cleared, queued deliveries cancelled.
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)| Method | Effect |
|---|---|
update | Changes the URL, description, or subscriptions. A new URL is checked again |
pause | Keeps subscriptions and queued deliveries. The worker holds them until resume or expiry |
resume | Checks the URL again, then queued deliveries send |
rotateSecret | Returns a new signing secret. Both sign requests during the overlap (default and max 24 h) |
remove | Permanent. 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
| Limit | Value |
|---|---|
| Live endpoints per scope | 1,000 |
| Subscribed event types | 1 to 100 per endpoint, each one of your configured names |
| URL | Up to 2,048 characters, HTTPS, no credentials, no unsafe IP literals |
| Description | Up 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.