Session + Onboarding Guide

Overview

Introduction

Trustfull Session and Trustfull Onboarding address different layers of fraud. Session analyzes the device and browser environment to detect bots, automation, and spoofing. Onboarding cross-references phone, email, and IP signals to verify identity consistency. Used together, they create a layered defense that catches what neither solution can detect alone.

This guide explains how to integrate both products into your signup or registration flow, with two approaches depending on your cost and data requirements.

Want to learn how each product works individually? Visit the Trustfull Session and Trustfull Onboarding product guides.

Below, this guide covers:

  1. Why combining both products catches more fraud
  2. Prerequisites and authentication setup
  3. Two integration approaches: Waterfall (sequential) and Combined (parallel)
  4. How to combine scores and build decision logic
  5. Cross-layer risk signals to watch for

Why Combine Both Products?

Each product targets a different class of fraud:

ThreatSessionOnboarding
Bots and automated signups
Device spoofing and emulators
AI agents (ChatGPT, etc.)
Headless browsers
Synthetic identities
Stolen phone/email combinations
Disposable phone numbers
Cross-signal inconsistency (email ↔ phone ↔ name)
VPN / Proxy / Tor masking

Session validates that the device and browser environment are legitimate. Onboarding validates that the identity behind the device is real. Combining both layers makes fraud significantly harder to scale.

Prerequisites

Before starting, make sure you have:

  • A Trustfull account with both Session and Onboarding products enabled
  • Your JS key for the Session SDK (client-side, starts with TFF- or TFB-)
  • Your API key for backend calls (server-side, used in the x-api-key header)
⚠️

The JS key and the API key are different credentials. The JS key is safe to expose in frontend code. The API key is a shared secret between your backend and Trustfull and must never be exposed in the browser.

You can find both keys in the App section of your Trustfull dashboard. The JS key appears in the SDK snippet that is pre-filled for you.

How It Works

The integration follows three phases:

  1. Collect — The Session JavaScript SDK runs in the user's browser, silently capturing device fingerprint, browser signals, and behavioral data (mouse movements, interaction timing). This happens with zero friction.
  2. Score — Your backend calls the Trustfull APIs to get risk assessments. Session analyzes the device/browser environment. Onboarding cross-references phone, email, IP, and name signals.
  3. Act — Based on the combined risk picture, your system routes the user through the appropriate flow: approve, step-up verification, or decline.

Session and Onboarding are independent products with separate API endpoints. Your backend is responsible for calling both and combining their results.


Integration Approaches

Trustfull supports two strategies. Choose the one that fits your risk appetite and budget.

Approach 1: Waterfall (Sequential)

Run Session first. Only call Onboarding if the session score falls in the review zone. This avoids unnecessary identity verification costs on traffic that is clearly legitimate or clearly fraudulent.

When to use: Standard signup flows where cost optimization matters. Most customers find that 60–70% of sessions score clearly high or clearly low, meaning Onboarding only needs to run for the remaining 30–40%.

Step 1: Add the Session SDK to your signup page

Place the snippet as early as possible in the page so the SDK has time to collect behavioral signals before the user submits the form.

<script>
    (function (f, i, d, o, c, od, e) {
        f['FidoObject'] = c;
        f[c] = f[c] || function () {
            (f[c].q = f[c].q || []).push(arguments);
        }, f[c].l = 1 * new Date();
        od = i.createElement(d),
            e = i.getElementsByTagName(d)[0];
        od.async = 1;
        od.src = o;
        e.parentNode.insertBefore(od, e);
    })(window, document, 'script', 'https://det.trustfull.com/det.js', 'tfbd');

    tfbd('create', 'your-js-key');
</script>
⚠️

Replace 'your-js-key' with the JS key provided to you.

Step 2: Trigger session data collection on form submit

When the user submits the signup form, generate a unique session ID, send the collected fingerprint to Trustfull, then forward the session ID to your backend along with the form data.

const form = document.getElementById("signup-form");
form.addEventListener("submit", async (ev) => {
    ev.preventDefault();

    // Generate a unique session ID (max 125 characters)
    const sessionId = crypto.randomUUID();

    // Send session fingerprint to Trustfull
    await tfbd.sendRecord(sessionId);

    // Submit form data + sessionId to your backend
    await fetch('/api/signup', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
            phone: document.getElementById('phone').value,
            email: document.getElementById('email').value,
            first_name: document.getElementById('first_name').value,
            last_name: document.getElementById('last_name').value,
            session_id: sessionId
        })
    });
});
⚠️

