SenseJS
Overview
SenseJS runs in the shopper browser during checkout. It collects device signals — fingerprint, interaction events, and form data — and stores them under a senseKey that the merchant backend forwards on the /v1/evaluate call. FlexFactor uses these signals to correlate device identity to a transaction and improve risk scoring.
SenseJS does not authorize transactions by itself. It only collects signals. The scoring and rescue decision happen server-side at /v1/evaluate.
Scope of this guide
This guide covers the SenseJS widget. The FlexFactor UI Widget (the cardholder challenge popup that renders when
/v1/evaluatereturnsCHALLENGE) is documented separately. SenseJS alone does not handle the challenge UI.
Embedding SenseJS
Paste this snippet on every page of the checkout funnel where signal collection should run. At minimum, include it on the cart, checkout, and confirmation pages. MID and siteId are issued per merchant by FlexFactor and can be retrieved from the portal under Developers > Sites.
| Environment | Portal URL |
|---|---|
| Sandbox | https://portal-sandbox.flexfactor.io/developers/sites |
| Production | https://portal.flexfactor.io/developers/sites |
<script>
var scriptId = 'fc';
var scriptSrc = 'https://sensejs.flexfactor.io/widget.js';
var MID = '{MERCHANT_GUID}';
var siteId = '{SITE_GUID}';
(function (w, d, s, o, f, js, fjs) {
w['JS-Widget'] = o;
w[o] = w[o] || function () { (w[o].q = w[o].q || []).push(arguments) };
js = d.createElement(s); fjs = d.getElementsByTagName(s)[0];
js.id = o; js.src = f; js.async = 1; fjs.parentNode.insertBefore(js, fjs);
}(window, document, 'script', scriptId, scriptSrc));
fc('init', MID, siteId);
</script>How it works:
- The IIFE injects
widget.jsand exposes a queueing stub atwindow.fc. Any call made before the bundle finishes loading — including the immediatefc('init', MID, siteId)— is queued and replayed automatically once the widget initializes. initmust be called exactly once per page. Subsequent calls are silently ignored after the widget has started.- Including the snippet twice on the same page is safe (the widget guards against double-initialization) but should still be avoided.
Content Security Policy
If your site uses CSP headers, add
*.flex-charge.comto theconnect-srcdirective:Content-Security-Policy: connect-src 'self' https://*.flex-charge.com;
Binding an Order
SenseJS needs to join the device session to the merchant order so FlexFactor can correlate signals to a transaction at /v1/evaluate. Three patterns exist. Choose based on when the order id becomes known in your checkout flow.
Option A — setOrderId (preferred)
setOrderId (preferred)Use when the checkout page already knows the merchant order id before the shopper submits. This is the correct choice for server-rendered order ids and for order ids generated client-side on page load.
Call setOrderId on load, then include both externalOrderId and senseKey in the checkout payload your frontend sends to your backend on submit.
<script>
window.addEventListener('load', function () {
window.fc.setOrderId({ externalOrderId: 'your-order-id' });
});
</script>The FlexFactor record is keyed by the same identifier your system uses — no translation layer needed for support lookups or reconciliation.
Option B — getSenseKey only (server-side order creation)
getSenseKey only (server-side order creation)Use when the order id does not exist at page load because your backend mints it after the shopper submits.
Skip setOrderId entirely. On submit, read senseKey from the widget and post it to your backend. The backend then calls /v1/evaluate with the newly created order id plus the senseKey.
senseKey alone is sufficient as the join key — FlexFactor looks up device signals by senseKey regardless of whether an order id was set on the page.
Option C — createOrderId (last resort)
createOrderId (last resort)Use only when neither Option A nor Option B is achievable. fc.createOrderId() generates a UUID, binds it to the session, and returns it. Your system must either adopt this UUID as the order identifier or accept that the FlexFactor record carries an order id that does not appear in your native order table.
<script>
window.addEventListener('load', function () {
window.fc.createOrderId();
});
</script>This approach complicates order lookups by order number. Treat it as a fallback for unusual integration constraints, not a default.
Reading senseKey on Submit
senseKey on SubmitRead senseKey immediately before posting the checkout. Use fc.getSenseKey() rather than reading sessionStorage directly — the method falls back to the in-memory global if sessionStorage is unavailable (private-mode browsers, cross-origin iframes).
<script>
function getFlexFactorSenseKey() {
if (window.fc && typeof window.fc.getSenseKey === 'function') {
return window.fc.getSenseKey();
}
try {
return window.sessionStorage.getItem('fc_sk');
} catch (_) {
return null;
}
}
document.getElementById('checkout-form').addEventListener('submit', function (event) {
event.preventDefault();
var payload = collectCheckoutPayload(); // your existing payload builder
payload.senseKey = getFlexFactorSenseKey();
payload.externalOrderId = window.fc && window.fc.externalOrderId;
fetch('/api/checkout', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
});
});
</script>For server-rendered forms (no JavaScript submit handler), populate a hidden input on load:
<input type="hidden" id="ff_sense_key" name="senseKey" />
<script>
window.addEventListener('load', function () {
document.getElementById('ff_sense_key').value = window.fc.getSenseKey() || '';
});
</script>Your backend takes the senseKey and externalOrderId values from the checkout payload and includes them on the /v1/evaluate request body. If senseKey is absent or stale on that call, FlexFactor can still score the transaction but loses the device-signal contribution, which materially degrades rescue performance.
Important:
setOrderIdrotatessenseKeywhenever the order id changes. Always callfc.getSenseKey()immediately before submit — do not cache the value across orders.
End-to-End Flow
sequenceDiagram
participant Browser as Browser<br/>(SenseJS)
participant Backend as Merchant backend
participant FlexFactor as FlexFactor
rect rgb(245, 247, 250)
note over Browser: Page load
Browser->>Browser: fc('init', MID, siteId)
Browser->>Browser: fc.setOrderId({ externalOrderId })
Browser->>Browser: Store senseKey and externalOrderId
end
Browser-->>FlexFactor: Stream device signals automatically
rect rgb(245, 247, 250)
note over Browser,Backend: Checkout submit
Browser->>Browser: senseKey = fc.getSenseKey()
Browser->>Backend: POST /checkout<br/>{ senseKey, externalOrderId, ... }
end
Backend->>FlexFactor: POST /v1/evaluate<br/>{ externalOrderId, senseKey, ... }
FlexFactor-->>Backend: APPROVED, DECLINED, or CHALLENGE (+ orderId)
Backend-->>Browser: Return checkout outcome
API Reference
Initialization (queue-style)
| Command | Arguments | Notes |
|---|---|---|
fc('init', MID, siteId) | MID string, siteId string | Required first call. Triggers fingerprinting and configuration fetch. Missing MID aborts initialization. |
Direct methods (available after init)
init)| Method | Signature | Purpose |
|---|---|---|
fc.setOrderId(props) | ({ orderId?, externalOrderId? }) => string | undefined | Binds the merchant order id to the widget session. Either orderId or externalOrderId is required; if both are present, externalOrderId takes precedence. Rotates senseKey if the order id changed. Returns the current senseKey. |
fc.getSenseKey() | () => string | Returns the current senseKey from sessionStorage (falls back to in-memory global). Use this on submit to read the value to forward to your backend. |
fc.createOrderId() | () => string | Generates a UUID, calls setOrderId internally, and returns the new id. Use only when no merchant-native order id is available on the page. |
Session globals (read-only)
After init, these properties are available on window.fc for debugging and for reading values to forward to your backend:
| Property | Value |
|---|---|
window.fc.senseKey | Current session key (UUID). Mirrored in sessionStorage.fc_sk. |
window.fc.externalOrderId | Last value set via setOrderId or createOrderId. Mirrored in sessionStorage.fc_eoi. |
window.fc.mid | Merchant GUID passed to init. |
window.fc.siteId | Site GUID passed to init. |
window.fc.isStarted | true once the widget has bootstrapped. |
Storage and query string
| Location | Key | Meaning |
|---|---|---|
sessionStorage | fc_sk | Current senseKey. Cleared with the browser tab. |
sessionStorage | fc_eoi | Last externalOrderId bound via setOrderId or createOrderId. |
| Query string | senseKey or fc_sk | If present on page load, overrides the stored fc_sk. Used for cross-domain session stitching. |
What SenseJS Collects
Once initialized, the widget runs the following automatically — no additional merchant calls required:
- Device fingerprint — composite fingerprint computed at startup.
- Interaction events — page-load and user-interaction telemetry streamed to FlexFactor.
- Email capture — listens for email addresses entered into form fields and associates them with the session.
- Form scraping — when configured, binds to merchant-specified form selectors and submits captured values on checkout. Selectors and triggers are configured per-merchant by FlexFactor; no merchant code changes are needed.
- SPA route changes — a
MutationObserveron<body>re-runs signal collection when a single-page application pushes a new route.
These behaviors are controlled by the merchant configuration that FlexFactor provisions per site. They do not require additional opt-in calls from merchant code.
Integration Requirements
- Call
fc('init', MID, siteId)once per page. Embed the snippet on every page of the checkout funnel — at minimum cart, checkout, and confirmation. - Call
fc.setOrderIdorfc.createOrderIdon theloadevent, notDOMContentLoaded. The widget binds its form scrapers afterload; order-id timing affects rotation logic. - Your checkout page must be served over HTTPS.
- Forward both
senseKeyandexternalOrderId(ororderId) on your backend call to/v1/evaluate. The values must match what the page bound — a stale or missingsenseKeyon the evaluate call materially degrades rescue performance. - Share the URL of your test environment with FlexFactor so it can be allowlisted for signal ingestion during testing.
Updated 5 days ago
