Skip to main content
Foil sits alongside your KYC vendor, not in place of it. The vendor checks whether the identity is valid; Foil checks whether a real human at a real device is operating the form. Stacking the two catches fraud that passes each layer individually - especially synthetic-identity farms, anti-detect browsers, and repeat submissions from one device under many identities.

The threat

KYC-gated flows (fintech onboarding, crypto exchanges, gambling, lending, payouts, any “high-limit” account upgrade) attract some of the most well-funded browser automation on the public internet. The rewards per account are large enough to justify real investment: stolen identity kits bought on dark markets, anti-detect browsers ($100–$500/month retail), residential proxy pools, and increasingly LLM-driven agents that can fill forms and respond to vendor prompts autonomously. Four abuse patterns dominate:
  • Synthetic identities. Fabricated or Frankenstein PII (real SSN, fake name; real name, fake DOB) submitted through automated browsers. Individual submissions may pass the KYC vendor’s checks because the underlying data points look plausible; the tell is the browser operating the form, which Foil sees.
  • Identity farming. Operator runs dozens or hundreds of accounts through KYC on one device using different PII each time, then resells the verified accounts. Every individual submission looks fine; the visitor fingerprint betrays the pattern.
  • Anti-detect browser fraud. Anti-detect browsers exist specifically to defeat device fingerprinting, and fraud operations use them heavily. These environments push Foil’s overall risk_score up even when the behavioral profile looks human, and the stored session’s runtime_integrity object reports tampering and virtualization levels.
  • Liveness / video bypass. Virtual cameras, deepfakes, and face-swap tooling target the selfie/liveness step. This is primarily the KYC vendor’s problem, but the browser environment used to run the bypass often trips Foil’s virtualization and anti-tamper signals.
Against all four, Foil produces signal before the KYC vendor sees the request - cheap, high-precision, and useful in two ways: as a pre-check that stops obvious fraud before paying the vendor per-verification fee, and as an input to a layered decision for everything the vendor returns as “approved but we’re not sure.”

The flow

1

Start Foil when the KYC flow begins

This might be the “Upgrade your account” button or the start of a dedicated onboarding journey. KYC users spend time entering their details, which gives Foil time to collect evidence before they submit.
2

Call getSession() at submit

The handoff travels with the KYC payload. getSession() waits for fingerprinting to finish, so the token carries a visitor_fingerprint.id for cross-session correlation.
3

Pre-check the Foil verdict and risk score

Hard-block on bot. For human verdicts, apply a stricter risk_score threshold than you would on a lower-friction surface - a borderline score on a regulated flow usually warrants manual review.
4

Check the fingerprint's history

GET /v1/fingerprints/:visitorId - has this device run KYC before under a different identity?
5

Forward to the KYC vendor with Foil metadata attached

So the vendor’s own decision engine can incorporate the signal on their side.
6

Monitor post-KYC sessions

A verified account that starts behaving like automation later is a strong account-takeover or farming signal. Keep Foil on sensitive in-app actions.

Client integration

Start Foil when the KYC flow begins and call getSession() at submit. getSession() waits for fingerprinting to finish before it returns a handoff, so the token carries the visitor ID that the identity-farming checks below rely on. You don’t need to disable the submit button or call waitForFingerprint() first.
If the script fails to load or getSession() fails, the page sends the submission without a handoff and the server rejects it, so the user sees an error instead of an unresponsive form.

Server verification

The pattern below uses a generic kycVendor.verify(...) placeholder. Substitute your identity-verification provider or your internal equivalent. The KYC_RISK_SCORE_THRESHOLD below is set at 30 as a starting point - well below Foil’s default bot verdict threshold (~70) but strict enough that genuinely suspicious sessions don’t auto-approve on a regulated flow. risk_score is an integer from 0 to 100; tune the threshold to your traffic.
The server SDKs don’t check a token’s age. Reject a token whose decision.evaluated_at is more than a few minutes old, so that an attacker can’t reuse a token captured from one session for another submission. See Verify the token on your server for the check in each language. Store the session_id from the verified token with the KYC record, as the examples do, rather than the sessionId that the browser sent.

Layered decision matrix

The decision you hand back to your own business logic (and record on the KYC row) should be the product of Foil and the vendor verdict, not either one alone. risk_score is an integer 0–100. A human verdict always has a risk_score below 40, and a bot verdict starts at 70, so there’s a lot of useful gradient below the bot threshold, and KYC is the surface where you should care about it. Two principles worth holding:
  • Never auto-approve on vendor-only signal for a KYC-gated flow. If Foil’s risk_score is elevated or the verdict is inconclusive, the vendor saying “approved” is not enough to promote an account to its full permission set.
  • Use a stricter risk_score threshold than you would on other surfaces. Foil’s default bot/human cutoff is tuned for broad use across signup, login, and content surfaces; KYC’s false-negative cost justifies treating anything above roughly 30 as worth a human look.

