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 processedeventIds 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-sdk→webhook-handler.ts,webhook-sender.ts,signing.ts - Event registry (101 events): /reference/events