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
EventTriggered when
fraud.alertHigh-risk transaction detected
fraud.score.updatedAccount fraud score changes
credit.report.readyAsync credit report generation completes
verification.completedBVN/NIN verification finishes
report.readyFinancial report is ready
report.failedReport failed or consent link expired
webhook.testManual test from Settings → Controls

Event delivery

  1. Set a webhook URL in Settings → Controls (and copy the signing secret)
  2. Scrub POSTs JSON to your URL with X-Signature and X-Timestamp
  3. Return 2xx within 15 seconds
  4. 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)
  );
}
javascript

Financial report events

EventDescription
report.readyBank connection finished and the report is ready
report.failedReport generation failed or the consent link expired
webhook.testFired 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/..."
}
json

report.failed example

{
  "event": "report.failed",
  "case_id": "550e8400-e29b-41d4-a716-446655440000",
  "reference_id": "loan_app_9981",
  "status": "FAILED"
}
json

You can also poll GET /api/v1/report/{case_id}webhook_delivered_at is set after a successful delivery.

Best practices

  • Return 2xx quickly — process the payload asynchronously
  • Deduplicate on event + case_id
  • Rotate the signing secret periodically from Controls
  • Use Send test after saving your URL