{"slug": "check-the-payee-before-your-ai-agent-pays-it-x402-node-and-python", "title": "Check the payee before your AI agent pays it (x402, Node and Python)", "summary": "A developer published an open-source pattern, with Node and Python examples, for screening an x402 payee address against OFAC SDN, UN, EU and UK sanctions lists before an AI agent sends USDC. The code calls a paid screening endpoint (0.01 USDC per call over x402 on Base or Solana) and treats any response that is not an explicit clean verdict — including a 400, 5xx, timeout, or an unavailable OFAC feed — as a refusal to pay. The author notes their first draft incorrectly returned \"pay\" when the OFAC feed was down, calling it \"the worst possible failure for a check like this.", "body_md": "An AI agent that pays over x402 reads a `payTo` address from a 402 response and sends USDC to it. Some agent wallets already let the owner set a daily budget, a per transaction limit and approved domains. A check on the address itself is a separate question: is the wallet you are about to pay on a sanctions list?\n\nThis post shows a small pattern for that, with working Node and Python examples. The full code is here: [https://github.com/Nikoble1926/sanctions-check-before-pay](https://github.com/Nikoble1926/sanctions-check-before-pay)\n\nOne paid call screens a wallet address against four lists: OFAC SDN, UN Consolidated, EU and UK. The response is signed and reports each list separately, matched or clear, with that list's version date. It costs 0.01 USDC per call, paid over x402 itself, on Base or Solana. No account, no API key.\n\n```\nGET https://sanctions.nsgoods.org/screen-multi?address=<payTo>&chain=<optional hint>\n```\n\nThere is a free signed sample of the exact response shape:\n\n```\nGET https://sanctions.nsgoods.org/screen-multi/preview\n```\n\nThe response has a headline `verdict` (`clean`, `deny` or `indeterminate_ofac_unavailable`) and an `any_list_match` flag that covers all four lists. The headline verdict on its own only speaks for OFAC. So the decision uses both, and it treats everything that is not an explicit clean answer as a no:\n\n``` js\nexport const decide = (status, d) =>\n  status === 200 && !!d && d.verdict === \"clean\" && d.any_list_match === false;\n```\n\n`deny`, `indeterminate_ofac_unavailable`, a 400, a 5xx, a timeout or a body that is not JSON all mean: do not pay. We had this wrong in our first draft. The script said \"pay\" when the OFAC feed was unavailable, which is the worst possible failure for a check like this. A check that cannot run must never turn into a yes.\n\nNode 20 or newer:\n\n```\nnpm install\nEVM_PRIVATE_KEY=0x... node check-before-pay.mjs <payTo address> [chain]\n```\n\nPython:\n\n```\npip install -r requirements.txt\nEVM_PRIVATE_KEY=0x... python check_before_pay.py <payTo address> [chain]\n```\n\nExit code 0 means clean, go ahead. Any other exit code means do not pay, so you can drop it in front of the payment step of an agent or a script. Without `EVM_PRIVATE_KEY` both scripts stop at the 402 and print the price, so you can try them without a wallet. Use a low balance hot wallet for the key, never your main one.\n\nTwo things we only found by testing on a clean machine, in case they save you time:\n\n`@x402/axios`, the payment happens when the first 402 comes back as an error. If you set `validateStatus: () => true` on the paying client, the 402 counts as a success and the client never pays. The example uses `validateStatus: s => s !== 402`, so 400 and 5xx still do not throw.` web3` installed (the `x402[evm]` extra). Without it, the import fails before anything runs. The `requirements.txt` in the repo pins it.\nThe response is signed, so you do not have to trust the transport:\n\n```\nnode verify-signature.mjs\n```\n\nIt fetches the free preview and recovers the signer offline. The service signs the body without `signed_by` and `signature`, as compact JSON with sorted keys and non ASCII characters escaped, with an Ethereum personal_sign. Expected signer: `0x57fF0F084Cba33e6761503f90eEF0Da9F159350c`. The one trap here: the escaping. JavaScript's `JSON.stringify` leaves non ASCII characters as they are, so the verifier escapes them by hand to match.\n\nNot legal advice. Passing the rule means the address is not on these four lists at their stated version dates, nothing more. It says nothing about who controls the wallet or where its funds came from.\n\nCode, MIT licensed: [https://github.com/Nikoble1926/sanctions-check-before-pay](https://github.com/Nikoble1926/sanctions-check-before-pay)\n\nMore about the service: [https://x402.nsgoods.org](https://x402.nsgoods.org)", "url": "https://wpnews.pro/news/check-the-payee-before-your-ai-agent-pays-it-x402-node-and-python", "canonical_source": "https://dev.to/nikoble1926/check-the-payee-before-your-ai-agent-pays-it-x402-node-and-python-671", "published_at": "2026-10-07 14:37:09+00:00", "updated_at": "2026-10-07 14:47:01.054570+00:00", "lang": "en", "topics": ["ai-agents", "ai-tools", "ai-infrastructure"], "entities": ["x402", "OFAC", "USDC", "Base", "Solana", "nsgoods.org", "GitHub"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/check-the-payee-before-your-ai-agent-pays-it-x402-node-and-python", "markdown": "https://wpnews.pro/news/check-the-payee-before-your-ai-agent-pays-it-x402-node-and-python.md", "text": "https://wpnews.pro/news/check-the-payee-before-your-ai-agent-pays-it-x402-node-and-python.txt", "jsonld": "https://wpnews.pro/news/check-the-payee-before-your-ai-agent-pays-it-x402-node-and-python.jsonld"}}