Real-Time IBAN Validation with Webhooks: Architecture Guide
Architecture guide for event-driven IBAN validation: webhook receiver implementation in Node.js, HMAC signature verification, idempotency, exponential backoff retry logic, bulk batching, and dead-letter queue handling.
Real-time IBAN validation with webhooks is the right architecture when you need payment account verification to happen asynchronously — on form submission, on file upload, or on record creation — without blocking your user-facing request cycle. This guide walks through the event-driven architecture, shows complete webhook handler implementations, and covers retry logic, idempotency, and dead-letter handling for production deployments.
Why Would You Use Webhooks Instead of Synchronous IBAN Validation?
Synchronous validation — calling the ibanchecker.cash API inline during a form POST — is correct for most single-IBAN flows. But several scenarios call for an async, event-driven approach:
- Bulk file imports: A finance team uploads a CSV of 2,000 supplier IBANs. Validating all of them during the HTTP request would time out. Instead, accept the file, emit an event, and validate each IBAN in a background worker.
- Third-party integrations: Your ERP or accounting system creates payment records via its own UI. A webhook fires on each new record; your validation service catches it, validates the IBAN, and writes the result back.
- Microservice architectures: The payment service owns IBAN storage; the compliance service owns validation. They communicate via an event bus, not direct HTTP calls.
- Audit trails: You need a durable log of every validation event — timestamp, result, bank name — separate from your main database schema.
What Does the Webhook Validation Architecture Look Like?
The flow has four stages:
- Producer — your application emits a
payment_account.createdevent to a message queue (Kafka topic, SQS queue, or HTTP webhook endpoint) whenever a new IBAN is submitted. - Validator service — consumes the event, calls
POST /api/v1/validate, and emits apayment_account.validatedresult event. - Consumer — your downstream service (notification service, ERP connector, payment engine) reacts to the result event and marks the account as verified or invalid.
- Dead-letter queue — events that fail validation retries after exhausting attempts land here for manual review.
How Do You Build a Webhook Receiver for IBAN Validation Events?
The following Node.js (Express) example receives a webhook from an upstream system, validates the IBAN, and calls back to a configurable results endpoint. It includes HMAC signature verification, idempotency checking, and structured error responses.
import express from "express";
import crypto from "crypto";
const app = express();
app.use(express.json());
const WEBHOOK_SECRET = process.env.WEBHOOK_SECRET;
const PROCESSED = new Set(); // replace with Redis in production
function verifySignature(rawBody, signature, secret) {
const expected = crypto
.createHmac("sha256", secret)
.update(rawBody)
.digest("hex");
return crypto.timingSafeEqual(
Buffer.from(signature, "hex"),
Buffer.from(expected, "hex")
);
}
async function validateIban(iban) {
const res = await fetch("https://ibanchecker.cash/api/v1/validate", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ iban }),
});
return res.json();
}
app.post("/webhooks/iban-validate", express.raw({ type: "application/json" }), async (req, res) => {
const sig = req.headers["x-webhook-signature"];
if (!sig || !verifySignature(req.body, sig, WEBHOOK_SECRET)) {
return res.status(401).json({ error: "Invalid signature" });
}
const event = JSON.parse(req.body.toString());
if (PROCESSED.has(event.id)) {
return res.status(200).json({ status: "already_processed" });
}
PROCESSED.add(event.id);
if (event.type !== "payment_account.created") {
return res.status(200).json({ status: "ignored" });
}
const { accountId, iban } = event.data;
const result = await validateIban(iban);
await notifyResult({
accountId,
valid: result.valid,
bankName: result.bankName,
bic: result.bic,
validatedAt: new Date().toISOString(),
});
res.status(200).json({ status: "processed" });
});
app.listen(3000);How Do You Handle Retries and Dead-Letter Queues?
Network failures and rate limit responses (HTTP 429) require retry logic with exponential backoff. Structure your validator as a standalone function so it can be called from any queue consumer.
async function validateWithRetry(iban, maxAttempts = 4) {
let attempt = 0;
while (attempt < maxAttempts) {
try {
const res = await fetch("https://ibanchecker.cash/api/v1/validate", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ iban }),
signal: AbortSignal.timeout(5000),
});
if (res.status === 429) {
const retryAfter = parseInt(res.headers.get("Retry-After") ?? "60", 10);
await sleep(retryAfter * 1000);
continue;
}
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return await res.json();
} catch (err) {
attempt++;
if (attempt >= maxAttempts) throw err;
await sleep(200 * 2 ** attempt);
}
}
}
function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
async function processEvent(event, dlq) {
try {
const result = await validateWithRetry(event.data.iban);
await emitResult(event.data.accountId, result);
} catch (err) {
console.error(`Failed after retries: ${event.id}`, err);
await dlq.send(event);
}
}How Do You Validate IBANs in Bulk via the Webhook Pipeline?
When a file upload event carries more than one IBAN, use the bulk endpoint to validate up to 100 at once. Batch the file's IBANs into chunks of 100 and process each chunk as a single API call.
async function validateBulkChunked(ibans) {
const CHUNK_SIZE = 100;
const results = [];
for (let i = 0; i < ibans.length; i += CHUNK_SIZE) {
const chunk = ibans.slice(i, i + CHUNK_SIZE);
const res = await fetch("https://ibanchecker.cash/api/v1/validate/bulk", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ ibans: chunk }),
});
if (!res.ok) throw new Error(`Bulk validate failed: HTTP ${res.status}`);
const { results: chunkResults } = await res.json();
results.push(...chunkResults);
if (i + CHUNK_SIZE < ibans.length) {
await sleep(100);
}
}
return results;
}
async function handleFileImportEvent(event) {
const { fileId, ibans } = event.data;
const results = await validateBulkChunked(ibans);
const summary = {
fileId,
total: results.length,
valid: results.filter((r) => r.valid).length,
invalid: results.filter((r) => !r.valid).length,
results,
};
await saveValidationReport(summary);
await notifyUploader(event.data.userId, summary);
}How Do You Secure the Webhook Endpoint in Production?
Beyond HMAC verification, apply these hardening measures to your webhook receiver:
- IP allowlist: If your producer's IP range is fixed (e.g., an internal service or a known SaaS provider), allowlist it at the load balancer level before the request reaches your application.
- Replay protection: Include a timestamp in the signed payload and reject events older than 5 minutes. Rotate the
PROCESSEDset to a time-bounded Redis key with TTL to avoid unbounded memory growth. - Rate limiting: Protect the webhook endpoint itself against flood attacks using a token bucket per source IP.
- Structured logging: Log every event ID, IBAN country prefix (not the full IBAN — never log raw IBANs), result, and latency for audit purposes.
function isReplayAttack(timestamp) {
const age = Date.now() - new Date(timestamp).getTime();
return age > 5 * 60 * 1000;
}
app.post("/webhooks/iban-validate", express.raw({ type: "application/json" }), async (req, res) => {
const sig = req.headers["x-webhook-signature"];
const ts = req.headers["x-webhook-timestamp"];
if (!sig || !ts) return res.status(400).json({ error: "Missing headers" });
if (isReplayAttack(ts)) return res.status(400).json({ error: "Stale event" });
if (!verifySignature(req.body, sig, WEBHOOK_SECRET)) {
return res.status(401).json({ error: "Invalid signature" });
}
// ... rest of handler
});See the API documentation for full request/response schemas and authentication details, and the pricing page for bulk validation rate limits.
Last updated: June 2026
Validate an IBAN instantly
Free IBAN checker — MOD-97 verification, bank lookup, and SEPA status across 84 countries.
Open IBAN Checker →Related Articles