Runtime integrity: deeper environment signal

When the sealed-token verdict isn’t enough - high-limit approvals, compliance-heavy jurisdictions, repeat-review queues - fetch the stored session and inspect runtime_integrity. Each field is a level that Foil computes server-side from the full observation stream, so you can write precise rules instead of thresholding a single aggregated score. A field is clean when Foil found no evidence, and it rises through notice and elevated to high_risk as the evidence gets stronger.
Node.js
tampering, emulation, and identity_spoofing at elevated or high_risk deserve a manual-review route even when the sealed-token verdict is human and the risk_score is under threshold. They’re the signals most likely to be silent on the aggregated score when an attacker is specifically tuning against it.
Node.js
Signed bot identity isn’t part of runtime_integrity. When a request carries a valid Web Bot Auth signature, the session detail reports it in the top-level web_bot_auth object (status and domain), and the sealed token carries a trust label with the value verified. This is a positive signal: the session is a self-identified, cryptographically attested bot, which usually explains an automated verdict rather than pointing to fraud. KYC flows generally still don’t accept these submissions, but you can reject them without an investigation. runtime_integrity is only available via GET /v1/sessions/:sessionId - it’s not on the sealed token. Two integration shapes work well:
  • Session readback on every KYC submission. One extra API call per verification; negligible next to the cost of the KYC vendor itself.
  • Session readback only on borderline cases. Fast path on clean human + low-score submissions; fetch the stored session for everything else, including the manual_review paths in the code above.

Identity farming: cross-session correlation

The visitor fingerprint is the axis that catches farms. One device running KYC under five identities over two weeks looks human on every individual submission - it is human, just a human engaged in fraud. The tell is in GET /v1/fingerprints/:visitorId.
Node.js
Practical thresholds for flagging:
  • 2+ distinct accounts from the same visitor fingerprint completing KYC → manual review. Real households exist, but KYC-gated flows rarely involve two adults on the same device.
  • Any prior verdict: "bot" in that fingerprint’s activity.sessions → manual review. The device has failed Foil before. activity.sessions holds only the device’s latest 100 sessions, so on a very active device it may not reach back as far as you want. Filter on decision.evaluated_at if you need a fixed window such as 90 days.
  • lifecycle.seen_count > 50 with first-seen under 24 hours → auto-reject. That’s a headless browser being spun up repeatedly on one machine.
See Promo & trial abuse for a more general treatment of the cross-session correlation pattern; the KYC version is essentially that page with tighter thresholds and a manual-review default.

Attaching Foil signal to the vendor

Most KYC vendors expose a metadata or custom_fields parameter on their verification API. Putting Foil’s session ID and verdict there pays off in two ways:
  • Vendor-side rules. Many KYC platforms let you write decisioning rules on custom fields. “Decline if foil_risk_score >= 30 AND document score below 0.9” is a one-line rule that would otherwise require integrating Foil into the vendor’s system separately.
  • Post-hoc reconciliation. When a chargeback, SAR, or compliance audit surfaces a KYC approval months later, the Foil session ID stamped on the vendor record lets you pull GET /v1/sessions/:sessionId and recover the device evidence from the time of submission, including runtime_integrity. The signals_fired list is empty unless raw signal access is enabled for your organization.
Which fields to send:
foil_attribution_category is the value of the label whose kind is actor in attribution.bot.labels. A session can have no actor label, particularly when the verdict is human, so for consistency store "human" in that case and "unknown" when the verdict is bot or inconclusive.

Post-KYC monitoring

Getting a human verdict at KYC submission is necessary but not sufficient. Account takeover happens later - the KYC is clean, but six months on, someone logs in from a new device, hardens their settings, and starts draining the account or withdrawing funds. Keep Foil on:
  • Every login (see Login & credential stuffing).
  • Payout / withdrawal / transfer initiation.
  • Limit-increase requests (the typical next step after compromise).
  • Any sensitive setting change - email, password, 2FA, beneficiary.
A KYC-verified account that suddenly starts producing bot verdicts or an elevated risk_score on withdrawals is a very strong ATO signal, and the fact that the account is KYC-verified makes the response more important, not less - the attacker has done the work to look legitimate precisely to unlock high-limit actions.
KYC is a regulated flow in most jurisdictions. How long you retain Foil verdicts and visitor fingerprints on KYC records is a compliance question, not just an engineering one. Confirm retention requirements with your privacy/legal team before you ship. See Privacy & data for Foil’s side of the data picture.

What’s next

Signup protection

Stop automated account creation before KYC even starts.

Promo & trial abuse

The same cross-session correlation pattern, at lower stakes.

Verdicts & scoring

Deep-dive on verdict, risk_score, and where thresholds come from.

Privacy & data

What Foil stores and for how long.