If the SDK fails to load (e.g., the user has a script blocker or ad blocker), tfbd.sendRecord() will not be available. Wrap it in a try/catch and decide whether to fail open (proceed without session data) or fail closed (block the signup) based on your risk tolerance.

Step 3: Backend — Check session score, then decide

Your backend retrieves the session score. If the session is clearly trusted or clearly risky, it can act immediately. For borderline sessions, it calls the Onboarding API for identity verification.

Session APIGET https://api.fido.id/1.0/session/result/{sessionId}

import requests
import time

TRUSTFULL_API_KEY = "your-api-key"
TRUSTFULL_BASE = "https://api.fido.id/1.0"


def handle_signup(form_data, session_id):
    # 1. Retrieve session results
    session = get_session_result(session_id)

    if session is None:
        # Session SDK did not run — decide based on your policy
        return {"decision": "review", "reason": "No session data available"}

    # 2. Route based on session score cluster
    if session["score_cluster"] in ("high", "very_high"):
        return {"decision": "approve", "method": "session_only"}

    if session["score_cluster"] in ("very_low", "low"):
        return {"decision": "block", "reason": "Session risk too high"}

    # 3. Review zone — run Onboarding for identity verification
    onboarding = run_onboarding(form_data)
    return combine_decision(session, onboarding)

The Session API returns the enriched data when ready. If the enrichment is still in progress, it returns a processing status. Poll until the result is available:

def get_session_result(session_id, max_attempts=10):
    """Poll the Session API until enrichment completes."""
    for _ in range(max_attempts):
        resp = requests.get(
            f"{TRUSTFULL_BASE}/session/result/{session_id}",
            headers={"x-api-key": TRUSTFULL_API_KEY},
        )

        if resp.status_code == 404:
            return None  # Session not found — SDK may not have run

        data = resp.json()

        if data.get("status") == "processing":
            time.sleep(1)  # Enrichment still running
            continue

        return data  # Enrichment complete

    return None  # Timed out waiting for enrichment

Onboarding APIPOST https://api.fido.id/1.0/hub

The Onboarding API accepts a customer_id (your unique identifier for the user) and a list of claims specifying which products to run. The score claim produces the cross-product onboarding score and requires at least 2 other claims.

def run_onboarding(form_data):
    """Run Onboarding enrichment."""
    resp = requests.post(
        f"{TRUSTFULL_BASE}/hub",
        headers={
            "x-api-key": TRUSTFULL_API_KEY,
            "Content-Type": "application/json",
        },
        json={
            "customer_id": form_data["customer_id"],
            "claims": ["phone", "email", "ip", "name", "score"],
            "phone_number": form_data["phone"],
            "email": form_data["email"],
            "ip": form_data["ip"],
            "first_name": form_data.get("first_name", ""),
            "last_name": form_data.get("last_name", ""),
        },
    )
    return resp.json()

Available claims: phone, email, ip, name, domain, tax_id, vat_code, browser, device, score. Include whichever claims match the data you collect from your users.

Waterfall: Pros and cons

AspectDetail
✅ Lower costOnboarding only runs when needed
✅ Fewer API callsClearly good/bad sessions skip Onboarding
✅ Fast for trusted usersHigh-trust sessions resolve immediately
⚠️ Higher latency for review zoneSequential calls add up for borderline users
⚠️ Less data on skipped sessionsSessions that bypass Onboarding have device signals only

Approach 2: Combined (Parallel)

Run Session and Onboarding simultaneously for every user. Both APIs enrich in parallel, giving you the fullest possible risk picture.

When to use: High-value transactions, regulated industries, or flows where maximum fraud detection accuracy justifies the additional cost.

Steps 1 and 2 are identical to the Waterfall approach: add the Session SDK to your page and trigger sendRecord on form submit.

Step 3: Backend — Run both APIs in parallel

Instead of checking the session score first, your backend fires both requests at the same time and combines their results once both complete.

from concurrent.futures import ThreadPoolExecutor


def handle_signup_combined(form_data, session_id):
    with ThreadPoolExecutor(max_workers=2) as pool:
        session_future = pool.submit(get_session_result, session_id)
        onboarding_future = pool.submit(run_onboarding, form_data)

        session = session_future.result()
        onboarding = onboarding_future.result()

    if session is None:
        # Session SDK did not run — decide on Onboarding alone
        o_cluster = onboarding.get("score", {}).get("score_cluster", "")
        if o_cluster in ("high", "very_high"):
            return {"decision": "approve", "method": "onboarding_only"}
        return {"decision": "review", "reason": "No session data"}

    return combine_decision(session, onboarding)

Both approaches reuse the same get_session_result, run_onboarding, and combine_decision functions. The only difference is whether the calls happen sequentially or in parallel.

