Browser SDK

This page describes how to install and use the JavaScript client-side SDK

❗️

Breaking changes in 2.0 and 2.1

If you are about to upgrade Castle.js from a 1.x or 2.0.x version, please see the chapter on how to deal with the breaking changes that were introduced for these versions.

Introduction

The Browser SDK is a central component of the Castle integration, and provides device fingerprinting, behavioral analysis, and client-side event monitoring.

All the client-side SDKs, have two main purposes:

The script is designed to work in most known browser starting from Internet Explorer 5 and up. Please see the Minimal Requirements section in the NPM package docs for more details.

👍

Castle and third-party cookies

The Castle's Browser SDK uses only first-party cookies to work properly, so you don't need to worry about disabling third-party cookies.

Google's Third-Party Cookies deprecation will also require no adjustments on your end.

Installation

The recommended way of installing Castle.js in your app is via the NPM package and using either npm or yarn:

npm install @castleio/castle-js
yarn add @castleio/castle-js
"dependencies": {
	"@castleio/castle-js": "^2.1.15",
  …
}
# Check this file for the latest version (dist/tarball)
https://registry.npmjs.org/@castleio/castle-js/latest

#  And then pull the tgz file:
https://registry.npmjs.org/@castleio/castle-js/-/castle-js-2.1.15.tgz

Configuration

Once installed, the SDK needs to be configured using your Publishable Key, which can be found in the Dashboard for users with administrator access.

🚧

Initialize the SDK as early as possible

The SDK should be initialized immediately at page load. The more time that passes from initialization to when you eventually create a request token (next section), the more data the agent can collect, which in turn improves accuracy.

import * as Castle from '@castleio/castle-js'

var castle = Castle.configure({ pk: '<YOUR_PUBLISHABLE_KEY>' });
import '@castleio/castle-js/dist/castle.browser.js'

Castle.configure({ pk: '<YOUR_PUBLISHABLE_KEY>' });
optiondescriptiondefault
pkRequired. Publishable Keynull
windowOverrides the browser window object. Useful e.g. when running in a test environment with a mocked DOM, like JSDOM.window
timeoutrequest timeout for page, form, custom events.1000 ms
storagecontainer for the namespace ("name") and the expiration time in seconds ("expireIn"), used for storing castle data in the localStorage and/or in the cookies. (use name in case of conflicts with other vendors){"name":"__cuid", "expireIn": 34560000}
throttlingSends multiple custom events in a single request when they are tracked with very short intervals (less than 500ms apart). We recommend leaving this setting set to truetrue

Creating request tokens

Once Castle.js is running on your web pages, you need to ensure that the request_token value generated by Castle.js is passed to your application server, where the Castle server-side SDK will be able to extract the request_token value.

👍

Adblock-safe

Many analytics solution rely on the client-side SDK performing outgoing API requests, which results in them getting blocked by adblockers and privacy plugins. Castle.js won't make such requests, resulting in much higher accuracy.

🚧

Request tokens don't live forever

A new request token value should to be generated for each request to your backend. A request token will expire after 120 seconds and should only be used during a single request to your backend. The reason for this is that Castle.js continuously monitors behavior in the user session and the data will need to be fresh when processed by the Castle APIs. A scalable approach is to implement the token generation as a client-side middleware which generates a new request token with each request to your backend.

castle.createRequestToken().then( (requestToken) => {
  // `token` contains a unique, one-time use, string that should be passed
  // in the request to the server, eg. in a form post.
});

// or
const requestToken = await castle.createRequestToken();

The createRequestToken method doesn't accept any options, and it generates tokens immediately in a non-blocking way under normal conditions. In the edge case where the method is called immediately after page load, it can take up to a maximum of 300 ms to collect all the device data, but this edge-case should only happen in bot scenarios.

Examples on how to pass the request token

var myForm = document.getElementById('loginForm');

myForm.addEventListener(
  'submit',
  function (e) { castle.injectTokenOnSubmit(e) }
);
var myHeaders = new Headers();
var token = await castle.createRequestToken();

myHeaders.append('X-Castle-Request-Token', token);

