Bank Statement · Webhooks
See Webhooks (global) → for verification, retries, idempotency.
Event catalog
| Event | Fires when | data keys |
|---|---|---|
analysis.uploaded | PDF accepted into queue | analysisId · filename · sha256 · externalRef |
analysis.parsed | Parser finished (before categorisation/scoring) | analysisId · detectedBank · transactionCount |
analysis.completed | Full pipeline done — status: completed | analysisId · status · detectedBank · totalInflow · transactionCount · confidenceScore |
analysis.requires_review | Completed but low-confidence; flagged for analyst | analysisId · confidenceScore · reasons |
analysis.failed | Parsing failed | analysisId · errorMessage · stage |
analysis.password_required | Encrypted PDF; password needed | analysisId |
categorization.corrected | User manually re-categorized a transaction | transactionId · categoryId · ruleCreated · bulkAffected |
anomaly.high | High-severity anomaly raised | analysisId · transactionId · type · severity · description |
Signature
Bank Statement uses the X-BankStatement-Signature header (legacy product prefix). Same HMAC-SHA256 scheme as the global webhook recipe.
X-BankStatement-Event: analysis.completed
X-BankStatement-Delivery: whd_01HXY...
X-BankStatement-Signature: sha256=8b2c4d7e...
X-BankStatement-Timestamp: 1716552000
X-BankStatement-Attempt: 1Example: analysis.completed
{
"id": "evt_01HXY...",
"event": "analysis.completed",
"occurredAt": "2026-05-24T08:14:22.481Z",
"orgId": "org_01HXY...",
"data": {
"analysisId": "ana_01HXY...",
"status": "completed",
"detectedBank": "BCA",
"transactionCount": 45,
"accountHolder": "Budi Santoso",
"currency": "IDR",
"totalInflow": 25000000,
"confidenceScore": 95,
"consolidationGroupId": "cgr_01HXY...",
"externalRef": "loan-app-2026-05-24-001"
}
}consolidationGroupId lets you correlate parallel statement parses for the same loan application. If you uploaded 5 PDFs, you'll get 5 analysis.completed events sharing the same consolidationGroupId — bucket them in your receiver and trigger your downstream pipeline once you've seen one per uploaded analysis. A first-class consolidation.completed event that fires once when the last sibling finishes is on the 2026 Q3 roadmap.
Example: anomaly.high
{
"id": "evt_01HXY...",
"event": "anomaly.high",
"occurredAt": "...",
"orgId": "...",
"data": {
"analysisId": "ana_01HXY...",
"transactionId": "txn_01HXY...",
"type": "doctored_row",
"severity": "high",
"description": "Row appears edited: balance-after column has different font metrics from surrounding rows."
}
}Subscribe to anomaly.high and surface them in your underwriting UI — they're the canonical signal of "you might be looking at a fabricated statement."
Register
/api/webhooks{
"url": "https://your-app.example.com/webhooks/bank-statement",
"events": ["analysis.completed", "analysis.requires_review", "analysis.failed", "anomaly.high"],
"active": true
}Response includes the signing secret (shown once):
{
"data": {
"id": "whk_01HXY...",
"url": "...",
"events": [...],
"secret": "qe_whsec_..."
}
}Delivery history
/api/webhooks/{id}/deliveriesReturns the last 200 deliveries with status, response code, attempt count, and signed-bytes hash. Use this for debugging delivery failures from your end.
Rotating the secret
/api/webhooks/{id}/rotate-secretReturns new secret. The old secret stays valid for a 24-hour grace window so you can roll the new value through your verifier without an outage.