Skip to main content
Foil’s browser surface is intentionally small. Import 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)

Bootstraps the runtime and begins signal collection. Safe to call on page load.
  • publishableKey is required. Pass a pk_live_* or pk_test_* key. Secret keys (sk_*) are rejected.
  • start() is idempotent for the same key - calling it again with the same publishableKey resolves to the same client without re-bootstrapping. Calling it with a different key rejects with config.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 a transport.* code when Foil can’t create a session.
  • A rejection with retryable: true applies to that attempt only. It usually means the network failed or Foil was unreachable while the session was being created. Call start() again, for example when the user submits, and getSession() retries the session.

FoilClient

getSession()

Flushes pending observations and returns a sealed handoff for your backend.
  • Resolves with { sessionId, sealedToken }. Every call produces a fresh sealedToken; the sessionId stays 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_id of each token you accept (it’s unique to each token) or the session_id if 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 a visitor_fingerprint. If fingerprinting can’t finish, getSession() rejects instead of returning a handoff without one.
  • Rejects with a FoilError: runtime.unavailable when called before start() resolved or after destroy(), runtime.session_failed (retryable: true) when the handoff request fails or takes longer than 15 seconds, or a transport.* 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 than getSession() 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 use getSession() 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, so getSession() 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 handler receives the error as a FoilErrorPayload (the toJSON() shape of FoilError), which doesn’t include cause. Exceptions thrown from your handler are swallowed to preserve event-emitter behavior - don’t rely on them propagating.
  • onError reports the errors that stop the runtime, which are failures during load and start(). Failures from getSession() and waitForFingerprint() reject those promises and aren’t sent to onError.
  • Use this for logging and observability. For control flow, await the promise from start() / getSession() / waitForFingerprint() and catch rejections.

destroy()

Stops timers and releases resources.
  • Terminal. After destroy() the client cannot be restarted - discard the reference. Any subsequent getSession() / waitForFingerprint() calls reject with runtime.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

Call getSession() right before the protected action - not on page load.
The handoff contains:
Every call produces a fresh handoff. The browser never receives verdicts, scores, or visitor IDs.

Error handling

All async methods reject with a structured FoilError. 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.
For server-side error handling and the rest of the 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