fetch('https://example.com/login', {
  method: 'POST',
  mode: 'no-cors',
  headers: myHeaders,
  body: JSON.stringify(loginData)
})
// Note: It's recommended that you use the form submit helper if you can
var myForm = document.getElementById('loginForm');
myForm.addEventListener(
  'submit',
  function (e) {
    e.preventDefault();

    // get or insert a hidden field with the Castle request token
    var hiddenField;
    for (var i = 0; i < myForm.childNodes.length; i++) {
      var node = myForm.childNodes[i];
      if (node.getAttribute && node.getAttribute('name') === 'castle_request_token') {
        hiddenField = node;
        break;
      }
    }
    if(!hiddenField) {
      hiddenField = document.createElement('input');
      hiddenField.setAttribute('type', 'hidden');
      hiddenField.setAttribute('name', 'castle_request_token');
      myForm.appendChild(hiddenField);
    }

    castle.createRequestToken().then(function (token) {
      hiddenField.value = token;
      myForm.submit();
    });
    return false;
  }
);

Tracking client-side events

📘

API calls and requests per second limits

Tracking client-side events count towards your API calls and request per second (RPS) limits, as defined in your current plan. Contact us about our Enterprise plan if you are interested in pricing based on Monthly Tracked Users (MTU).

Building the user object

The Browser SDK offer three methods of sending data: page, form and custom (described below) depending on which type of action the user performs. Common for these calls is that a user object needs to be provided. The contents of the user object are the same as what you'll send to the Risk and Filter APIs.

var user = {
  id: 'ca1242f498', // required, and should be a string
  email: '[email protected]', // optional
  phone: '+1415232183', // optional
  name: 'Michael Brown',
  registered_at: '2012-12-02T00:30:08.276Z',
  traits: {
    plan: 'premium'
  }
}

// The `user` object is required for all event tracking methods
castle.page({user: user});

Securing the user object

In order to prevent user information from being spoofed by a bad actor, it is recommended to send the user information as a signed JWT when Castle.js is used in production.

From your backend code, you need to encode the user as a JWT and sign it using your API secret key. Then, when Castle receives the JWT, the integrity of the user data will be verified to ensure that the data isn't being tampered with. Below is an example of how to generate a JWT using the Ruby language:

var userJwt = "<%= JWT.encode({ id: current_user.id.to_s, email: current_user.email, registered_at: current_user.registered_at }, ENV.fetch('CASTLE_API_SECRET'), 'HS256') %>";

// Then use the `userJwt` argument instead of `user` when using any of the tracking methods
castle.page({userJwt: userJwt});

💡

Please note that the user ID needs to be sent as a string

castle.page()

Usage

// The page method needs to be called with the currently logged in user
castle.page(options);

// Fully customizable example:
castle.page({
  userJwt: userJwt, // or `user` if you're not using secure mode
  url: 'http://castle.io',
  name: 'User Page',
  referrer: null
}).then( (response) => {
   console.log(response)
})
optiondescriptiondefault
userRequired unless userJwt is used. The user object, as described abovenone
userJwtRequired unless user is used. JWT string containing user information and a signaturenone
nameOptional. Name of the page that the user is viewing.Extracted automatically from document.title
urlOptional. URL of the page that the user is viewingExtracted automatically from document.location.href
referrerOptional. URL of the referrer from the user came fromExtracted automatically from document.referrer

page function is async and returns the promise which is resolved in max 1000ms (timeout can be configured in the configure method)

Additionally, all the page events on the page are debounced with 300ms (to avoid quickly changed urls for SPA frameworks)

Event promise can be resolved with one of the values:

responsedescriptin
trueevent sent successfully
falsewrong configuration or data is invalid
nulltimeout or debounce appeared

castle.form()

Usage

castle.form({
  userJwt: userJwt, // or `user` if you're not using secure mode
  name: 'Update Profile',
  values: {
    first_name: 'John',
    newsletter: false
  }
});
optiondescriptiondefault
userRequired unless userJwt is used. The user object, as described abovenone
userJwtRequired unless user is used. JWT string containing user information and a signaturenone
nameRequired. Name of the form that the user is submitting.none
valuesOptional. Object with the input values of the form that the user is submittingnone

form function is async and returns the promise which is resolved in max 1000ms (timeout can be configured in the configure method)

