How to Build an AML Compliance Pipeline with a Sanctions Screening API
Most compliance guides are written for lawyers. This one is written for developers. If you are building a fintech product and need to add sanctions screening to your user onboarding flow, this guide will walk you through exactly how to do it with working code examples.
By the end of this article, you will have a complete AML screening pipeline that checks customers against OFAC, UN, EU, and UK sanctions lists at signup, flags risky users, and maintains an audit trail for regulators.
The architecture
A typical sanctions screening pipeline has three integration points in your application:
- Onboarding screening. When a new user signs up, screen their name before activating their account.
- Transaction screening. Before processing an outbound payment, screen the recipient.
- Batch re-screening. Periodically re-screen your entire customer base against updated sanctions lists.
Let's build each one.
Step 1: Get your API key
Sign up at verifex.dev and grab your API key from the dashboard. The free tier gives you 100 screens per month, which is plenty for development and testing.
Store the key as an environment variable. Never hardcode API keys in your source code.
export VERIFEX_API_KEY="vfx_sk_live_your_key_here"Step 2: Screen at signup (Node.js)
The most important checkpoint is customer onboarding. When a user submits their registration form, screen their name before creating their account. Here is how to do it in Node.js:
async function screenCustomer(name, type = "person") {
const response = await fetch("https://api.verifex.dev/v1/screen", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.VERIFEX_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ name, type }),
});
return response.json();
}
// In your signup handler:
async function handleSignup(req, res) {
const { name, email, password } = req.body;
// Screen the customer before creating their account
const screening = await screenCustomer(name);
if (screening.risk_level === "critical" || screening.risk_level === "high") {
// Flag for manual review - do not auto-approve
await db.user.create({
data: {
name, email, password,
status: "pending_review",
screeningResult: JSON.stringify(screening),
},
});
return res.json({
message: "Your account is under review.",
});
}
// Low risk or clear - approve automatically
await db.user.create({
data: { name, email, password, status: "active" },
});
return res.json({ message: "Account created." });
}The key insight here is that you do not need to block every match. A "critical" risk level (confidence 90%+) usually means an exact or near-exact match and should trigger a review. A "low" risk level might just be a common name collision and can be auto-approved in most cases.
Step 3: Screen at signup (Python)
Here is the same flow in Python using the requests library:
import os
import requests
def screen_customer(name, entity_type="person"):
response = requests.post(
"https://api.verifex.dev/v1/screen",
headers={
"Authorization": f"Bearer {os.environ['VERIFEX_API_KEY']}",
"Content-Type": "application/json",
},
json={"name": name, "type": entity_type},
)
return response.json()
# Usage
result = screen_customer("John Doe")
if result["risk_level"] in ("critical", "high"):
print(f"High risk match: {result['matches'][0]['name']}")
print(f"Source: {result['matches'][0]['source']}")
print(f"Confidence: {result['matches'][0]['confidence']}%")
# Flag for review
else:
print("Clear - no significant matches")
# Auto-approveStep 4: Transaction screening
For payment processors and money transfer apps, you also need to screen the recipient of each outbound transaction. This is similar to onboarding screening but happens at a different point in the user journey.
async function processPayment(sender, recipient, amount) {
// Screen the recipient before processing
const screening = await screenCustomer(recipient.name);
if (screening.risk_level === "critical") {
// Block the transaction
await logBlockedTransaction(sender, recipient, amount, screening);
throw new Error("Transaction blocked: recipient on sanctions list");
}
if (screening.risk_level === "high") {
// Queue for manual review
await queueForReview(sender, recipient, amount, screening);
return { status: "pending_review" };
}
// Process the payment
return await executePayment(sender, recipient, amount);
}Step 5: Batch re-screening
Sanctions lists are updated frequently. Someone who was not sanctioned when they signed up might get added to a list months later. That is why you need periodic batch re-screening of your entire customer base.
The Verifex batch API lets you screen up to 100 entities in a single request:
async function reScreenAllCustomers() {
const customers = await db.user.findMany({
where: { status: "active" },
});
// Process in batches of 100
for (let i = 0; i < customers.length; i += 100) {
const batch = customers.slice(i, i + 100);
const response = await fetch("https://api.verifex.dev/v1/screen/batch", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.VERIFEX_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
entities: batch.map(c => ({ name: c.name, type: "person" })),
}),
});
const data = await response.json();
// Check each result
for (let j = 0; j < data.results.length; j++) {
const result = data.results[j];
if (result.risk_level === "critical" || result.risk_level === "high") {
await flagCustomerForReview(batch[j].id, result);
}
}
}
}Run this as a weekly or monthly cron job depending on your compliance requirements. For most fintechs, monthly re-screening is sufficient. Higher-risk businesses like crypto exchanges may want to run it weekly.
Step 6: Building the audit trail
Regulators will ask you to prove that you screened specific customers. Every screening request should be logged with:
- The name that was screened
- Timestamp of the screening
- Which lists were checked
- The results (matches found, confidence scores)
- What action was taken (approved, flagged, blocked)
Verifex includes a request ID in every response, and the Pro plan includes audit log access via the API. You should also log these results in your own database for redundancy.
Handling false positives
False positives are the biggest operational challenge in sanctions screening. Common names like "Mohammed Ali" or "David Kim" will frequently match against sanctions entries that are not actually the same person.
Here is how to handle them:
- Use confidence scores. A 95% match needs investigation. A 45% match on a common name is almost certainly a false positive.
- Check additional fields. Compare date of birth, nationality, and country against the matched entry. If your customer is a 25-year-old from Canada and the match is a 60-year-old from Iran, it is a false positive.
- Maintain a whitelist. Once you have confirmed a match is a false positive, add the customer to an internal whitelist so they do not get flagged again.
- Set a threshold. For automated flows, consider only flagging matches above a certain confidence level (e.g., 75%) and auto-approving lower matches.
Summary
A complete AML screening pipeline has three components: onboarding screening, transaction screening, and periodic batch re-screening. With a modern API like Verifex, you can implement all three in a single afternoon.
The key principles are: screen early (at signup), screen often (before transactions and periodically), log everything (for audit trails), and handle false positives gracefully (using confidence scores and additional data).
Try it yourself
Get a free API key and start screening in 5 minutes. No credit card required.
Get Free API Key