@molecule/api-webhook
Core interface · webhook · API (Node) · v1.0.3 · Apache-2.0
Webhook core interface for molecule.dev
npm install @molecule/api-webhookHow it works
@molecule/api-webhook is the webhook core interface on the API (Node) side: the API your app calls, with no vendor inside.
Choose the implementation by bonding one of its 3 providers: @molecule/api-webhook-capture, @molecule/api-webhook-http, @molecule/api-webhook-queue.
import { setProvider, register, dispatch } from '@molecule/api-webhook'
import { createProvider } from '@molecule/api-webhook-http'
setProvider(createProvider()) // bond at startup
// SSRF guard: reject private/link-local/metadata destinations BEFORE registering.
if (!isAllowedWebhookUrl(url)) throw new Error('Destination not allowed')
const hook = await register(url, ['order.created']) // provider signs deliveries with hook.secret
await saveWebhookRow({ id: hook.id, userId: getUserId(res) }) // own it → scope list/delete by user
// Share hook.secret with the receiver so they can verify the signature header.
const results = await dispatch('order.created', { orderId: '123' })
console.log(results[0].success) // trueProviders (3): @molecule/api-webhook-capture, @molecule/api-webhook-http, @molecule/api-webhook-queue
Works with: @molecule/api-bond, @molecule/api-i18n
Reference
Webhook core interface for molecule.dev.
Defines the standard interface for webhook dispatch providers (HTTP, queue-backed, etc.).
Auto-generated, AI-first package reference for the molecule.dev ecosystem. It is written to be read by coding agents as much as by people, and is generated from this package's source — edit
src/index.tsJSDoc, not this file.
Quick Start
import { setProvider, register, dispatch } from '@molecule/api-webhook'
import { createProvider } from '@molecule/api-webhook-http'
setProvider(createProvider()) // bond at startup
// SSRF guard: reject private/link-local/metadata destinations BEFORE registering.
if (!isAllowedWebhookUrl(url)) throw new Error('Destination not allowed')
const hook = await register(url, ['order.created']) // provider signs deliveries with hook.secret
await saveWebhookRow({ id: hook.id, userId: getUserId(res) }) // own it → scope list/delete by user
// Share hook.secret with the receiver so they can verify the signature header.
const results = await dispatch('order.created', { orderId: '123' })
console.log(results[0].success) // true
Type
core
Installation
npm install @molecule/api-webhook @molecule/api-bond @molecule/api-i18n
API
Interfaces
WebhookOptions
Options for registering a webhook endpoint.
interface WebhookOptions {
/** Shared secret used to sign webhook payloads. Auto-generated if omitted. */
secret?: string
/** Custom HTTP headers to include with every delivery. */
headers?: Record<string, string>
/** Number of automatic retries on delivery failure (default: 3). */
retryCount?: number
}
WebhookRegistration
A registered webhook endpoint.
interface WebhookRegistration {
/** Provider-assigned webhook identifier. */
id: string
/** Destination URL that receives webhook payloads. */
url: string
/** Event types this webhook is subscribed to. */
events: string[]
/** Shared secret used to sign payloads for this webhook. */
secret: string
/** Whether this webhook is currently active. */
active: boolean
/** When this webhook was registered. */
createdAt: Date
}
WebhookDeliveryResult
Result of delivering a webhook payload to a single endpoint.
interface WebhookDeliveryResult {
/** The webhook this delivery was sent to. */
webhookId: string
/** Provider-assigned delivery attempt identifier. */
deliveryId: string
/** HTTP status code returned by the receiver. */
status: number
/** Whether the delivery was successful (2xx response). */
success: boolean
/** Round-trip delivery time in milliseconds. */
duration: number
}
WebhookDelivery
A recorded webhook delivery attempt.
interface WebhookDelivery {
/** Provider-assigned delivery identifier. */
id: string
/** The webhook this delivery belongs to. */
webhookId: string
/** The event type that triggered this delivery. */
event: string
/** The payload that was delivered. */
payload: unknown
/** HTTP status code returned by the receiver. */
status: number
/** Whether the delivery was successful (2xx response). */
success: boolean
/** Round-trip delivery time in milliseconds. */
duration: number
/** When this delivery attempt occurred. */
createdAt: Date
}
PaginationOptions
Pagination options for listing webhook deliveries.
interface PaginationOptions {
/** Maximum number of results to return. */
limit?: number
/** Number of results to skip. */
offset?: number
}
WebhookProvider
Webhook provider interface.
All webhook providers must implement this interface to provide registration, dispatch, delivery logging, and retry capabilities.
interface WebhookProvider {
/**
* Registers a new webhook endpoint to receive event notifications.
*
* @param url - Destination URL that will receive POST requests.
* @param events - Event types to subscribe to.
* @param options - Optional registration configuration.
* @returns The created webhook registration.
*/
register(url: string, events: string[], options?: WebhookOptions): Promise<WebhookRegistration>
/**
* Removes a webhook registration, stopping all future deliveries.
*
* @param webhookId - The webhook to unregister.
*/
unregister(webhookId: string): Promise<void>
/**
* Dispatches an event payload to all webhooks subscribed to the event.
*
* @param event - The event type being dispatched.
* @param payload - The event payload to deliver.
* @returns Delivery results for each matching webhook.
*/
dispatch(event: string, payload: unknown): Promise<WebhookDeliveryResult[]>
/**
* Lists all registered webhooks.
*
* @returns All webhook registrations.
*/
list(): Promise<WebhookRegistration[]>
/**
* Retrieves the delivery log for a specific webhook.
*
* @param webhookId - The webhook to retrieve deliveries for.
* @param options - Pagination options.
* @returns Delivery attempts for the webhook.
*/
getDeliveryLog(webhookId: string, options?: PaginationOptions): Promise<WebhookDelivery[]>
/**
* Retries a previously failed delivery attempt.
*
* @param deliveryId - The delivery attempt to retry.
* @returns The result of the retry attempt.
*/
retry(deliveryId: string): Promise<WebhookDeliveryResult>
}
Functions
setProvider(provider)
Registers a webhook provider as the active singleton. Called by bond packages during application startup.
function setProvider(provider: WebhookProvider): void
provider— The webhook provider implementation to bond.
getProvider()
Retrieves the bonded webhook provider, throwing if none is configured.
function getProvider(): WebhookProvider
Returns: The bonded webhook provider.
hasProvider()
Checks whether a webhook provider is currently bonded.
function hasProvider(): boolean
Returns: true if a webhook provider is bonded.
register(url, events, options)
Registers a new webhook endpoint to receive event notifications.
function register(
url: string,
events: string[],
options?: WebhookOptions,
): Promise<WebhookRegistration>
url— Destination URL that will receive POST requests.events— Event types to subscribe to.options— Optional registration configuration.
Returns: The created webhook registration.
unregister(webhookId)
Removes a webhook registration, stopping all future deliveries.
function unregister(webhookId: string): Promise<void>
webhookId— The webhook to unregister.
Returns: Resolves when the webhook has been unregistered.
dispatch(event, payload)
Dispatches an event payload to all webhooks subscribed to the event.
function dispatch(event: string, payload: unknown): Promise<WebhookDeliveryResult[]>
event— The event type being dispatched.payload— The event payload to deliver.
Returns: Delivery results for each matching webhook.
list()
Lists all registered webhooks.
function list(): Promise<WebhookRegistration[]>
Returns: All webhook registrations.
getDeliveryLog(webhookId, options)
Retrieves the delivery log for a specific webhook.
function getDeliveryLog(webhookId: string, options?: PaginationOptions): Promise<WebhookDelivery[]>
webhookId— The webhook to retrieve deliveries for.options— Pagination options.
Returns: Delivery attempts for the webhook.
retry(deliveryId)
Retries a previously failed delivery attempt.
function retry(deliveryId: string): Promise<WebhookDeliveryResult>
deliveryId— The delivery attempt to retry.
Returns: The result of the retry attempt.
Available Providers
| Provider | Package |
|---|---|
| Capture | @molecule/api-webhook-capture |
| HTTP | @molecule/api-webhook-http |
| Queue-backed | @molecule/api-webhook-queue |
Injection Notes
Requirements
Peer dependencies:
@molecule/api-bond^1.0.1@molecule/api-i18n^1.0.1
Runtime Dependencies
@molecule/api-bond@molecule/api-i18n
This is OUTBOUND webhook DISPATCH — your app POSTing events to endpoints your users
register. (Verifying an INBOUND provider webhook, e.g. Stripe's, is the opposite
direction — you verify ITS signature; see @molecule/api-payments.) A weak dispatch
integration is an SSRF hole and a spoofable firehose:
- The destination URL is untrusted → SSRF. A user-supplied
registerURL can point atlocalhost, a private range (10./192.168./127.), or the cloud metadata IP169.254.169.254. Validate + allowlist the scheme/host and BLOCK private/link-local targets BEFORE registering/dispatching, or a user can read internal services. - Deliveries are signed — share the secret so receivers verify. The provider signs
each payload with
WebhookRegistration.secret(fromWebhookOptions.secret, auto-generated if omitted). Give that secret to the receiver so they can check the HMAC header; an unverified webhook on the receiving end is spoofable. - Delivery is AT-LEAST-ONCE — receivers must dedup on the delivery id. Failed
deliveries are retried, and a receiver that processed an event but whose 2xx response was
lost gets the retry as an indistinguishable duplicate. The HMAC signature is NO help here
(identical payloads sign identically). Each delivery carries a stable id header
(
x-webhook-delivery-idon the HTTP bond) that is the SAME across all retries of one logical delivery and unique per new event — receivers dedup on it (process each id once). - Scope registrations to their owner. Persist the returned
WebhookRegistrationid with the owner'suser_idand scope list/delete by it — an unscoped list/delete is an IDOR. - Send only what the event needs. Never put secrets, tokens, or unrelated PII in the payload — it leaves your system.
E2E Tests
Integration checklist — drive the real UI (live preview, no mocks), adapt each item to this app's actual screens/flows, and check every box off one by one. A box you can't check is an integration bug to fix — not a skip:
- Registering a webhook endpoint through the app's UI/API succeeds, and
an event the app dispatches actually produces a delivery. The sandbox
CAPTURES outbound deliveries instead of POSTing — read them with the
read_activitytool (filter type 'webhook'); never mock the dispatch or modify production code to expose the payload. - The captured delivery carries the signature header (derived from the registration's secret), a stable delivery-id header the receiver can dedup on (at-least-once), and an event payload free of secrets/unrelated PII.
- A registration targeting a private/link-local/metadata destination
(
localhost,10.…,169.254.169.254) is REJECTED before any dispatch.