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/evaluate returns CHALLENGE) 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.

<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.js and exposes a queueing stub at window.fc. Any call made before the bundle finishes loading — including the immediate fc('init', MID, siteId) — is queued and replayed automatically once the widget initializes.
  • init must 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.com to the connect-src directive:

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)

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)

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)

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

Read 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: setOrderId rotates senseKey whenever the order id changes. Always call fc.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)

CommandArgumentsNotes
fc('init', MID, siteId)MID string, siteId stringRequired first call. Triggers fingerprinting and configuration fetch. Missing MID aborts initialization.

Direct methods (available after init)

MethodSignaturePurpose
fc.setOrderId(props)({ orderId?, externalOrderId? }) => string | undefinedBinds 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()() => stringReturns 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()() => stringGenerates 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:

PropertyValue
window.fc.senseKeyCurrent session key (UUID). Mirrored in sessionStorage.fc_sk.
window.fc.externalOrderIdLast value set via setOrderId or createOrderId. Mirrored in sessionStorage.fc_eoi.
window.fc.midMerchant GUID passed to init.
window.fc.siteIdSite GUID passed to init.
window.fc.isStartedtrue once the widget has bootstrapped.

Storage and query string

LocationKeyMeaning
sessionStoragefc_skCurrent senseKey. Cleared with the browser tab.
sessionStoragefc_eoiLast externalOrderId bound via setOrderId or createOrderId.
Query stringsenseKey or fc_skIf 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 MutationObserver on <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.setOrderId or fc.createOrderId on the load event, not DOMContentLoaded. The widget binds its form scrapers after load; order-id timing affects rotation logic.
  • Your checkout page must be served over HTTPS.
  • Forward both senseKey and externalOrderId (or orderId) on your backend call to /v1/evaluate. The values must match what the page bound — a stale or missing senseKey on 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.