Webhooks API
UCRM is both a webhook sender (outbound from journeys) and a webhook receiver (inbound from casino platforms + messaging providers).
Inbound — bonus events
POST /v1/adapters/:slug/webhooks/bonus-events
Casino platform → CRM. Lifecycle events for bonus_grants. See Adapter integration / Bonus flow for full schema.
Headers:
X-UCRM-Signature: <hex(hmac_sha256(secret, "<ts>.<body>"))>X-UCRM-Timestamp: <unix-seconds>(anti-replay; 5-min window)Content-Type: application/json
Body:
{
"event": "bonus.completed",
"event_id": "uuid", // idempotency key
"ts": "2026-05-08T12:00:00Z",
"grant_id": "uuid", // CRM's bonus_grants.id
"platform_bonus_id": "platform-side-id",
"data": { ... } // event-specific
}
Response:
200 { received: true, applied: true | false, new_status: "..." }— accepted (delivered or replay)401 FORBIDDEN— bad signature / timestamp out of window404 NOT_FOUND— unknown adapter slug or grant_id422 VALIDATION_ERROR— malformed payload
Inbound — KYC events
POST /v1/adapters/:slug/webhooks/kyc-events
Same auth scheme. Body:
{
"event": "kyc.approved", // submitted | approved | rejected | expired | level_upgraded
"event_id": "uuid",
"ts": "2026-05-08T12:00:00Z",
"player_external_id": "player_001", // resolved to UCRM player_id by lookup
"level": 2,
"data": { "reason": "...", "expires_at": "..." }
}
Important: rejected with 403 when tenant.kyc_model in {crm_owns, none} — those tenants own KYC themselves; UCRM doesn't accept inbound updates.
Inbound — provider delivery webhooks
Per-provider, scoped to messaging:
POST /v1/messaging-webhooks/postmark— Postmark email delivery / open / click / bouncePOST /v1/messaging-webhooks/twilio— Twilio SMS status callback- (FCM doesn't push delivery webhooks; client SDK reports
push_opened/push_clickedvia the regular/v1/eventspath.)
Each provider has its own signature scheme — see Adapter integration / Security.
Outbound — journey webhook nodes
Journeys can fire webhooks via the call_webhook node. See Journeys feature.
UCRM's outbound webhook is signed identically to the inbound scheme (HMAC-SHA256 over <timestamp>.<body>), so an operator who wires a UCRM-side endpoint as the journey target can use the same shared secret on both legs.
Idempotency ledger
webhook_events_processed is the dedupe table — (adapter_slug, event_id) PK. Replays are detected pre-route and return 200 { applied: false, replay: true } immediately.
Bonus reconciliation
If webhooks drop, pnpm --filter @ucrm/api reconcile:bonuses (daily cron) walks active grants + asks each adapter for the platform's current view via adapter.bonus.getBonusStatus(). Replays missing terminal transitions through the same state-sync pipeline.