cd /news/ai-agents/your-ai-agent-found-a-new-iban-on-an… · home › topics › ai-agents › article
[ARTICLE · art-168192] src=dev.to ↗ pub= topic=ai-agents verified=true sentiment=· neutral

Your AI agent found a new IBAN on an invoice. Should it pay? (IBAN checks in Python, and their limits)

A developer tested IBAN validation against nine inputs and found that MOD-97 checksum checks catch single-digit typos but pass altered account numbers with recomputed check digits, meaning a validator can confirm an IBAN is well-formed but not that it belongs to the supplier. The writeup recommends that payment agents treat any IBAN differing from vendor master data as a hold, flag bank or country changes as high risk, and require human confirmation through a known channel before releasing payment, with the agent never allowed to move a payment from hold to pay on its own.

by read4 min views2 publishedNov 12, 2026

An invoice arrives. Same supplier, same amount pattern, but the IBAN is different. If an AI agent prepares your payments, what should it do?

Bank-detail changes are a classic invoice-fraud pattern, and agents make it more interesting: they read every PDF, never get tired and will happily "fix" vendor master data if the instructions say so. So I tested what an IBAN check actually catches.

Disclosure: I built the IBAN Validator & Bank Lookup on Apify, which I use below. The lesson applies to any validator.

Nine inputs: a known-good German IBAN, the same IBAN with one typo, three IBANs where I changed the account number and recomputed the two check digits (what a fraudster would do), plus Dutch, French, Austrian and a truncated one.

import os, requests

URL = ("https://api.apify.com/v2/acts/fast_data_asap~iban-validator-bank-lookup"
       "/run-sync-get-dataset-items")
HEADERS = {"Authorization": f"Bearer {os.environ['APIFY_TOKEN']}"}

ibans = [
    "DE89370400440532013000",        # original
    "DE89 3704 0044 0532 0130 01",   # one digit typo
    "DE62370400440532013001",        # changed account + recomputed check digits
    "DE40370400440532013009",
    "DE18370400440532013017",
    "NL91ABNA0417164300", "FR1420041010050500013M02606", "AT611904300234573201",
    "DE12345",
]
resp = requests.post(URL, headers=HEADERS, timeout=120,
                     json={"ibans": ibans, "includeBankInfo": True})
resp.raise_for_status()
for r in resp.json():
    print(r["input"], r["valid"], r["checksumValid"], r["nationalChecksumValid"], r["bankName"], r["error"])
Input Valid MOD-97 National check Bank
DE89 …013000 (original) yes ok ok Commerzbank
DE89 …013001 (typo) no fails ok Commerzbank
DE62 …013001 (changed, recomputed) yes ok ok Commerzbank
DE40 …013009 (changed, recomputed) yes ok ok Commerzbank
DE18 …013017 (changed, recomputed) yes ok ok Commerzbank
NL91 ABNA… yes ok – ABN AMRO
FR14 2004… yes ok ok La Banque Postale
AT61 1904… yes ok – (no bank data)
DE12345 no fails – wrong length

Real output, run on 12 November 2026. Malformed rows are not charged.

Two takeaways:

A validator answers "is this a well-formed IBAN, and which bank is it?". It cannot answer "does this account belong to my supplier?".

The useful signal is not valid, it is the comparison with what you already know:

Signal Source What the agent should do
valid: false validator reject, ask for corrected invoice
IBAN differs from vendor master data your ERP hold payment , never update master data automatically
bankName /bic differs from the known bank validator + ERP hold, flag as high risk
country of the bank differs from the supplier's country validator + ERP hold, flag as high risk
new IBAN confirmed by the supplier through a known channel human release

Here is that rule as code:

def iban_decision(invoice_iban: str, vendor: dict, check: dict) -> tuple[str, str]:
    """vendor = your master data, check = one row from the validator."""
    if not check["valid"]:
        return "reject", check["error"]
    if check["iban"].replace(" ", "") == vendor["iban"].replace(" ", ""):
        return "pay", "IBAN matches master data"
    reasons = ["IBAN differs from master data"]
    if check.get("bic") and vendor.get("bic") and check["bic"][:8] != vendor["bic"][:8]:
        reasons.append(f"bank changed: {vendor.get('bank')} -> {check.get('bankName')}")
    if check["countryCode"] != vendor["iban"][:2]:
        reasons.append("bank country changed")
    return "hold", "; ".join(reasons) + " - confirm with supplier via known phone number"

The important design choice: the agent can never move from "hold" to "pay" on its own. It can prepare the evidence (old vs new bank, invoice, sender address), but the release needs a human or a bank-side confirmation.

Since October 2025, EU payment service providers have to offer Verification of Payee for euro credit transfers: before you send money, your bank checks whether the payee name matches the account holder of that IBAN and tells you "match", "close match" or "no match". That is the check that would expose the fraudulent IBANs above, because the name on the account would not be your supplier's.

So the full chain for an accounts-payable agent looks like this:

IBAN Validator & Bank Lookup on Apify: bulk IBAN validation with MOD-97 and national checksums, SEPA flag, bank name and BIC (German banks from the Bundesbank file), $0.0005 per IBAN, runs offline inside the actor. For the full supplier check (company register, VAT ID, IBAN) see our free Agent Skill verify-supplier-de.

This is about payment hygiene, not legal advice.

Question: if you let an agent prepare payments, where do you draw the line: may it ever update vendor bank details, or is that always a human step?

── more in #ai-agents 4 stories · sorted by recency
── more on @apify 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
→ Live at https://your-agent.zahid.host ✓
Get free account → Pricing
from €0/mo · no card required
LIVE [news/your-ai-agent-found-…] indexed:0 read:4min 2026-11-12 · —