Client SDK

About the Client SDK

The Composer Client SDK for JavaScript runs in your users' browsers. It isn't strictly required, but it's highly recommended:

  • It augments your data. The SDK identifies the device and adds context such as device fingerprinting, behavior intelligence, and session intelligence. This improves risk decisions and can be used to produce session replays for fraud investigations.
  • It runs third-party frontend code for you. Many fraud and verification services ship their own frontend code, such as Veriff's JavaScript SDK for ID verification. The Client SDK loads and runs it, so you don't manage it yourself.
  • It handles checkpoint outcomes in real time. When a workflow needs more from the user, such as MFA, the SDK shows that step, and callbacks let you update your UI with the result.

Prerequisites

You will need to obtain a public API key for your application from the Dodgeball developer center. Use the public key in the browser, and never put your secret key in frontend code. For setup instructions, see the Client SDK README. If you don't already have an account, contact support.

The SDK supports Chrome 85, Edge 85, Firefox 79, Safari 14 or later. Internet Explorer is not supported.

Related

The Client SDK works alongside the Server SDKs, which call checkpoints from your backend.

For a runnable reference app, see composer-examples. Its client/web-next app uses the Client SDK and calls the example servers.

Installation

Use npm to install the SDK:

npm install @spreedly/composer-sdk-client

Alternatively, using yarn:

yarn add @spreedly/composer-sdk-client

Loading with a script tag

The SDK is also available from unpkg. Pin the version so a new release can't change your page without you knowing, and copy the integrity value for that version from its release notes so the browser won't run the file if it's ever changed. crossorigin="anonymous" is required for the browser to check it:

<script src="https://unpkg.com/@spreedly/[email protected]/dist/umd/index.js"
        integrity="sha384-..." crossorigin="anonymous"></script>
<script>
  const composer = new Composer('public-api-key...');
</script>

The script sets window.Composer. It also sets window.SpreedlyComposer, which holds every export (SpreedlyComposer.Composer, SpreedlyComposer.LogLevel and so on), in case another script on your page already uses the name Composer.

Content Security Policy

The SDK adds RequireJS and your workflow's integration code to the page as inline scripts. If your page sets a Content-Security-Policy, its script-src must allow them with either:

  • a nonce on the script that loads the SDK, plus 'strict-dynamic', or
  • 'unsafe-inline', plus any host your integrations load scripts from.

A script-src without 'strict-dynamic' or 'unsafe-inline' blocks them, as does Trusted Types enforcement (require-trusted-types-for 'script'), which the SDK doesn't support. In either case the SDK logs an error and finishes starting up without integrations.

connect-src must also allow your apiUrl (https://api.dodgeballhq.com by default).

Configuration

Initialize the SDK once, when your application loads, with your public API key.

import { Composer } from '@spreedly/composer-sdk-client';

const composer = new Composer('public-api-key...', {
  apiUrl: 'https://api.sandbox.dodgeballhq.com',
  logLevel: 'ERROR'
});

In React, use the useComposer hook. It creates the instance the first time it's called with your public API key, and returns the same instance everywhere after that:

import { useComposer } from '@spreedly/composer-sdk-client';
import { useEffect } from 'react';

function App({ session, user }) {
  const composer = useComposer('public-api-key...'); // Once, in your root component

  useEffect(() => {
    composer.track(session?.id, user?.id);
  }, [session?.id, user?.id]);
  // ...
}

function Checkout() {
  const composer = useComposer(); // Once initialized, you can omit the public API key
  // ...
}

Every option is optional.

OptionDefaultDescription
apiUrlhttps://api.dodgeballhq.com/The base URL of the Composer API. Use https://api.sandbox.dodgeballhq.com for the sandbox environment.
apiVersionv1The Composer API version to use.
logLevelINFOTRACE, INFO, ERROR, or NONE. TRACE and INFO are useful for debugging, ERROR only logs errors, and NONE turns off logging.
disableCookiesfalseWhen true, the SDK keeps the source token in localStorage instead of a cookie.
isEnabledtrueWhen false, the SDK makes no API calls: getSourceToken() returns a placeholder token and handleVerification() calls onApproved. Useful for local development.
integrationTimeout2000How long each third-party integration may take to load, in milliseconds, before the SDK skips it.
sessionIdThe session ID, if you have it when you create the instance. You can also pass it to track().
userIdThe user ID, if you have it when you create the instance. Requires sessionId.

The constructor throws ComposerMissingConfigError when the public API key is missing, and ComposerInvalidConfigError when apiVersion or logLevel isn't one of the values above.

Usage

Every application has key moments of risk where it needs to check that a request should be allowed to proceed, such as registration, login, placing an order, creating a listing, leaving a review, adding a payment method, changing bank account information, redeeming a coupon, and transferring funds. In Composer, these moments are called checkpoints. Your API calls them with a Server SDK and returns the result to your frontend.

Once you know the session, and later the user, pass them to track(). At a moment of risk, such as placing an order, get a source token and send it to your API. Your API calls a checkpoint and returns the verification. Pass the verification to handleVerification(), which runs any extra steps the workflow needs and calls you back with the result:

// As soon as you have a session ID
composer.track(session.id);

// When the user logs in
composer.track(session.id, user.id);

const placeOrder = async (order, previousVerificationId = null) => {
  const sourceToken = await composer.getSourceToken();

  const response = await fetch('/api/orders', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'x-dodgeball-source-token': sourceToken, // Pass the source token to your API
      ...(previousVerificationId && { 'x-dodgeball-verification-id': previousVerificationId })
    },
    body: JSON.stringify({ order })
  });
  const body = await response.json();

  if (response.status === 200) {
    showOrderPlaced();
    return;
  }
  if (!body.verification) {
    showError('Something went wrong. Please try again.');
    return;
  }

  composer.handleVerification(body.verification, {
    onVerified: async (verification) => {
      // An extra step was completed and the request is approved. Retry with the verification ID
      await placeOrder(order, verification.id);
    },
    onApproved: async () => {
      // Approved with no extra step
      showOrderPlaced();
    },
    onDenied: async (verification) => {
      showOrderDenied();
    },
    onError: async (error) => {
      showError(error.details);
    }
  });
};

