Webhooks
Real-time notifications when a consumer grants, denies, or revokes consent. Configure your endpoint in the partner portal under Webhooks.
Events
session.grantedConsumer approved. Payload is ready to fetch.session.deniedConsumer denied. Session is terminal.session.expiredSession expired before the consumer acted.grant.revokedA previously granted scope was revoked by the consumer or partner.
Delivery
POST https://yourapp.com/webhooks/ucap
Content-Type: application/json
UCAP-Signature: t=1779371356,v1=8a4f...
{
"event": "session.granted",
"session_id": "ses_01HW9...",
"product_id": "prd_8a4f...",
"external_reference": "loan_app_42",
"occurred_at": "2026-05-21T12:33:11Z"
}We retry on any non-2xx response with exponential backoff (5s, 30s, 2m, 10m, 1h, 6h) for up to 24 hours. Three consecutive failures emails the partner owner.
Verifying signatures
Every webhook is signed with HMAC-SHA256 using your partner signing secret. Always verify before trusting the payload.
import { createHmac, timingSafeEqual } from "node:crypto";
export function verifyUcap(rawBody: string, header: string, secret: string) {
const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
const expected = createHmac("sha256", secret)
.update(`${parts.t}.${rawBody}`)
.digest("hex");
const a = Buffer.from(expected);
const b = Buffer.from(parts.v1);
if (a.length !== b.length) return false;
return timingSafeEqual(a, b) &&
Math.abs(Date.now() / 1000 - Number(parts.t)) < 300;
}