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:
| Header | Value |
|---|---|
Content-Type | application/json |
X-Signature | Hex 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 field | Meaning |
|---|---|
appName | Your partner id. |
cardId | Yumi card id. |
userId | Yumi internal card-user id. |
externalUserId | Your id for the user. |
id | Underwrite record id. |
creditLimit | Approved limit, in USD. 0 when status is declined or manual_review. |
status | Underwrite outcome: issued (limit granted), declined (rejected — limit 0), or manual_review (held for review — limit 0, may be issued later). |
reviewReasons | Reason codes for a declined / manual_review outcome; empty [] when issued. |
currency | Currency code. |
createdAt | Unix 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 →