Every financial audit includes an assessment of cash flows. Not only to understand where money comes from and where it goes, but also to ensure there are no payments made to high-risk countries. Such payments may indicate money laundering or fraud.
The FATF list (Financial Action Task Force) contains countries with an increased risk in these areas. Organizations active in international trade, for example, importers, donation platforms, or logistics companies, are particularly exposed to such risks.
As an auditor or controller, you can use data analysis to easily identify suspicious cash flows. We use bank statements in the CAMT.053 format and analyze them using Python.
Required Data and Packages
For this analysis, we use CAMT files. These are XML files used by banks to report transactions. Nearly all European banks will provide exports in this format from 2025 onward.
You can find more information about the format here, and another article offers a converter to Excel.
We'll use Python for the analysis, with one additional package ('pandas'). It's easy to install, and you can find more information about pandas in auditing on the website.
Step 1: List of High-Risk Countries
First, we define a list of high-risk countries based on the FATF list.
You can find the latest version here:
https://www.fatf-gafi.org/en/countries/black-and-grey-lists.html
# Example list of high-risk countries
high_risk_country_codes = {"IR", "KP", "SY", "SD", "PK", "YE", "MM", "CU", "AF", "ZM"}
Step 2: Read the CAMT.053 File
We read the bank transactions and extract key fields: date, amount, debtor, creditor, and IBAN. The example script below is a simplified version. You can find more details about the CAMT converter in the linked article.
import xml.etree.ElementTree as ET
import pandas as pd
def parse_camt053(file_path):
tree = ET.parse(file_path)
root = tree.getroot()
ns = {'ns': 'urn:iso:std:iso:20022:tech:xsd:camt.053.001.02'}
transactions = []
for entry in root.findall('.//ns:Ntry', ns):
try:
amount = float(entry.find('ns:Amt', ns).text)
except (ValueError, AttributeError):
amount = 0.0
currency = entry.find('ns:Amt', ns).attrib.get('Ccy', 'N/A')
date = entry.find('.//ns:BookgDt/ns:Dt', ns).text or '-'
debtor = entry.find('.//ns:Dbtr/ns:Nm', ns).text or '-'
debtor_iban = entry.find('.//ns:DbtrAcct/ns:Id/ns:IBAN', ns).text or '-'
creditor = entry.find('.//ns:Cdtr/ns:Nm', ns).text or '-'
creditor_iban = entry.find('.//ns:CdtrAcct/ns:Id/ns:IBAN', ns).text or '-'
transactions.append({
"Date": date,
"Debtor": debtor,
"Debtor IBAN": debtor_iban,
"Creditor": creditor,
"Creditor IBAN": creditor_iban,
"Amount": amount,
"Currency": currency
})
return pd.DataFrame(transactions)
# Load the CAMT file
file_path = "camt053_dummy.xml"
df = parse_camt053(file_path)
print(df.head())
Step 3: Extract Country Codes from IBAN
The first two letters of an IBAN indicate the country code. This allows you to determine in which country the account is held.
def get_country_from_iban(iban):
return iban[:2]
df["Creditor Country"] = df["Creditor IBAN"].apply(get_country_from_iban)
df["Debtor Country"] = df["Debtor IBAN"].apply(get_country_from_iban)
Step 4: Filter Transactions from High-Risk Countries
Now we filter transactions where either the payer or the recipient is located in a high-risk country.
df_high_risk = df[
df["Creditor Country"].isin(high_risk_country_codes) |
df["Debtor Country"].isin(high_risk_country_codes)
]
Step 5: Summarize the Results
Finally, we summarize how many payments were made and the total amounts involved.
df_analysis = df_high_risk.groupby(["Creditor Country"]).agg(
Transaction_Count=("Amount", "count"),
Total_Amount=("Amount", "sum")
).reset_index()
print(df_analysis)
Creditor Country Transaction Count Total Amount
IR 5 12500 PK 3 7600 YE 7 18100 CU 2 3200
Interpretation and Next Steps
This analysis shows to which countries payments have been made and how frequently. As an auditor, you should further investigate:
- Are these legitimate payments (e.g., regular suppliers)?\
- Are there new accounts, unusual names, or unexpected amounts?\
- Are there transactions to countries unrelated to business activities?
This provides a quick overview of potential risks of money laundering or unauthorized payments.
Limitations of IBAN Country Codes & BIC
Not all countries use IBAN numbers. The International Bank Account Number (IBAN) is mainly used within Europe and countries that participate in the SEPA standard (Single Euro Payments Area). Outside these regions, banks often use different account structures or national formats.
When analyzing CAMT files or other payment data, keep in mind:
- Check whether the 'IBAN' field is actually filled. Missing values might mean the transaction originates from a non-IBAN country.
- Use additional fields such as BIC (Bank Identifier Code) to determine the country of origin or destination.
- For international SWIFT payments, the BIC or country code in the transaction can often help identify the correct country.
Note that some banks route international payments through intermediary accounts. This can cause the original country code to be missing in the IBAN field, potentially distorting your analysis.
In short: while IBAN is a reliable indicator within Europe, for payments outside the SEPA area you should use alternative fields or perform additional checks to accurately identify the transaction's origin.
Toolkit
Want to go further? Check out the CAMT Analytics
Toolkit.
This toolkit includes extended scripts, documentation, and dummy data to
perform this and similar analyses. It also contains a BIC-based analysis
for even more accurate tracing of transaction origins. More information
about the available toolkits can be found
here.