Combined: Pros and cons

AspectDetail
✅ Maximum dataEvery user gets both checks
✅ Fastest overallParallel calls reduce total latency
✅ Strongest fraud detectionCross-layer signals for every session
⚠️ Higher costOnboarding runs for every user regardless

Combining Scores

Both Session and Onboarding return a score on a 0–1000 scale with a score_cluster classification:

Scorescore_clusterScorecard LabelRecommendation
776–1000very_highHighAPPROVE
551–775highGoodAPPROVE
451–550reviewModerateMANUAL REVIEW / APPROVE
226–450lowBadDECLINE
0–225very_lowPoorDECLINE

Curious how scores work? Explore our Scoring Methodology and the Reason Codes that drive them.

When combining both scores, use the following matrix as a starting point:

Session ClusterOnboarding ClusterRecommended Action
very_high / highvery_high / high✅ Approve
very_high / highreview⚠️ Review — good device but identity needs attention
very_high / highlow / very_low🚫 Block — possible stolen device or synthetic identity
reviewvery_high / high⚠️ Step-up — request additional verification
reviewreview⚠️ Review — manual investigation recommended
reviewlow / very_low🚫 Block — multiple risk indicators
low / very_lowAny🚫 Block — bot or automation detected

Here is an example implementation of the combined decision logic:

def combine_decision(session, onboarding):
    """Combine Session and Onboarding results into a single decision."""
    s_cluster = session["score_cluster"]
    o_cluster = onboarding.get("score", {}).get("score_cluster", "")

    trusted = ("high", "very_high")
    risky = ("very_low", "low")

    # Both trusted — approve
    if s_cluster in trusted and o_cluster in trusted:
        return {"decision": "approve"}

    # Either critically risky — block
    if s_cluster in risky or o_cluster in risky:
        return {"decision": "block"}

    # Mixed signals — review
    return {
        "decision": "review",
        "session_score": session["score"],
        "session_cluster": s_cluster,
        "onboarding_score": onboarding["score"]["score"],
        "onboarding_cluster": o_cluster,
        "reason_codes": onboarding["score"].get("reason_codes", ""),
    }
⚠️

These thresholds are starting points. Analyze your historical data to calibrate them to your specific fraud patterns and false positive tolerance. Your Trustfull account manager can help with this process.

Cross-Layer Risk Signals

The most valuable fraud patterns emerge when Session and Onboarding signals are read together.

Red flags to watch

Session detectsOnboarding detectsRisk interpretation
has_automated_browser: trueValid, established identityCredential stuffing or account farming with stolen data
has_spoofed_device: trueDisposable phone + disposable emailFully synthetic fraud operation
ip_is_vpn: trueIP country ≠ phone countryIdentity does not match claimed location
has_ai_agent: trueNew email, no social footprintAI-driven synthetic identity creation
mouse_movement: 0Valid identity, strong historyAPI abuse or bot with stolen credentials
All signals genuinePhone/email are disposableReal device but throwaway identity — potential drop account

Trust signals that reinforce each other

Session detectsOnboarding detectsInterpretation
Real browser, no spoofingConsistent phone + email + IP countryGenuine user on a genuine device
Residential IP, normal interactionsEmail with history, valid phone carrierLow-risk, established identity
Matching user agents, valid video cardName consistent across phone and emailStrong cross-layer trust

Best Practices

  • Load the SDK early. Place the Session SDK script at the top of your page, not just before the submit button. The SDK needs time to collect behavioral signals like mouse movements and interaction timing.

  • Use the real client IP. Pass the user's actual IP address to the Onboarding API (from X-Forwarded-For or your load balancer's equivalent). This ensures IP-based signals are accurate and consistent with what Session observes.

  • Include name when available. Adding first_name and last_name to the Onboarding call enables name-consistency signals across phone and email, which significantly improves synthetic identity detection.

  • Store both responses. Save the full response from both APIs alongside the user record. This creates an audit trail for compliance and helps you tune decision thresholds over time.

  • Start permissive, tighten gradually. Begin with lower thresholds (approve more users) and monitor fraud rates. Gradually tighten as you build confidence in the signal quality for your specific user base.

  • Handle SDK failures. If the Session SDK cannot load (ad blockers, script blockers, network issues), tfbd.sendRecord() will fail. Your frontend should catch this and still submit the form. Your backend should handle the case where session data is unavailable — either by falling back to Onboarding-only or by flagging for manual review.

  • Monitor score distributions. Track the distribution of Session and Onboarding scores for your traffic over time. Unusual shifts may indicate changes in your fraud landscape or integration issues.


📚 Resources