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
| Export | Type | Description |
|---|---|---|
start(options) | (options: FoilStartOptions) => Promise<FoilClient> | Bootstrap the runtime and start signal collection. Resolves to a FoilClient. |
version | string | SDK bundle version. Useful for support tickets and telemetry. |
FoilError | class | Error class thrown by all async methods. See Error handling. |
Foil.start(options)
publishableKeyis the only option. 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 fatal
FoilErrors (see Error codes):config.validation_failed,config.already_initialized,runtime.bootstrap_failed,runtime.integrity_failed,runtime.start_failed,runtime.start_timeout.
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. - You do not need to
await waitForFingerprint()first. If fingerprinting hasn’t resolved, the handoff still works — the verified server-side result just won’t include a durablevisitor_fingerprint. - Rejects with
runtime.unavailable(called beforestart()resolved or afterdestroy()) orruntime.session_failed(network or server-side rejection,retryable: true).
waitForFingerprint()
Resolves when fingerprinting has completed — useful when you want the handoff to carry a durable visitor ID.
- Optional. 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). A subsequentgetSession()call may still succeed without a fingerprint.
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). Exceptions thrown from your handler are swallowed to preserve event-emitter behavior — don’t rely on them propagating. - 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. The same shape is passed to onError handlers when the runtime reports a non-thrown error.
| Field | Type | Description |
|---|---|---|
code | string | Stable, machine-readable identifier. Branch on this — see the code reference below. |
message | string | Human-readable explanation. Safe to log; do not pattern-match. |
retryable | boolean | Whether retrying the failing operation may succeed. |
fatal | boolean | Whether the runtime is in a terminal state — further calls will reject with the same error. |
operation | 'start' | 'wait_for_fingerprint' | 'get_session' | Which method rejected. |
status | number, optional | HTTP status from the Foil API when the cause was a server response. |
requestId | string, optional | Foil request ID. Include verbatim in support tickets. |
details | object, optional | Code-specific context, e.g. details.fieldErrors on validation failures. |
Error codes
| Code | Operation | Retryable | Fatal | When it happens |
|---|---|---|---|---|
config.validation_failed | start | no | yes | publishableKey is missing, uses the sk_* secret prefix, or doesn’t match pk_live_* / pk_test_*. details.fieldErrors lists the offending fields. |
config.already_initialized | start | no | yes | start() was called a second time with a different publishable key in the same page. |
runtime.bootstrap_failed | any | no | yes | The WASM bundle failed to load or threw during bootstrap. Usually a network or CSP issue — see Content Security Policy. |
runtime.integrity_failed | start | no | yes | The WASM integrity check failed. The bundle is tampered with or corrupted. |
runtime.unavailable | any | no | yes | A client method was called before start() resolved, or after destroy(). |
runtime.start_failed | start | no | yes | The runtime rejected during startup. Inspect cause for the underlying reason. |
runtime.start_timeout | start | no | yes | The runtime didn’t signal ready in time. Typically a stalled network request to the Foil API. |
runtime.fingerprint_failed | wait_for_fingerprint | yes | no | Fingerprint resolution failed. A subsequent getSession() may still succeed without a visitor ID. |
runtime.session_failed | get_session | yes | no | The session handoff call rejected. Retry once; if it fails again, fall back to your default policy. |
transport.upgrade_required | any | no | yes | The Foil API rejected the request with HTTP 426 — the bundle on the page is too old. The user must reload to pick up a fresh t.js. |
code values above are stable. Categories (config.*, runtime.*, transport.*) are safe to branch on with a wildcard — new codes within a category will keep the same retry/fatal semantics.
Fallback policy
If Foil fails, your app should degrade gracefully. The pattern below treats anyfatal error 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