This matches the API in the Server SDKs example: it returns 200 when the order is placed, 202 with the verification when the workflow needs more from the user, and 403 with the verification when the order is denied. Your API can receive the source token and verification ID however you like; the headers above are a convention.

Track Parameters

ParameterRequiredDescription
sessionIdYesThe ID of the current session. Any unique string representing the user's activity in your system. If you don't have one, use the source token from getSourceToken().
userIdNoThe ID of the current user. Send it as soon as the user is authenticated.

Source Tokens

getSourceToken() returns a token representing this device's recent activity. Source tokens expire after 1 hour, and the SDK refreshes them automatically. Call getSourceToken() immediately before each request rather than storing the token yourself: Composer rejects requests made with an expired source token.

refreshSourceToken() replaces the current source token with a new one, for example when a user logs out.

Both return a promise, or take a callback that's called with the token:

const sourceToken = await composer.getSourceToken();
composer.refreshSourceToken((newSourceToken) => {});

Verification Callbacks

CallbackRequiredCalled when
onVerified(verification)YesThe user completed an extra step and the request is approved, but your API hasn't acted yet. Call your API again with verification.id, which it passes to useVerificationId.
onApproved(verification)YesThe request was approved without an extra step, so your API already acted on it.
onDenied(verification)NoThe request was denied.
onPending(verification)NoThe verification is still processing.
onBlocked(verification)NoThe verification is waiting on the user, such as an MFA challenge.
onUndecided(verification)NoThe workflow finished without a decision. Your application decides how to proceed.
onError(error)NoThe verification didn't finish, and error.details has more information. error.errorType is CANCELLED if the user closed the extra step, or SYSTEM if the verification failed on the server (for example, a third-party service was unavailable), in which case offering the user a retry can help.

handleVerification() takes an optional third argument, { maxDuration }: how long, in milliseconds, the SDK keeps polling a verification that hasn't finished. Defaults to 24 hours. When it runs out, polling stops without calling another callback, so the last one you received was onPending or onBlocked.

You won't need to read a verification's status and outcome yourself; the callbacks cover each case. For what they mean, see Interpreting the Checkpoint Response.

Handling Additional Verification

Some workflows need more from the user before they decide, such as a 3DS challenge, or 2FA at login only on a new device or from an IP address far from the user’s last one. When that happens:

  1. Your API returns the verification without acting on the request.
  2. handleVerification() shows the extra step to the user.
  3. When the user completes it and the request is approved, handleVerification() calls onVerified.
  4. Your frontend retries the request with verification.id, and your API passes it to useVerificationId.

Each verification ID can only be used once, to prevent replay attacks.

Changes from the Dodgeball client SDK

If you're upgrading from @dodgeball/trust-sdk-client, install @spreedly/composer-sdk-client and update your imports. The old names still work, but are deprecated:

Old nameNew name
DodgeballComposer
useDodgeballuseComposer
DodgeballApiVersionComposerApiVersion
IDodgeballConfigIComposerConfig
window.Dodgeballwindow.Composer

The CHANGELOG lists everything that changed. Read its Changed, Deprecated and Removed sections for the steps to migrate, including the new script-tag URL and the session cookie now requiring HTTPS.


Did this page help you?