Core Concepts
Webhooks
Scrub delivers asynchronous events to your server via HTTPS POST. Register your webhook URL in the dashboard under Settings → Controls.
When a financial report finishes (or fails), Scrub POSTs a signed JSON payload to that URL. Celery retries with exponential backoff if delivery fails.
End-to-end report flow (create → Link → statuses → webhook): Getting Started.
Scrub API→ POST webhook →Your Server→ 200 OK within 5s
| Event | Triggered when |
|---|---|
fraud.alert | High-risk transaction detected |
fraud.score.updated | Account fraud score changes |
credit.report.ready | Async credit report generation completes |
verification.completed | BVN/NIN verification finishes |
report.ready | Financial report is ready |
report.failed | Report failed or consent link expired |
webhook.test | Manual test from Settings → Controls |
Event delivery
- Set a webhook URL in Settings → Controls (and copy the signing secret)
- Scrub POSTs JSON to your URL with
X-SignatureandX-Timestamp - Return
2xxwithin 15 seconds - On failure, Scrub retries: 1m → 5m → 30m → 2h → 12h → 24h
Signature verification
Every webhook includes an X-Signature header — sha256= plus an HMAC-SHA256 of the raw body using your webhook secret:
const crypto = require('crypto');
function verifyWebhook(rawBody, signatureHeader, secret) {
const expected = crypto
.createHmac('sha256', secret)
.update(rawBody, 'utf8')
.digest('hex');
const expectedHeader = `sha256=${expected}`;
return crypto.timingSafeEqual(
Buffer.from(signatureHeader),
Buffer.from(expectedHeader)
);
}javascriptFinancial report events
| Event | Description |
|---|---|
report.ready | Bank connection finished and the report is ready |
report.failed | Report generation failed or the consent link expired |
webhook.test | Fired from Send test in Controls |
report.ready example
{
"event": "report.ready",
"case_id": "550e8400-e29b-41d4-a716-446655440000",
"reference_id": "loan_app_9981",
"status": "REPORT_READY",
"report": {
"score": 72,
"income_monthly_usd": 4200,
"income_monthly_local": 4200,
"affordability_ceiling_local": 1260,
"dti_ratio": 0.31
},
"report_pdf_url": "https://api.example.com/reports/..."
}jsonreport.failed example
{
"event": "report.failed",
"case_id": "550e8400-e29b-41d4-a716-446655440000",
"reference_id": "loan_app_9981",
"status": "FAILED"
}jsonYou can also poll GET /api/v1/report/{case_id} — webhook_delivered_at is set after a successful delivery.
Best practices
- Return
2xxquickly — process the payload asynchronously - Deduplicate on
event+case_id - Rotate the signing secret periodically from Controls
- Use Send test after saving your URL