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