t.js, call start(), and call getSession() when the user performs a sensitive action.
Load and initialize
Start Foil as early as possible on your page. The SDK begins collecting signals immediately.API reference
Module exports
Foil.start(options)
publishableKeyis required. Pass apk_live_*orpk_test_*key. Secret keys (sk_*) are rejected.start()is idempotent for the same key - calling it again with the samepublishableKeyresolves to the same client without re-bootstrapping. Calling it with a different key rejects withconfig.already_initialized.- On success, signal collection has started. You don’t need to await anything else before calling
getSession()at action time - the SDK will flush whatever it has collected. - Rejections are
FoilErrors (see Error codes):config.validation_failed,config.already_initialized,runtime.bootstrap_failed,runtime.integrity_failed,runtime.start_failed,runtime.start_timeout, or atransport.*code when Foil can’t create a session. - A rejection with
retryable: trueapplies to that attempt only. It usually means the network failed or Foil was unreachable while the session was being created. Callstart()again, for example when the user submits, andgetSession()retries the session.
FoilClient
getSession()
Flushes pending observations and returns a sealed handoff for your backend.
- Resolves with
{ sessionId, sealedToken }. Every call produces a freshsealedToken; thesessionIdstays the same for the lifetime of the client. - Safe to call multiple times. Calling it twice - e.g. on submit retry - is fine; just send the most recent handoff.
- Concurrent calls are coalesced: if you issue two
getSession()calls before the first resolves, both receive the same handoff. - Tokens aren’t single use. Verification is stateless: the server SDKs decrypt a token, but they don’t record it or check its age, so the same token verifies every time you submit it. To reject repeated submissions, record the
decision.event_idof each token you accept (it’s unique to each token) or thesession_idif you want one action per session. See What verification doesn’t check. - You do not need to
await waitForFingerprint()first.getSession()waits for fingerprinting to finish before it returns, and every handoff carries avisitor_fingerprint. If fingerprinting can’t finish,getSession()rejects instead of returning a handoff without one. - Rejects with a
FoilError:runtime.unavailablewhen called beforestart()resolved or afterdestroy(),runtime.session_failed(retryable: true) when the handoff request fails or takes longer than 15 seconds, or atransport.*or API error code when the Foil API rejects the request. See Error codes.
waitForFingerprint()
Resolves when fingerprinting has completed.
- Optional.
getSession()waits for fingerprinting on its own, so you never need this call to get a visitor ID in the handoff. Its only use is to surface a fingerprinting error earlier thangetSession()would. - No identity data is returned to the browser; the fingerprint ID is only visible server-side in the verified sealed token.
- Typical fingerprint resolution completes within a few hundred milliseconds. Don’t block your form submission on it - kick it off after
start()and usegetSession()at action time regardless. - Safe to call in parallel with
getSession(). - Rejects with
runtime.fingerprint_failed(retryable: true,fatal: false), including when fingerprinting doesn’t finish within 10 seconds. Foil doesn’t score a session without a fingerprint, sogetSession()can’t succeed until fingerprinting does. Retrying after a transient failure can succeed.
onError(handler)
Subscribes to FoilError events emitted by the runtime.
- Returns an unsubscribe function:
const off = foil.onError(fn); /* later */ off();. - If a fatal error has already occurred before you attach the handler, it is replayed on the next microtask so you don’t miss it.
- The
handlerreceives the error as aFoilErrorPayload(thetoJSON()shape ofFoilError), which doesn’t includecause. Exceptions thrown from your handler are swallowed to preserve event-emitter behavior - don’t rely on them propagating. onErrorreports the errors that stop the runtime, which are failures during load andstart(). Failures fromgetSession()andwaitForFingerprint()reject those promises and aren’t sent toonError.- Use this for logging and observability. For control flow,
awaitthe promise fromstart()/getSession()/waitForFingerprint()and catch rejections.
destroy()
Stops timers and releases resources.
- Terminal. After
destroy()the client cannot be restarted - discard the reference. Any subsequentgetSession()/waitForFingerprint()calls reject withruntime.unavailable. - Useful for SPAs that navigate away from a Foil-protected surface, or for tests that need to tear down between cases.
- Synchronous and best-effort - in-flight network requests may still complete in the background.
Getting a session handoff
CallgetSession() right before the protected action - not on page load.
Error handling
All async methods reject with a structuredFoilError. onError handlers receive the same shape, without cause, for errors that stop the runtime.
Error codes
The
code values above are stable, and the SDK can add new codes within a category. Branch on retryable and fatal for control flow, and use the category prefix for logging. A rejection can also carry an error code from the Foil API itself, such as auth.origin_not_allowed; see API errors for those codes.
Fallback policy
If Foil fails, your app should degrade gracefully. The pattern below treats any error that a retry can’t fix as a skip - you get no signal, but the user can still complete the action.FoilError envelope as returned by the Foil API itself, see API errors.
Best practices
- Start early - initialize on page load, not at action time
- Call
getSession()late - right before the sensitive action for the freshest signal data - Handle errors gracefully - if the SDK fails, your app should still work (degrade to a fallback policy)
- Don’t expose verdicts client-side - the browser API intentionally doesn’t return them
What’s next
- Server verification - verify the handoff on your backend
- Browser compatibility - supported browsers and known differences
- Quickstart - end-to-end integration in 5 minutes