See llms.txt for all machine-readable content.

Back to Templates

Screen counterparty names against the OFAC SDN list with webhook and Discord

Created by

Created by: Melbin Francis || francime
Melbin Francis

Last update

Last update 19 hours ago

Categories

Share


Quick overview

This workflow exposes a header-authenticated webhook that screens submitted names against the OFAC SDN sanctions list and its alias list, logs each screening to n8n Data Tables, and posts Discord alerts for possible matches plus a weekly audit summary.

How it works

  1. Receives a POST request on a Webhook endpoint with header authentication and a JSON body containing the name to screen.
  2. Downloads the latest OFAC SDN CSV and alternate names (alias) CSV via HTTP requests and loads the current human-cleared name pairs from n8n Data Tables.
  3. Normalizes the input name and compares it deterministically against every OFAC primary and alias entry, returning UNAVAILABLE (503) if either list cannot be read.
  4. Applies a match threshold to build a candidate match list, suppressing matches that were previously reviewed and recorded as cleared for the exact query/matched-name pair.
  5. Posts a Discord message when the result is a POSSIBLE MATCH or the screening is UNAVAILABLE, then writes the screening outcome to a Data Table log.
  6. Responds to the webhook caller with a JSON verdict, match details, and a screening disclaimer.
  7. Runs every Monday morning to summarize the past week’s screenings from the log and posts the audit summary to Discord.

Setup

  1. Run the manual “Set Up Tables” path once to create the n8n Data Tables (sanctions_screenings and sanctions_cleared) and seed the demo clearance examples.
  2. Create an HTTP Header Auth credential for the webhook and configure your callers to send the required header key/value.
  3. Add a Discord Webhook credential and set it to the channel where screening alerts and weekly audits should be posted.
  4. Activate the workflow, copy the production webhook URL, and POST a JSON payload like {"name":"Acme Trading Ltd","caller":"Your app"} to start screening.

Requirements

  • An n8n instance reachable by whatever will call the API, and n8n 2.38 or newer. Data Tables are built in and the workflow creates its own two tables when you run "Set Up Tables (run once)". No outside database.
  • A Header Auth credential on the Screening API webhook. An open screening endpoint tells anyone who finds it which counterparties you are checking. Tested live: a missing key and a wrong key both get 403.
  • A Discord webhook for the possible-match alert and the Monday summary. Swap both Discord nodes for Slack or Gmail if you prefer; every call is written to the screening log first either way.
  • No model credential and no API key for the list. The OFAC files are published openly by the US Treasury and the workflow fetches them directly. There is nothing to pay for per call.

Customization

  • Set Screening Policy holds the two list URLs, the match threshold, the minimum row counts and how many matches to return. That is the whole configuration surface.
  • The threshold is 85 and it is the noise dial. Lower it to catch more and review more; raise it and you will miss variants. A one word query scores 67 against a longer listing, so a surname alone does not alert. A query of two or more words that appears in full inside a listing is always surfaced, which is what catches GAZPROM NEFT inside PUBLIC JOINT STOCK COMPANY GAZPROM NEFT.
  • Two minimum row counts, 1000 each, one per file. They are what stops a truncated or failed download passing as a screening, and BOTH files gate the answer: the alias file carries the only listed spelling of many parties. Keep them well above zero and well below the real sizes of about 19,000 and 20,000.
  • sanctions_cleared is where a person records a match they looked at and found to be somebody else, with their name and the reason. A clearance applies to one exact pair of names, so clearing ACME TRADING GMBH does not clear ACME TRADING CO. The two seeded rows are prefixed DEMO ONLY so they can never silence a real listing; delete them once you have your own.
  • The audit window is one value in Set Audit Policy and the schedule is Monday at 08:00. Change the day, or delete the trigger and run it by hand.

Additional info

There is no sanctions screening template in this library. Measured on 11 September 2026: "sanctions screening" returns 20 results and every one is the substring "actions", "Functions" or "approvals", and "OFAC" returns 3 results that are all the word "Oracle". So this is the first, which also means nobody has checked it but me.

POST a name, get a verdict back in the same request. There is no model. The name is uppercased, accents stripped, dotted abbreviations collapsed and company suffixes dropped, then compared against every entry in the OFAC SDN file and every alternate spelling in the alias file: 39,594 names on each call. An exact match scores 100, and everything else is scored in both directions so a short query cannot rank highly just by being contained in a long one.

The part worth reading twice is what happens when a list cannot be read. The answer is HTTP 503 with the verdict UNAVAILABLE and the words "this name was NOT screened. This is not a clear result." It is never a clear, because the workflow did not look. That was tested by pointing each URL at a 404 in turn, and it also happened by accident: the first call after activation failed to fetch, answered UNAVAILABLE, and the same endpoint screened 39,594 entries seven seconds later. Both fetches now retry three times. A screening that could not happen is recorded as such, and the Monday summary counts those separately and says they must be run again.

An earlier version checked only the primary file. With the alias file missing, PDVSA came back NO MATCH with screened true, because its only listed spelling is an alias: a confident clear for a sanctioned party. Both files now gate the verdict and the refusal names which one failed. If you change the URLs, keep that property.

Real names found two misses. PETROLEOS DE VENEZUELA returned NO MATCH because the listing is PETROLEOS DE VENEZUELA, S.A. and stripping punctuation turned S.A. into the letters S and A, diluting the score to 75. GAZPROM NEFT returned NO MATCH against PUBLIC JOINT STOCK COMPANY GAZPROM NEFT, because a two word query scores 80 against a five word listing. Both are false negatives, and reading the code would never have shown either. Both are fixed, and a count of matches counts parties rather than spellings, because three spellings of one party reads as three targets.

The cost of catching GAZPROM NEFT is noise, measured rather than guessed: 9 parties for that query, 36 for AL HASSAN, 27 for MOHAMMAD ALI, 0 for a name that is not listed. An ambiguous personal name returns a lot of candidates. That is the right trade for screening, where a miss is a breach and a false positive is review time, and the wrong trade for an automated gate.

A clearance recorded by a person suppresses the alert for that exact pair of names, which is the point and also the sharpest edge in this template. The two demo rows are prefixed DEMO ONLY for that reason: an earlier draft used JOHN SMITH, which is inert against today's list and would stop being inert the day one is added. Delete the demo rows.

Known limits, stated plainly. This is the OFAC list only, so a counterparty sanctioned in the EU or the UK and nowhere else comes back NO MATCH. It compares names and nothing else, no date of birth, no address, no identifier, so every answer is a candidate list rather than a finding and says so. A name transliterated differently from the listing, or written in a non-Latin script, will be missed. The name being screened is stored in the log in plain text with no redaction. A response takes seven to nine seconds because 6.7 MB is fetched fresh every time, which is deliberate: a cache that went stale without saying so would bring back the exact silent failure this is built to avoid. And the parser reads the name from a fixed column, so if the Treasury ever reorders the CSV the wrong column would be compared. That is the most likely way this breaks in future.