A real-time bank statement fraud alert system uses webhooks to push fraud scores and signal-level analysis to your application the moment a document is processed — typically within 3 seconds of upload. ClearStaq webhooks surface all 27 fraud signals in a structured JSON payload, enabling teams to route high-risk submissions to compliance queues, Slack channels, or case management systems automatically.
What you'll learn
- ClearStaq webhooks deliver bank statement fraud scores and all 27 individual fraud signals to your endpoint in under 3 seconds of document upload.
- Webhook-based fraud detection eliminates the decisioning gap created by batch processing, enabling instant application blocking during live borrower sessions.
- HMAC-SHA256 signature verification with timing-safe comparison and a 5-minute timestamp tolerance window is required to secure webhook endpoints against spoofing and replay attacks.
- Idempotent event processing using Redis SET NX on the event_id field prevents double-alerting when ClearStaq retries failed webhook deliveries.
- Signals in the document_manipulation and metadata_tampering categories should always trigger immediate escalation regardless of aggregate fraud score.
A real-time bank statement fraud alert system uses webhooks to push fraud scores and signal-level analysis to your application the moment a document is processed — typically within 3 seconds of upload. ClearStaq webhooks surface all 27 fraud signals in a structured JSON payload, enabling teams to route high-risk submissions to compliance queues, Slack channels, or case management systems automatically.
What Is a Real-Time Bank Statement Fraud Alert System?
A real-time bank statement fraud alert system is an event-driven architecture that pushes fraud scores to your application the instant a document is analyzed — not minutes or hours later. For a broader conceptual foundation, see our guide to real-time bank statement screening with webhooks before working through the steps below.
The teams who need this most are MCA lenders, fintech underwriters, and embedded finance platforms that process bank statements at volume. Manual review can't keep pace when dozens of applications arrive simultaneously — and a batch processing queue introduces exactly the kind of latency that fraudsters exploit.
The foundation of any live alerting system is the webhook pattern: a server-to-server HTTP POST that fires automatically whenever ClearStaq finishes analyzing a submission. Your endpoint receives the full fraud analysis in structured JSON, and your code decides what to do next — block the application, escalate to a compliance officer, or pass it through.
Why Lenders Can't Afford Delayed Fraud Detection
In MCA and alternative lending, funding decisions happen in minutes. A batch fraud detection job that runs every four hours isn't a safety net — it's a gap that fraudsters know how to step through. By the time the batch job fires, funds may already be disbursed.
The cost of a single fraudulent loan approval typically ranges from $15,000 to $150,000 in principal losses — plus recovery costs, legal fees, and compliance remediation. By contrast, a real-time detection API costs a fraction of that per submission. FFIEC guidance on real-time monitoring for financial institutions makes clear that financial institutions are expected to implement continuous, event-driven controls — not periodic batch reviews.
The math is straightforward. One prevented fraudulent approval pays for months of API usage.
How Webhooks Fit Into a Document Fraud Detection Stack
The flow is simple: an applicant uploads a bank statement → ClearStaq parses and scores the document → a webhook fires to your registered endpoint → your system routes the alert.
The alternative to webhooks is polling: your application repeatedly asks the API "are there any new results?" at fixed intervals. That approach introduces latency equal to your polling interval, wastes API calls on empty responses, and creates race conditions in high-volume flows. For a detailed architectural comparison, see our post on webhook vs. polling for bank statement processing.
ClearStaq fires the webhook within sub-3-second latency from upload to event delivery. For a live application flow where a borrower is waiting on-screen, that's the difference between catching fraud before a decision and catching it after.
Why Real-Time Fraud Alerts Beat Batch Processing
Batch processing introduces latency by design. Documents queue up, the job runs on a schedule, results land in an analyst's inbox hours later. In a live lending flow, that timeline is unusable. The borrower has long since closed the browser tab — or completed the application with fraudulent documents while your system was still waiting to run.
ClearStaq webhook delivery averages under 3 seconds from document upload to event delivery at your endpoint. That's not a marketing claim — it's an architectural consequence of scoring documents synchronously and firing the webhook immediately on completion, following the webhooks.fyi best practices reference for event-driven reliability.
Polling vs. Webhooks: The Performance Case
Polling works like repeatedly refreshing your email inbox to see if a message arrived. Every request consumes API rate limit quota, and the minimum latency is one full polling interval. Webhooks flip the model: ClearStaq pushes results to your endpoint the instant analysis completes — no requests wasted.
| Approach | Average latency to receive result | API calls consumed | Risk during high volume |
|---|---|---|---|
| Polling (every 30 seconds) | 15–30 seconds average | High (continuous) | Rate limit exhaustion |
| Polling (every 5 minutes) | 2.5 minutes average | Moderate | Missed decisioning window |
| ClearStaq Webhooks | ~2.8 seconds average | Zero (push delivery) | Minimal — retry logic built in |
Polling also artificially inflates your API usage. At 100 submissions per day with 30-second polling intervals, you're making roughly 2,880 poll requests per day to retrieve 100 results. Webhooks reduce that to 100 event deliveries — one per result.
What Real-Time Enables That Batch Cannot
Real-time scoring changes what's operationally possible during an active application session:
- Block fraudulent applications before funding decisions are made — not after disbursement.
- Trigger step-up verification while the applicant is still in session — request additional documents without a separate outreach workflow.
- Feed fraud signals into a live underwriting dashboard for same-session analyst review.
- Fire automated Slack or PagerDuty alerts to fraud analysts with full signal context at the moment of detection.
None of these are possible when results arrive in a batch queue. The decisioning window that real-time scoring opens is only available if your detection infrastructure fires within seconds of document submission.
Step 1: Register Your Webhook Endpoint with ClearStaq
Before you can receive fraud alerts, you need to register a publicly reachable HTTPS endpoint with ClearStaq. You'll need your API key from the ClearStaq API documentation, a stable HTTPS URL, and ngrok if you're testing locally.
Register your endpoint with a POST request to the ClearStaq webhook registration endpoint:
curl -X POST https://api.clearstaq.com/v1/webhooks \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://your-app.com/webhooks/clearstaq",
"events": ["fraud.alert.high", "fraud.alert.medium", "fraud.alert.review", "statement.processed"],
"description": "Production fraud alert receiver"
}'
The events array lets you subscribe selectively. If you only want to act on high-severity submissions, subscribe to fraud.alert.high alone. For a full compliance queue that captures everything, include all four event types.
Your endpoint must return HTTP 200 within 5 seconds of receiving a delivery. If it doesn't, ClearStaq marks the delivery as failed and begins the retry schedule. Return 200 first — do processing after.
Setting Up a Local Test Endpoint with ngrok
During development, your localhost isn't reachable by ClearStaq's servers. ngrok solves this by creating a public HTTPS tunnel to your local port. See the ngrok documentation for installation instructions, then run:
ngrok http 3000
ngrok will output a forwarding URL like https://a1b2c3d4.ngrok.io. Use that URL as your webhook endpoint during registration. Important: ngrok URLs change every time you restart the process. Use a stable domain for production — ngrok is for development only.
Configuring Fraud Score Thresholds at the API Level
ClearStaq lets you configure high, medium, and low score thresholds at the API level. You don't need to build threshold logic inside your webhook handler — that work is done before the event fires.
curl -X PATCH https://api.clearstaq.com/v1/webhooks/{webhook_id}/thresholds \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"high_threshold": 80,
"medium_threshold": 50,
"low_threshold": 0
}'
With this configuration, only submissions scoring above 80 fire a fraud.alert.high event. Submissions between 50 and 79 fire fraud.alert.medium. Your receiver code stays clean — it routes by severity field, not by computing thresholds from a raw score.
This is a meaningful architectural difference. Most teams building on generic webhook platforms have to replicate threshold logic in every consumer. ClearStaq handles it centrally.
{
"status": "success",
"fraud_score": 57,
"transactions": 47,
"bank": "Chase",
"processing_time_ms": 238
}Step 2: Understand the Fraud Alert Payload Structure
The payload is where the real value lives. ClearStaq doesn't just send a score — it surfaces every fraud signal that fired, with individual confidence scores and signal categories. That granularity is what enables surgical routing logic.
Here's a complete fraud.alert.high payload with realistic values:
{
"event_id": "evt_01H9K2M7X3QRWZ8NP4YT6JBLSD",
"event_type": "fraud.alert.high",
"timestamp": "2026-03-15T14:23:07.841Z",
"submission_id": "sub_01H9K1A4R2MNBZ5QP7YTJD8CXE",
"fraud_score": 87,
"severity": "high",
"bank_name": "Chase",
"statement_period": "2026-01-01/2026-01-31",
"format_id": "chase_pdf_v3",
"page_count": 4,
"signals_fired": [
{
"signal_name": "pdf_metadata_altered",
"signal_category": "metadata_tampering",
"confidence": 0.96,
"description": "Document creation and modification timestamps are inconsistent with stated statement period"
},
{
"signal_name": "round_number_deposit_pattern",
"signal_category": "pattern_anomaly",
"confidence": 0.89,
"description": "14 of 17 deposits are exact round numbers — statistically anomalous for organic transaction behavior"
},
{
"signal_name": "duplicate_transaction_sequence",
"signal_category": "document_manipulation",
"confidence": 0.91,
"description": "Transaction on 2026-01-12 appears with identical amount and description on 2026-01-14"
}
]
}
Let's walk through the key fields. The event_id is your idempotency key — store it and deduplicate against it (covered in Step 6). The submission_id is the identifier that correlates this fraud alert to the loan application record in your own system. The timestamp is ISO 8601 UTC — store it as part of your audit trail.
The signals_fired array is what makes ClearStaq's payload genuinely actionable. For the full reference on all 27 fraud signals ClearStaq detects, see the complete signal guide. The array here contains three signals, each with a signal_category and a confidence score between 0 and 1. Those two fields drive your routing decisions in Step 4.
Anatomy of a ClearStaq Fraud Alert Payload
A few fields deserve special attention:
event_id— Unique per event delivery, not per submission. Retries send the sameevent_id. Use this field as your deduplication key, notsubmission_id.submission_id— Unique per document submission. Use this to look up the associated loan application in your database.format_id— Identifies which of ClearStaq's 900+ bank format parsers processed the document. Useful for debugging edge cases.fraud_score— Aggregate score from 0 to 100. High scores reflect multiple high-confidence signals firing simultaneously.severity— Pre-computed tier based on your configured thresholds. Route by this field directly — don't re-compute tiers from the raw score.
Key Fraud Signals to Watch in the Payload
Three signals appear frequently in high-severity payloads and warrant special handling:
pdf_metadata_altered fires when a document's creation, modification, or author metadata is inconsistent with what a genuine bank statement would contain. This is a strong indicator of document fabrication. See our deep dive on PDF metadata fraud signals to understand what the metadata analysis catches and why it's so reliable.
round_number_deposit_pattern fires when deposits cluster at suspiciously round values. Genuine payroll and business deposits distribute unevenly across decimal values — a statement where 80% of deposits end in .00 is statistically anomalous. Read the full explanation of round-number deposit patterns to understand the statistical baseline ClearStaq uses.
duplicate_transaction_sequence fires when the same transaction appears more than once within a statement period. Fraudsters manufacturing income often copy-paste transaction rows, leaving identical amounts and descriptions on different dates.
Use the signal_category field to distinguish document-level fraud from behavioral pattern fraud. High-confidence signals above 0.90 should trigger immediate escalation. Signals between 0.70 and 0.89 warrant review queue placement, not automatic block.
Step 3: Verify Webhook Signatures for Security
Signature verification is non-negotiable. Without it, any attacker who knows your webhook endpoint URL can POST fabricated fraud alerts — or, more dangerously, fabricated clean-statement events that suppress real alerts. Every incoming request must be verified before you act on it.
ClearStaq signs every webhook delivery using HMAC-SHA256. The signature appears in the Clearstaq-Signature header as a hex digest. The verification algorithm is: extract the raw request body → compute HMAC using your signing secret → compare to the header value using a timing-safe comparison.
After implementing verification, review our guide to securing financial API webhooks for deeper coverage of TLS pinning, PII handling, and audit logging requirements for bank statement data.
HMAC Signature Verification: Node.js Implementation
import crypto from 'crypto';
import express from 'express';
const app = express();
// Must use raw body for HMAC verification — do NOT use express.json() here
app.use('/webhooks/clearstaq', express.raw({ type: 'application/json' }));
app.post('/webhooks/clearstaq', async (req, res) => {
const signature = req.headers['clearstaq-signature'];
const timestamp = req.headers['clearstaq-timestamp'];
if (!signature || !timestamp) {
return res.status(401).send('Missing signature headers');
}
// Reject events older than 5 minutes to prevent replay attacks
const eventAge = Math.abs(Date.now() / 1000 - parseInt(timestamp, 10));
if (eventAge > 300) {
return res.status(401).send('Event timestamp out of tolerance');
}
// Compute expected signature
const signedPayload = `${timestamp}.${req.body.toString()}`;
const expectedSig = crypto
.createHmac('sha256', process.env.CLEARSTAQ_WEBHOOK_SECRET)
.update(signedPayload, 'utf8')
.digest('hex');
// Timing-safe comparison prevents timing attacks
const sigBuffer = Buffer.from(signature);
const expectedBuffer = Buffer.from(expectedSig);
if (sigBuffer.length !== expectedBuffer.length ||
!crypto.timingSafeEqual(sigBuffer, expectedBuffer)) {
return res.status(401).send('Invalid signature');
}
// Signature verified — parse and process
const event = JSON.parse(req.body.toString());
// Return 200 immediately, process asynchronously
res.status(200).send('OK');
await processWebhookEvent(event);
});
Two details matter here. First, use express.raw() — not express.json() — for the webhook route. HMAC is computed against the raw bytes of the request body. Parsing JSON first mutates whitespace and key ordering, breaking the signature. Second, use crypto.timingSafeEqual() for the comparison. A standard string equality check leaks timing information that attackers can exploit to brute-force the signing secret.
The timestamp tolerance check prevents replay attack prevention — an attacker intercepting a valid signed payload can't replay it hours later if your handler rejects events outside a 5-minute window.
HMAC Signature Verification: Python FastAPI Implementation
import hmac
import hashlib
import time
from fastapi import FastAPI, Request, HTTPException
app = FastAPI()
CLEARSTAQ_WEBHOOK_SECRET = os.environ["CLEARSTAQ_WEBHOOK_SECRET"]
@app.post("/webhooks/clearstaq")
async def handle_clearstaq_webhook(request: Request):
raw_body = await request.body()
signature = request.headers.get("clearstaq-signature")
timestamp = request.headers.get("clearstaq-timestamp")
if not signature or not timestamp:
raise HTTPException(status_code=401, detail="Missing signature headers")
# Replay attack prevention — reject events older than 5 minutes
event_age = abs(time.time() - int(timestamp))
if event_age > 300:
raise HTTPException(status_code=401, detail="Event timestamp out of tolerance")
# Compute expected HMAC
signed_payload = f"{timestamp}.{raw_body.decode('utf-8')}"
expected_sig = hmac.new(
CLEARSTAQ_WEBHOOK_SECRET.encode("utf-8"),
signed_payload.encode("utf-8"),
hashlib.sha256
).hexdigest()
# Timing-safe comparison
if not hmac.compare_digest(signature, expected_sig):
raise HTTPException(status_code=401, detail="Invalid signature")
# Parse and process
import json
event = json.loads(raw_body)
# Process asynchronously in background task
# background_tasks.add_task(process_event, event)
return {"status": "accepted"}
The Python implementation mirrors the Node.js version in structure. Use hmac.compare_digest() — not == — for the signature comparison. The 5-minute tolerance window (abs(time.time() - int(timestamp)) > 300) is the replay prevention check. Events outside that window are rejected with a 401 before any processing occurs.
Step 4: Parse Fraud Signals and Set Alert Thresholds
Once you've verified the signature, the next step is routing. You have a verified payload with a severity field, a fraud_score, and a signals_fired array. The routing logic converts those into an action: block the application, send it to a review queue, or let it pass.
The key architectural principle: route by severity and signal_category — not by raw score. Certain signals should always trigger immediate escalation regardless of aggregate score. A single pdf_metadata_altered signal at 0.96 confidence is a stronger fraud indicator than a broad score of 75 with no high-confidence individual signals.
Building a Severity Router in Node.js
// Signals that always escalate, regardless of aggregate severity
const ALWAYS_ESCALATE = ['pdf_metadata_altered', 'identity_mismatch', 'digital_signature_invalid'];
function routeByFraudPayload(event) {
const { severity, fraud_score, signals_fired } = event;
// Check for always-escalate signals first
const hasEscalatingSignal = signals_fired.some(signal =>
ALWAYS_ESCALATE.includes(signal.signal_name)
);
if (hasEscalatingSignal) {
return {
action: 'block',
reason: 'escalating_signal_detected',
topSignals: getTopSignals(signals_fired, 3)
};
}
// Route by pre-computed severity tier
switch (severity) {
case 'high':
return { action: 'block', reason: 'high_severity', topSignals: getTopSignals(signals_fired, 3) };
case 'medium':
return { action: 'review', reason: 'medium_severity', topSignals: getTopSignals(signals_fired, 3) };
case 'low':
default:
return { action: 'pass', reason: 'low_severity', topSignals: [] };
}
}
function getTopSignals(signals, n) {
return [...signals]
.sort((a, b) => b.confidence - a.confidence)
.slice(0, n)
.map(s => ({ name: s.signal_name, confidence: s.confidence }));
}
// Usage
const routingDecision = routeByFraudPayload(event);
// { action: 'block', reason: 'escalating_signal_detected', topSignals: [...] }
This function is pure — it takes a payload object and returns an action object with no side effects. That makes it unit-testable in isolation. Wire it up to your Slack dispatcher, case management creator, and application blocking logic in the next layer.
Mapping Signal Categories to Alert Severity Tiers
Use this table to configure your routing rules. These mappings reflect the inherent severity of each signal category — document manipulation signals are always high because they indicate intentional forgery, while behavioral patterns may represent honest mistakes.
| Signal Category | Recommended Severity Tier | Suggested Action |
|---|---|---|
document_manipulation |
Always High | Block application and escalate immediately |
metadata_tampering |
Always High | Block application and escalate immediately |
pattern_anomaly |
Score-dependent | Review queue if fraud_score > 60 |
behavioral_pattern |
Score-dependent | Review queue if fraud_score > 70 |
identity_inconsistency |
Always High | Block application and request identity verification |
No competitor tutorial provides this mapping for bank statement fraud specifically. Use it as your starting configuration, then tune thresholds based on your observed false positive rate after going live.
See ClearStaq's Fraud Detection in Action
Upload a bank statement and get a real-time fraud analysis with 27 individual signal scores — delivered to your endpoint in under 3 seconds. Start your free trial and test the full webhook flow in sandbox, no credit card required.
Step 5: Route Alerts to Slack, Email, or Your Case Management System
Your routing function returns an action. Now you need to execute it. Here are three concrete downstream integrations — with working code — that cover the most common alert destinations for fraud operations teams.
Sending a Slack Block Kit Alert with Fraud Signal Details
Slack's Block Kit format lets you send structured, visually distinct messages with conditional formatting. Use severity-based emoji in the header block so analysts can triage at a glance.
async function sendSlackFraudAlert(event, routingDecision) {
const { fraud_score, severity, bank_name, submission_id, signals_fired } = event;
const severityEmoji = { high: '🔴', medium: '🟡', low: '🟢' }[severity] || '⚪';
const signalList = routingDecision.topSignals
.map(s => `• \`${s.name}\` — confidence: ${(s.confidence * 100).toFixed(0)}%`)
.join('\n');
const payload = {
blocks: [
{
type: 'header',
text: {
type: 'plain_text',
text: `${severityEmoji} Bank Statement Fraud Alert — ${severity.toUpperCase()}`
}
},
{
type: 'section',
fields: [
{ type: 'mrkdwn', text: `*Fraud Score:*\n${fraud_score}/100` },
{ type: 'mrkdwn', text: `*Bank:*\n${bank_name}` },
{ type: 'mrkdwn', text: `*Action:*\n${routingDecision.action.toUpperCase()}` },
{ type: 'mrkdwn', text: `*Submission ID:*\n\`${submission_id}\`` }
]
},
{
type: 'section',
text: {
type: 'mrkdwn',
text: `*Top Signals Fired:*\n${signalList}`
}
},
{
type: 'actions',
elements: [
{
type: 'button',
text: { type: 'plain_text', text: 'View in ClearStaq' },
url: `https://app.clearstaq.com/submissions/${submission_id}`,
style: 'danger'
}
]
}
]
};
await fetch(process.env.SLACK_WEBHOOK_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
});
}
The Block Kit structure separates the alert header, score fields, signal list, and the direct link to the ClearStaq dashboard for immediate investigation. The 🔴/🟡/🟢 emoji in the header lets analysts scan a busy Slack channel and immediately identify severity — no reading required.
Creating a Case Management Record Automatically
For compliance workflows, you'll want a fraud review case created automatically in your case management system. Map ClearStaq payload fields to your system's API fields:
async function createFraudCase(event, routingDecision) {
const casePayload = {
reference_id: event.submission_id,
title: `Bank Statement Fraud Review — Score ${event.fraud_score}`,
priority: event.severity, // 'high' | 'medium' | 'low'
risk_score: event.fraud_score,
bank_name: event.bank_name,
statement_period: event.statement_period,
evidence_items: event.signals_fired.map(signal => ({
type: signal.signal_category,
description: signal.description,
confidence: signal.confidence
})),
action_required: routingDecision.action,
detected_at: event.timestamp,
source: 'clearstaq_webhook'
};
try {
const response = await fetch(`${process.env.CASE_MGMT_API}/cases`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.CASE_MGMT_API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(casePayload)
});
if (!response.ok) {
// Log to DLQ — do not propagate error to webhook handler
await logToDeadLetterQueue({ event, error: await response.text() });
}
} catch (err) {
// Case API is down — log to DLQ, but webhook receiver still returns 200
await logToDeadLetterQueue({ event, error: err.message });
}
}
Notice the error handling pattern: if the case management API is unavailable, you log to a dead letter queue but do not throw an error back to the webhook handler. The webhook handler must return 200 to ClearStaq regardless of downstream failures — otherwise ClearStaq retries, thinking your endpoint failed. Downstream reliability is your responsibility to manage separately.
For high-severity events during off-hours, consider adding PagerDuty integration alongside the case creation. The severity === 'high' path should always wake someone up — case creation alone isn't urgent enough.
Step 6: Handle Retries and Idempotency
ClearStaq uses at-least-once delivery semantics for webhook events — the same guarantee that powers robust event-driven systems in building a bank statement processing pipeline. At-least-once means you will receive every event, but you may receive some events more than once. Your handler must be idempotent: processing the same event twice must produce the same outcome as processing it once.
The retry schedule is: first retry after 30 seconds, then 2 minutes, 5 minutes, 15 minutes, and 30 minutes. That's five delivery attempts over roughly 53 minutes before an event is marked as permanently failed.
Implementing Idempotent Event Processing
Use Redis with a SET NX (set if not exists) command to deduplicate incoming events by event_id:
import { createClient } from 'redis';
const redis = createClient({ url: process.env.REDIS_URL });
await redis.connect();
async function processWebhookEvent(event) {
const idempotencyKey = `webhook:event:${event.event_id}`;
// NX = only set if key doesn't exist; EX 86400 = expire after 24 hours
const isNew = await redis.set(idempotencyKey, '1', { NX: true, EX: 86400 });
if (!isNew) {
// Already processed — return without reprocessing
console.log(`Duplicate event received: ${event.event_id} — skipping`);
return;
}
// First time seeing this event_id — process it
const routingDecision = routeByFraudPayload(event);
if (routingDecision.action === 'block') {
await Promise.all([
sendSlackFraudAlert(event, routingDecision),
createFraudCase(event, routingDecision)
]);
} else if (routingDecision.action === 'review') {
await sendSlackFraudAlert(event, routingDecision);
}
}
The Redis key expires after 24 hours. This handles the scenario where an applicant resubmits a corrected document: the new submission generates a new submission_id and a new event_id, so it passes through the idempotency check correctly. Deduplication is keyed on event_id, not submission_id — both events from the same applicant should be processed independently.
Always return HTTP 200 even for duplicate events. A non-200 response tells ClearStaq your endpoint failed, triggering another retry — which you'll also need to deduplicate.
Dead Letter Queue for Failed Webhook Deliveries
Events that exhaust all five retry attempts are marked as permanently failed. Without a dead letter queue (DLQ), those events — and their fraud signals — disappear. For compliance systems that require a complete audit trail, that's unacceptable.
Create a DLQ table in PostgreSQL to capture failed deliveries:
CREATE TABLE webhook_dead_letter_queue (
id SERIAL PRIMARY KEY,
event_id TEXT NOT NULL UNIQUE,
event_type TEXT NOT NULL,
payload JSONB NOT NULL,
failure_reason TEXT,
retry_count INTEGER DEFAULT 0,
created_at TIMESTAMPTZ DEFAULT NOW(),
processed_at TIMESTAMPTZ,
resolved BOOLEAN DEFAULT FALSE
);
Monitor the ClearStaq webhook delivery status endpoint to detect events that reached the DLQ, or subscribe to the webhook.delivery.failed callback event. Build a cron job that processes unresolved DLQ records during recovery windows. This guarantees compliance systems receive all fraud alerts even after infrastructure outages — a hard requirement for SOC2-audited lending operations.
Step 7: Test Your Monitoring System in Sandbox
ClearStaq's sandbox environment lets you inject realistic fraud scenarios without real PII. Run every code path before going live — the retry logic, the idempotency check, and each downstream integration.
Trigger a sandbox fraud event with a POST to the injection endpoint:
curl -X POST https://api.clearstaq.com/v1/sandbox/inject \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"scenario_type": "all_signals_fired",
"webhook_url": "https://a1b2c3d4.ngrok.io/webhooks/clearstaq",
"fraud_score_override": 92,
"severity_override": "high"
}'
Available scenario types: metadata_tampered, round_deposits_only, duplicate_transactions, all_signals_fired, clean_statement.
Running End-to-End Fraud Alert Tests
Work through this checklist before marking the integration as production-ready:
- Trigger a
fraud.alert.highsandbox event and confirm your endpoint receives it. - Verify that HMAC signature validation passes without errors.
- Confirm the severity router returns
action: 'block'for the high-severity payload. - Verify a Slack alert fires to your test channel with correct score, bank name, and top signals.
- Verify a case management record is created with the correct priority and evidence items.
- Trigger the same event a second time and confirm the idempotency check blocks reprocessing.
- Trigger a
clean_statementevent and confirm no alert fires andaction: 'pass'is returned. - Trigger a
fraud.alert.mediumevent and confirm it routes to the review queue, not block.
Use ngrok's request inspector at http://localhost:4040 to view the raw payload for each delivery. This lets you verify the exact JSON structure before writing your parsing code.
Simulating Infrastructure Failures to Test Retry Logic
Temporarily modify your endpoint to return HTTP 500, then trigger a sandbox event. Confirm ClearStaq begins retrying according to the documented schedule. Watch your ngrok inspector for the retry deliveries at 30 seconds, 2 minutes, and 5 minutes.
When you restore your endpoint to return 200, verify two things: the event is processed exactly once (not re-processed for each retry that already fired), and the idempotency store correctly blocks the duplicate when the retry arrives after recovery. Log the delivery timestamps from the Clearstaq-Timestamp header on each retry to confirm exponential backoff behavior matches the documented schedule.
Putting It All Together: A Complete Live Monitoring Architecture
Here's the full system in one picture: ClearStaq API → webhook delivery → HTTPS receiver endpoint → HMAC signature verification → idempotency check → severity router → [Slack | Email | Case Management] + Dead Letter Queue for any delivery failures.
This architecture is where the fraud alert webhook connects to the broader workflow described in building a bank statement processing pipeline. The fraud scoring event is downstream of the parsing pipeline — ClearStaq processes the raw PDF, normalizes it against one of 900+ supported bank formats, runs all 27 signal checks, and then fires the webhook with structured results. Your receiver code works identically whether the source document was a Chase PDF or a regional credit union statement.
For teams that need to extend beyond the default 27 signals — adding industry-specific patterns or custom threshold logic — see custom fraud rules with ClearStaq for the machine learning pipeline integration options.
Infrastructure Checklist Before Going Live
- HTTPS endpoint with a valid TLS certificate on a stable domain (not an ngrok URL).
- HMAC signature verification implemented and tested with both valid and invalid signatures.
- Idempotency store — Redis SET NX or a unique constraint on
event_idin your database. - Dead letter queue configured, monitored, and recoverable via cron job.
- At least one downstream alert channel — Slack, email, or case management — tested in sandbox end-to-end.
- Fraud score thresholds configured in ClearStaq API settings for your specific risk tolerance.
- Receiver endpoint monitoring — know immediately when your own endpoint goes down, not when ClearStaq tells you deliveries are failing.
Scaling to High Statement Volume
The right architecture depends on your statement volume:
- Under 100 statements/day — synchronous handler is fine. Process inline, return 200. No queue needed.
- 100–1,000 statements/day — add async processing. Acknowledge the webhook immediately with 200, push the payload to an internal queue (Redis list or SQS), and process asynchronously in worker threads.
- Over 1,000 statements/day — dedicated webhook receiver service with a message queue (SQS, Google Pub/Sub) and multiple consumer workers for alert dispatch. Auto-scale consumer workers based on queue depth.
Note: ClearStaq's sub-3-second scoring latency means the performance bottleneck is your receiver, not the API. Invest in receiver reliability and idempotency infrastructure first. ClearStaq's side of the architecture is already scaled.
Frequently Asked Questions
What is a real-time fraud detection API?
A real-time fraud detection API analyzes financial documents and returns fraud scores and signal-level results within seconds of submission. Unlike batch systems that process documents on a schedule, a real-time API like ClearStaq fires a webhook to your application the moment analysis completes — typically within 3 seconds of upload — enabling instant decisioning during live application flows.
How do webhooks work in fraud detection systems?
Webhooks allow a fraud detection API to push results to your system the instant a document is analyzed, rather than requiring your application to poll for updates. When ClearStaq finishes scoring a bank statement, it sends an HTTP POST to your registered endpoint with a signed JSON payload containing the fraud score, severity tier, and all triggered fraud signals — no polling required.
What is the difference between polling and webhooks for fraud alerts?
Polling requires your system to repeatedly ask the API for results at set intervals, introducing latency proportional to the polling frequency and consuming unnecessary API calls. Webhooks push results to your endpoint the moment they are ready — ClearStaq delivers bank statement fraud alert webhooks in under 3 seconds, compared to minutes or hours of latency with typical 30-second to 5-minute polling intervals.
What fraud signals should trigger an immediate bank statement fraud alert?
Signals in the document_manipulation and metadata_tampering categories — such as altered PDF metadata, pixel-level image manipulation, and identity field mismatches — should always trigger immediate high-severity alerts regardless of aggregate score. Pattern-based signals like round-number deposit clustering and duplicate transaction sequences should escalate automatically when their individual confidence score exceeds 0.85.
How do I secure webhook payloads for bank statement data?
Implement HMAC-SHA256 signature verification using the Clearstaq-Signature header on every incoming request — reject any request that fails verification before processing the payload. Add a timestamp tolerance check (reject events older than 5 minutes) to prevent replay attacks, and use crypto.timingSafeEqual() in Node.js or hmac.compare_digest() in Python to prevent timing-based signature brute-force attacks. See our full guide to securing financial API webhooks for PII handling and TLS requirements.
Ready to Build a Live Fraud Monitoring System?
ClearStaq's real-time fraud detection API surfaces 27 individual fraud signals in a structured webhook payload — delivered in under 3 seconds, with SOC2-compliant infrastructure and a full sandbox for testing. Start your free trial today and have your first fraud alert firing within an afternoon.
Frequently Asked Questions
What is a real-time fraud detection API?
A real-time fraud detection API analyzes financial documents and returns fraud scores and signal-level results within seconds of submission. Unlike batch systems that process documents on a schedule, a real-time API like ClearStaq fires a webhook to your application the moment analysis completes — typically within 3 seconds of upload — enabling instant decisioning during live application flows.
How do webhooks work in fraud detection systems?
Webhooks allow a fraud detection API to push results to your system the instant a document is analyzed, rather than requiring your application to poll for updates. When ClearStaq finishes scoring a bank statement, it sends an HTTP POST to your registered endpoint with a signed JSON payload containing the fraud score, severity tier, and all triggered fraud signals — no polling required.
What is the difference between polling and webhooks for fraud alerts?
Polling requires your system to repeatedly ask the API for results at set intervals, introducing latency proportional to the polling frequency and consuming unnecessary API calls. Webhooks push results to your endpoint the moment they are ready — ClearStaq delivers bank statement fraud alert webhooks in under 3 seconds, compared to minutes or hours of latency with typical 30-second to 5-minute polling intervals.
What fraud signals should trigger an immediate bank statement fraud alert?
Signals in the document_manipulation and metadata_tampering categories — such as altered PDF metadata, pixel-level image manipulation, and identity field mismatches — should always trigger immediate high-severity alerts regardless of aggregate score. Pattern-based signals like round-number deposit clustering and duplicate transaction sequences should escalate automatically when their individual confidence score exceeds 0.85.
How do I secure webhook payloads for bank statement data?
Implement HMAC-SHA256 signature verification using the Clearstaq-Signature header on every incoming request and reject any request that fails verification before processing. Add a timestamp tolerance check to reject events older than 5 minutes to prevent replay attacks, and use crypto.timingSafeEqual() in Node.js or hmac.compare_digest() in Python to prevent timing-based brute-force attacks against your signing secret.
ClearStaq Team
Engineering Team
The ClearStaq team builds AI-powered tools for bank statement parsing, fraud detection, and income verification.



