Block trades from sanctioned countries
Your web app lets users place trades. Your obligations say a trade must not go through when the user is in certain countries. On this page, your backend fetches the verdict for the user's session when they submit a trade, and applies that rule.
What you need
- Your edge running, and the collector on your trade page. The Quickstart sets up both.
- A read token. See Credentials.
verify-verdict.mjsfrom Verify the Signed Token.
1. Collect while the trade page is open
When your server renders the trade page, it creates a sessionRef, gets a one-time challenge for it from Octet, and keeps the sessionRef against the user's session. It writes EDGE_URL, sessionRef and challenge into the page, as the Quickstart does. The page starts the collector, and waits for the collection to finish before it submits the trade:
octet.start({ apiUrl: EDGE_URL, sessionRef, challenge });
const form = document.getElementById('trade');
form.addEventListener('submit', async (event) => {
event.preventDefault();
try { await octet.ready(); } catch (err) { console.warn('octet', err); }
form.submit();
});
2. Decide on your backend
When the trade arrives, look up the sessionRef you stored for this user's session. Fetch the verdict, verify its token, and decide from the verified claims:
import { verifyVerdictToken } from './verify-verdict.mjs';
// Example list only. Build yours from your own obligations,
// and list every territory code your rule covers.
const BLOCKED = new Set(['IR', 'KP']);
async function tradeDecision(sessionRef) {
let claims;
try {
// fetchVerdict is the function from the Quickstart.
const verdict = await fetchVerdict(sessionRef);
if (!verdict) return 'hold'; // no verdict for this session
claims = await verifyVerdictToken(verdict.token, sessionRef);
} catch (err) {
return 'hold'; // the fetch failed, or the token didn't verify
}
const { country, alarm } = claims;
if (alarm === 'medium') return 'hold'; // masked: country unreliable
if (country === undefined) return 'hold'; // no country
if (BLOCKED.has(country)) return 'block'; // on your list
if (alarm === 'high') return 'hold'; // masked
return 'allow';
}
hold stands for your own next step, such as a KYC check or a manual review. The order of the checks matters:
- At
medium, the connection appears masked andcountryis not reliable, so the trade is held whatevercountrysays. - At
high,countryis still the country the user is in, so the list check runs before the masked check. A user in a listed country is blocked, and any otherhighsession is held. - An absent
countrymeans Octet could not determine one. Treat it as unknown, not as a pass.
Decide from the verified claims, not from the unsigned fields beside the token.
The order of the checks is a policy choice: at medium, this draft holds the trade even when country is on the list. Confirm that is the example we want to show.
What each case returns
| The session | country |
alarm |
Decision |
|---|---|---|---|
| A user in Germany | DE |
none |
allow |
| A traveller, or a device set up for another country | the country Octet found | low |
allow, unless country is on your list |
| A user on a VPN or proxy | not reliable | medium |
hold |
| A user in a country on your list | for example IR |
none, low or high |
block |
| Octet could not determine a country | absent | any | hold |
| No verdict for the session, or the token didn't verify | hold |
country is a country-level result. A session in a sanctioned region inside a country, such as Crimea, comes back as UA. If your obligations cover a region like that, don't rely on the verdict alone. See Sanctioned regions.
3. Keep a record
Store the token with each decision, so an auditor can check later what your backend saw. See Keep Verdicts for Audit.
Related
- Verdicts: how to read
country,confidenceandalarm. - Fetch the Verdict: the request, the responses and the timeouts.
- Go-Live Checklist.