Trigger icon
Schedule icon
Parallel icon
Download icon
Query icon
Script icon
If icon
Exit icon
SlackIncomingWebhook icon
Pause icon
CopyIn icon
Queries icon
Set icon

Sanctions Screening for Vendors and Customers Against the OFAC Lists, with Compliance Review and Audit Log

Screen vendors and customers against OFAC sanctions lists with fuzzy matching, compliance review, remembered false positives and an audit log.

Categories
BusinessData

Paying, shipping to or onboarding a sanctioned party can mean heavy fines, and the lists change every week. Fintechs, payment companies, exporters and B2B SaaS companies must screen every vendor and customer, usually by copying names into a website or with a spreadsheet nobody re-runs. This blueprint screens your counterparty table against the official OFAC lists. It screens new counterparties within minutes and everyone again when the lists change. Compliance only sees the possible matches, false positives are never asked twice, and confirmed matches are blocked with an audit trail.

How it works

  1. When: new_counterparties (io.kestra.plugin.jdbc.postgresql.Trigger) polls for rows with screening_status = 'new', starts a run, and marks them queued with afterSQL, so each counterparty fires once. daily_rescreen (io.kestra.plugin.core.trigger.Schedule) re-screens everyone every morning.
  2. lists (io.kestra.plugin.core.flow.Parallel) downloads the OFAC SDN list and aliases, and the consolidated non-SDN list and aliases (io.kestra.plugin.core.http.Download). If one fails, the run fails instead of screening against an incomplete list.
  3. counterparties (io.kestra.plugin.jdbc.postgresql.Query, stored as a file) reads the counterparties to screen. Blocked ones are never re-screened.
  4. screen (io.kestra.plugin.scripts.python.Script with rapidfuzz):
    • normalizes names (legal suffixes such as LLC, Ltd, GmbH and PJSC, accents, punctuation);
    • fuzzy-matches each counterparty against every listed name and alias;
    • skips pairs compliance already cleared, using the decisions kept in the KV store;
    • lists each possible match with its score, the matched name or alias, the sanctions program and the list.
  5. skip_if_unchanged ends a full re-screen early when the lists haven't changed and nothing new matched. post_summary posts the counts to Slack.
  6. Outside a dry run, ask_compliance posts a link, and decision (io.kestra.plugin.core.flow.Pause) shows a review form filled with this run's possible matches. Compliance picks the true matches and gives a reason; every other pair becomes a false positive. Without a decision within three days, the run is cancelled and nothing changes.
  7. decide builds the audit rows. audit_log (io.kestra.plugin.jdbc.postgresql.CopyIn) appends them to sanctions_screening_log, which audit_table creates on first use. apply_status (io.kestra.plugin.jdbc.postgresql.Queries) sets confirmed matches to blocked and the rest to cleared. remember_decisions (io.kestra.plugin.core.kv.Set) stores the false positives, and tell_blocked posts confirmed matches to Slack.
  8. alert_failure posts any failure to Slack.

Prerequisites

  • A PostgreSQL table (or view backed by a table) of counterparties: CREATE TABLE counterparties (id serial PRIMARY KEY, name text NOT NULL, country text, type text, screening_status text NOT NULL DEFAULT 'new'); New rows start as new; the flow sets queued, cleared or blocked. Your onboarding or payment processes should refuse blocked counterparties.
  • A database login with SELECT and UPDATE on counterparties, and the right to create the sanctions_screening_log table (or create it yourself).
  • Outbound HTTPS to sanctionslistservice.ofac.treas.gov, and Docker on the worker for the Python script.
  • Secrets:
    • SANCTIONS_DB_URL: the JDBC URL, for example jdbc:postgresql://db:5432/erp.
    • SANCTIONS_DB_USERNAME and SANCTIONS_DB_PASSWORD: the database login above.
    • SLACK_WEBHOOK_URL: Slack incoming webhook for summaries, review requests, confirmed matches and failure alerts.

Inputs

  • scope (SELECT new or all, default new): new screens counterparties added since the last run; all re-screens everyone who isn't blocked. The daily schedule uses all.
  • review_threshold (INT, default 85): the fuzzy score (0 to 100) from which a pair goes to review.
  • include_consolidated (BOOL, default true): also screen the consolidated non-SDN lists.
  • dry_run (BOOL, default false): screen and report only. No review request, status change, audit row or remembered decision. Nothing is ever blocked without a reviewer's decision, so real runs are safe by default.

Quick start

  1. Create or map the counterparties table, add the four secrets, and save the flow.
  2. Run it with dry_run set to true and read the possible matches in the screen outputs.
  3. Run it again without the dry run, decide in the review form, and check screening_status and sanctions_screening_log.
  4. Enable new_counterparties and daily_rescreen.

Outputs

  • {{ outputs.possible_matches }} / {{ outputs.screen.vars.hits }} and {{ outputs.screen.vars.review_options }}: the possible matches.
  • {{ outputs.hits_file }} / {{ outputs.screen.outputFiles['hits.json'] }}: every possible match with score, listed name, alias, program and list.
  • {{ outputs.blocked }} / {{ outputs.decide.vars.blocked }}: counterparties blocked in this run.
  • Table sanctions_screening_log: one row per screened counterparty and per possible match, with decision, reason, list version and time.
  • KV keys sanctions-decisions (remembered false positives) and sanctions-list-version.

Common pitfalls

  • Fuzzy matching on names alone produces false positives by design; that's what the review is for. Lower review_threshold for stricter screening, raise it for fewer reviews.
  • OFAC files don't include every identifier. Match on names here, and confirm a true match with addresses or registration numbers before blocking a long-standing customer.
  • A remembered false positive is tied to one counterparty and one list entry. If OFAC changes that entry, it can come back for review.
  • This screens against the US OFAC lists only. Add EU, UK or UN lists as more downloads in lists and more files in screen.

How to extend

  • Screen individuals too, for example customer names, from a second query.
  • Open a case in your ticketing tool for each confirmed match after apply_status.
  • Add the EU Financial Sanctions or UK OFSI lists in the same format.

Links

See How

New to Kestra?

Use blueprints to kickstart your first workflows.