Skip to main content

Webhooks

Open Saasframe provides a built-in webhook system compliant with the Standard Webhooks specification. The webhooks module enables:

  1. Outbound webhooks — push domain events to external HTTP endpoints
  2. Inbound webhooks — receive and verify webhooks from external systems via a generic adapter pattern

How It Works​

Outbound Flow​

When any module emits a domain event (e.g., customers.person.created, sales.order.fulfilled), the webhooks module:

  1. A persistent wildcard subscriber (*) catches all platform events
  2. Matches each event against registered webhook subscriptions (supports wildcards like customers.*)
  3. Enqueues delivery jobs to a dedicated webhook-deliveries queue
  4. A delivery worker signs the payload per Standard Webhooks spec and delivers it
  5. On failure, retries with exponential backoff (up to 10 attempts over ~24 hours)
Module emits event → Event Bus → Webhook Dispatcher → Delivery Queue → HTTP POST

Standard Webhooks Headers​

Every outbound delivery includes these headers per the specification:

webhook-id: msg_2KWPBgLlAfxdpx2AI54pPJ85f4W
webhook-timestamp: 1674087231
webhook-signature: v1,K5oZfzN95Z9UVu1EsfQmfVNQhnkZ2pj9o9NDN/H/pI4=
content-type: application/json

Signature format: v1,<base64_hmac_sha256> of {webhook-id}.{webhook-timestamp}.{body}

Payload Structure​

{
"type": "customers.person.created",
"timestamp": "2026-03-04T10:30:00.000Z",
"data": {
"id": "uuid-here",
"firstName": "John",
"lastName": "Doe",
"tenantId": "tenant-uuid",
"organizationId": "org-uuid"
}
}

Webhook Management​

Integration-level controls​

The module also registers a Custom Webhooks integration in the Integration Marketplace. That integration acts as the global control plane for the webhook subsystem:

  • The integration State switch enables or blocks outbound deliveries, retries, test sends, and inbound receives.
  • The integration Settings tab exposes quick links to /backend/webhooks, a create action, and the Notify admins on failed delivery setting.
  • The integration Logs tab shows an aggregated delivery log across all configured webhook endpoints.

This means webhook operations have two scopes:

  • Integration scope — global on/off and shared notification behavior.
  • Endpoint scope — per-webhook URL, event patterns, retries, activation state, and secret lifecycle.

Creating a Webhook​

Via API:

POST /api/webhooks
{
"name": "My Integration",
"url": "https://example.com/webhook",
"subscribedEvents": ["customers.*", "sales.order.created"],
"httpMethod": "POST"
}

The response includes a whsec_-prefixed signing secret. This is the only time the secret is returned in plaintext — store it securely.

Event Subscriptions​

Webhooks support pattern-based event subscriptions:

PatternMatches
customers.person.createdExact match only
customers.*All events in the customers namespace
*All platform events

API Endpoints​

MethodPathDescription
GET/api/webhooksList webhooks
POST/api/webhooksCreate webhook
GET/api/webhooks/:idGet webhook details
PUT/api/webhooks/:idUpdate webhook
DELETE/api/webhooks/:idSoft-delete webhook
POST/api/webhooks/:id/rotate-secretRotate signing secret
POST/api/webhooks/:id/testSend test delivery
GET/api/webhooks/deliveriesList delivery logs
POST/api/webhooks/deliveries/:id/retryRetry failed delivery
GET/api/webhooks/eventsList available event types

Verifying Webhooks (Consumer Guide)​

If you're building a service that receives webhooks from Open Saasframe, verify the signature to ensure authenticity:

import { createHmac, timingSafeEqual } from 'node:crypto'

function verifyWebhook(headers: Record<string, string>, body: string, secret: string): boolean {
const webhookId = headers['webhook-id']
const timestamp = headers['webhook-timestamp']
const signature = headers['webhook-signature']

// Check replay window (5 minutes)
const now = Math.floor(Date.now() / 1000)
if (Math.abs(now - parseInt(timestamp)) > 300) return false

// Verify signature
const toSign = `${webhookId}.${timestamp}.${body}`
const secretBytes = Buffer.from(secret.replace('whsec_', ''), 'base64')
const expected = createHmac('sha256', secretBytes).update(toSign).digest('base64')

// Extract signature value (after "v1,")
const received = signature.split(' ').find(s => s.startsWith('v1,'))?.slice(3)
if (!received) return false

return timingSafeEqual(Buffer.from(expected), Buffer.from(received))
}
tip

