Skip to content

Webhooks (HMAC-SHA256)

All ecosystem events flow over signed HTTPS webhooks. One signature recipe, one payload envelope, ~100 event types.

Signature: HMAC-SHA256 over the raw request body Header: X-Webhook-Signature: sha256=<hex>Timestamp header: X-Webhook-Timestamp (Unix seconds) Window: 5 minutes — reject outside Replay protection: Dedupe on eventId (UUID) for at least 24 hours

Payload envelope

ts
interface WebhookPayload {
  eventId: string        // UUID for idempotency
  eventType: string      // e.g., "payment.received"
  timestamp: string      // ISO 8601
  source: string         // Emitting app, e.g., "colectiva"
  organizationId: string // RBS org ID
  data: unknown          // Typed per eventType
}

Verify recipe

ts
import { createWebhookHandler } from '@r-bsoftware/ecosystem-sdk'

export const { POST, GET } = createWebhookHandler({
  app: 'plenura',
  secrets: {
    camino: process.env.CAMINO_WEBHOOK_SECRET!,
    colectiva: process.env.COLECTIVA_WEBHOOK_SECRET!,
    constanza: process.env.CONSTANZA_WEBHOOK_SECRET!,
  },
  handlers: {
    'payment.received': async (event, ctx) => { /* ... */ },
    'cfdi.stamped': async (event, ctx) => { /* ... */ },
  },
})

secrets is keyed by source app — a single verifier accepts signed events from every sender that has a secret configured. The handler verifies the signature, enforces the timestamp window, dedupes by eventId, routes by eventType, and responds 2xx only after your handler succeeds.

Send recipe

ts
import { createWebhookSender } from '@r-bsoftware/ecosystem-sdk'

const send = createWebhookSender({
  sourceApp: 'caracol',
  secrets: {
    constanza: process.env.CONSTANZA_WEBHOOK_SECRET!,
  },
})
await send('constanza', {
  eventType: 'sale.completed',
  data: { tenantId, orderId, total },
})

Gotchas

  • Raw body only. Signature is over bytes. If your framework parses JSON before you verify, you'll fail with mysterious 401s. Keep a raw body reader before any JSON middleware.
  • Clock skew. Reject requests outside the 5-minute window, but also NTP-sync your server — 1–2 minute skew causes intermittent rejections.
  • Idempotency on eventId. Retries are guaranteed. Store processed eventIds for 24h and return 2xx on duplicate delivery.
  • Never weaken the HMAC. No optional signatures, no "dev mode skip" outside of localhost. All 21 apps enforce this.

Source of truth

  • SDK source: @r-bsoftware/ecosystem-sdkwebhook-handler.ts, webhook-sender.ts, signing.ts
  • Event registry (101 events): /reference/events

Red Broom Software Ecosystem