Skip to main content

Webhooks — receiving credit limits

After underwriting, Yumi sends a CREDIT_LIMIT webhook to the URL configured for you. This is how you learn each user's underwriting outcome — and, when approved, their credit limit.

Delivery

POST to your endpoint with headers:

HeaderValue
Content-Typeapplication/json
X-SignatureHex HMAC-SHA256 of the raw request body

Body (canonical JSON):

{
"eventType": "CREDIT_LIMIT",
"timestamp": 1721300000,
"data": {
"appName": "acme_credit",
"cardId": "1f7c...",
"userId": "b2e1...",
"externalUserId": "949",
"id": "c9a4...",
"creditLimit": 120.0,
"status": "issued",
"reviewReasons": [],
"currency": "USD",
"createdAt": 1721299990
}
}
data fieldMeaning
appNameYour partner id.
cardIdYumi card id.
userIdYumi internal card-user id.
externalUserIdYour id for the user.
idUnderwrite record id.
creditLimitApproved limit, in USD. 0 when status is declined or manual_review.
statusUnderwrite outcome: issued (limit granted), declined (rejected — limit 0), or manual_review (held for review — limit 0, may be issued later).
reviewReasonsReason codes for a declined / manual_review outcome; empty [] when issued.
currencyCurrency code.
createdAtUnix timestamp (seconds).

:::note Handle every outcome A CREDIT_LIMIT webhook fires for all underwrite outcomes, not only approvals. Branch on status: treat only issued as a spendable limit. declined and manual_review arrive with creditLimit: 0 — don't surface those as a real $0 limit. :::

Verify the signature

Compute the HMAC over the exact raw request body (before parsing), using the webhook secret Yumi issued you.

import crypto from 'crypto';

function verifyWebhook(rawBody, xSignatureHeader, webhookSecret) {
// X-Signature is a hex SHA-256 digest (64 chars). Reject anything malformed
// up front — timingSafeEqual throws if the two buffers differ in length.
if (typeof xSignatureHeader !== 'string' || !/^[0-9a-f]{64}$/i.test(xSignatureHeader)) {
return false;
}
const expected = crypto.createHmac('sha256', webhookSecret).update(rawBody).digest('hex');
const received = Buffer.from(xSignatureHeader, 'hex');
const expectedBuf = Buffer.from(expected, 'hex');
return received.length === expectedBuf.length && crypto.timingSafeEqual(received, expectedBuf);
}

:::warning Verify before you trust Always validate X-Signature before acting on a webhook. Read the raw body for the HMAC — re-serializing the parsed JSON can change the bytes and break verification. :::

Reliability

Respond 2xx quickly. Treat delivery as best-effort: if you ever miss an event, read the current value any time from GET /card/credit-limit (see API Reference).

Persist both status and creditLimit. Only authorize draws when status is issued — a declined or manual_review outcome arrives with creditLimit: 0 and must not be treated as an available limit.

Next: Credit lifecycle →