Use the official Standard Webhooks libraries for verification in production — they handle edge cases like key rotation and multiple signatures.

Retry Behavior​

Failed deliveries are retried with exponential backoff:

AttemptDelay
1Immediate
2~5 seconds
3~5 minutes
4~30 minutes
5~2 hours
6~5 hours
7~10 hours
8-10~14-24 hours

Non-retryable responses: HTTP 4xx (except 408 and 429) are not retried.

Auto-disable: After a configurable number of consecutive failures (default: 100), the webhook is automatically disabled. HTTP 410 (Gone) responses immediately disable the webhook.

Key Rotation​

Rotate webhook secrets without downtime:

POST /api/webhooks/:id/rotate-secret

During the rotation period (default: 24 hours):

  • Outbound deliveries are dual-signed with both the new and old secret
  • Consumers can verify with either secret
  • After the rotation period, the old secret is automatically removed

Secret Format​

Webhook secrets use the Standard Webhooks format:

  • Prefix: whsec_
  • Content: Base64-encoded random bytes (minimum 24 bytes / 192 bits)
  • Example: whsec_h4fk4iWceEsgYw/JbT9Feg98sgSFYsAy

Delivery Strategies​

StrategyUse CaseStatus
httpStandard HTTP/HTTPS endpoints (default)Available today
sqsAWS SQS queues for async processingPlanned
snsAWS SNS topics for fan-outPlanned

Inbound Webhooks​

The webhooks module provides a generic receiver for external webhook sources:

POST /api/webhooks/inbound/:endpointId

Third-party modules can register WebhookEndpointAdapter implementations to handle specific providers (e.g., Stripe, Zapier). The adapter contract:

interface WebhookEndpointAdapter {
readonly providerKey: string
readonly subscribedEvents: string[]
formatPayload(event): Promise<WebhookPayload>
verifyWebhook(input): Promise<InboundWebhookEvent>
processInbound(event): Promise<void>
}

Inbound webhooks are:

  • Rate limited per endpoint
  • Deduplicated by message ID
  • Processed via fire-and-forget (return 200 immediately, emit event for async processing)

Shared Primitives​

The webhook signing, verification, and secret management utilities are available as a shared library for use by any module:

import {
signPayload,
buildStandardHeaders,
verifyStandardWebhook,
generateWebhookSecret,
parseWebhookSecret,
} from '@saasframe/shared/lib/webhooks'

Admin UI​

The webhook management dashboard is available at /backend/webhooks with:

  • A webhook list with status, event patterns, last delivery, and quick actions
  • Create/edit forms with event selection, retry controls, timeout, rate limiting, and custom headers
  • One-time secret reveal after create or rotate
  • Per-endpoint detail pages with test sends, secret rotation, and delivery drilldown
  • Aggregated delivery visibility from the integration detail page at /backend/integrations/webhook_custom

Primary operator routes​

RoutePurpose
/backend/integrations/webhook_customGlobal webhook settings, failed-delivery notifications, configured endpoint shortcuts, aggregated delivery log
/backend/webhooksEndpoint list and create entry point
/backend/webhooks/createCreate a new webhook endpoint
/backend/webhooks/:idEndpoint detail, send test, rotate secret, inspect delivery history

Failed delivery notifications​

When Notify admins on failed delivery is enabled in the integration Settings tab, the module creates in-app notifications for admin-capable users after a delivery exhausts all retry attempts. This notification layer is independent from HTTP retry logic and is meant to surface operational issues early.

Secret lifecycle UX​

Secrets are generated automatically and revealed in plaintext only:

  • immediately after webhook creation
  • immediately after secret rotation

After that, the admin UI only shows a masked value. Operators must copy the secret during the reveal window or rotate it again later.

RBAC Features​

FeatureDescription
webhooks.viewView webhooks and delivery logs
webhooks.manageCreate, edit, and delete webhooks
webhooks.secretsView and rotate webhook secrets
webhooks.testSend test webhook deliveries