Event promise can be resolved with one of the values:

responsedescription
trueevent sent successfully
falsewrong configuration or data is invalid
nulltimeout appeared

castle.custom()

Usage

castle.custom({
  userJwt: userJwt, // or `user` if you're not using secure mode
  name: 'Added to cart',
  properties: {
    product: 'iPhone 13 Pro',
    price: 1099.99
  }
});
optiondescriptiondefault
userRequired unless userJwt is used. The user object, as described abovenone
userJwtRequired unless user is used. JWT string containing user information and a signaturenone
nameRequired. Name of the event that is tracked.none
propertiesOptional. Object with extra properties to attach to the event.none

custom function is async and returns the promise which is resolved in max 1000ms (timeout can be configured in the configure method)

Event promise can be resolved with one of the values:

responsedescription
trueevent sent successfully
falsewrong configuration or data is invalid
nulltimeout appeared

Helper methods

Castle.injectTokenOnSubmit()

Global helper method that can be used directly on a form to inject the Castle request token as a hidden parameter called castle_request_token

Usage

<form onsubmit="Castle.injectTokenOnSubmit(event)">
  <input type="email" name="email">
  <input type="password" name="password">
</form>
parametersdescriptiondefault
1. eventRequired form SubmitEvent
2. onDone(event)a function called after injecting the tokenfunction(event) {
formElement.submit()
}

Castle.formEventOnSubmit()

Global helper method that can be used directly on a form to generate form events based on the data-castle- attributes

Usage

<form data-castle-name="Update Profile" onsubmit="Castle.formEventOnSubmit(event, user)" action="/">
  <input type="text" name="email" placeholder="email" data-castle-value="email">
</form>

This will trigger form event like below before the submit:

castle.form({
  user: user,
  name: 'Update Profile',
  values: {
    email: '<email>',
  }
});
parametersdescriptiondefault
eventRequired form SubmitEvent
userRequired user object
options-{}
onDone(event)a function called after sending the form eventfunction(event) {
formElement.submit()
}

HTML data attributes

attributedescriptionelement
data-castle-nameattribute describing name of the form eventon the form element
data-caste-valueattribute describing name of the form values nameson the input, select or textarea element

Breaking changes

Upgrading from 1.x to 2.0.x

1.x

Legacy package: https://www.npmjs.com/package/castle.js.

require "castle.js"  
_castle('setAppId', "YOUR_APP_ID")
_castle('getClientId')

2.0.x

New package:

import * as Castle from "@castleio/castle-js"
  1. Renamed the main object from _castle to Castle in the new module, but kept the _castle in the CDN version as well as the browser-specific module @castleio/castle-js/dist/castle.browser.js
  2. Introduced the concept of request tokens that need to be generated for each call to Castle's server-side API.
Castle.createRequestToken().then( (requestToken) => {
});

// or

const token = await Castle.createRequestToken();
  1. Request tokens can no longer be retrieved from the cookie string.
  2. All the following methods were removed:

autoForwardClientId, autoTrack, catchHistoryErrors, identify, setUserId, setAccount, setKey, setAccount, sessionId, reset, page, trackPageView, setTrackerUrl

📘

Page views (and more) returned in 2.1

The old way of sending page views were deprecated with 2.0, but recently returned in a more powerful version were you can track any events from the frontend.

Upgrading from 2.0.x to 2.1.x or later

2.0.x

Castle.configure(YOUR_CASTLE_APP_ID);

2.1.x or later

  1. Removed the _castle object for all versions and now only relying on Castle.

  2. Switched to use the Publishable Key that can be found in the same place as the now deprecated App ID.

Castle.configure({pk: YOUR_PUBLISHABLE_KEY});
  1. onFormSubmit has been renamed to injectTokenOnSubmit

  2. _castle global method is no longer supported and has been replaced with Castle (for the CDN and castle.browser.js versions) check docs

  3. CDN version no longer needs appID in the url and requires <script>Castle.configure({pk: YOUR_CASTLE_PUBLISHABLE_KEY});</script> to be added. The CDN version can't be used for generating request tokens, but only for tracking client-side events.

  4. Introduced page, form, and custom methods for client-side event tracking