Quickstart guide

The goal of this guide is to get you quickly up and running with requesting your first device intelligence payload from Castle with a focus on getting from end to end with everything from preparations, SDK installation to debugging and viewing data in the Dashboard. After completing this guide, you'll have a better understanding of how Castle works in general.

Step 1. Setting up your account

Before you start, you need a Castle user account with admin access in order to view and copy the API keys required to send data to Castle.

:wave: Reach out to our team using this form and we'll set you up with an account.

The first phase of the integration requires you to have access to the frontend source code of your application, as well as some knowledge on how to inject server-side template variables to populate a user object.


A new Castle organization is created with two default environments, one for Sandbox and one for Production. You should always use the Sandbox environment while developing in order to not taint your production data, and only once you've completed with the integration and the data has been verified, you switch to the production environment by replacing the integration keys described below.

Your data will appear in the Sandbox in real-time, with the limitation of having tighter rate limits and lower data retention. The Production environment is optimized for throughput, but you might have to wait up to 60 seconds to see your data.

Integration keys

To complete the integration, you need both the Publishable Key and the API Secret. You'll find both in the Dashboard Settings.

  • Publishable Key – this is what'll you'll be using for the quickstart guide. It's a non-secret string starting with pk_, used to identify your application on the client-side side of the integration.

  • API Secret – a 32 character long string comprised of random numbers and letters, which you'll be using for your server-side integration.

Step 2. Frontend integration

Castle.js is the browser SDK for collecting and passing device intelligence to the Castle API. 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)

#  And then pull the tgz file:


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

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>' });


Initialize the SDK everywhere, as early as possible

The SDK should be initialized immediately at page load, and it's recommended that you put it on all your pages so that enough behavioral metrics can be collected. which improves accuracy especially for bot detection.

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.

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
var requestToken = await castle.createRequestToken();


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.

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 to your server

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

  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');
  function (e) {

    // 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;
    if(!hiddenField) {
      hiddenField = document.createElement('input');
      hiddenField.setAttribute('type', 'hidden');
      hiddenField.setAttribute('name', 'castle_request_token');

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

Step 3. Backend integration

Use the Filter API to forward the request_token string from generated by Castle.js in Step 2.

castle = ::Castle::Client.new

  token = request.params['castle_request_token']
  context = Castle::Context::Prepare.call(request)

  res = castle.filter(
    request_token: token, # forward from Castle.js
    context: {
      ip: context[:ip],
      headers: context[:headers]
    type: '$registration', # or '$login', '$transaction', etc
    status: '$attempted',
    matching_user_id: 'ca1242f498', # Optional, but will improve accuracy
    params: {
      email: request.params['email'], # Optional, but will improve accuracy

	# fetch device.fingerprint, scores.bot.score, etc

rescue Castle::InvalidRequestTokenError
  # Deny attempt. Likely a bad actor bypassing fingerprinting
rescue Castle::Error => e
  # Allow attempt. Data missing or invalid, or a server or timeout error
try {
  $token = $_POST['castle_request_token'];

  $res = Castle::filter([
    'request_token' => $token,
    'context' => [
      'ip' => Castle_RequestContext::extractIp(),
      'headers' => Castle_RequestContext::extractHeaders()
    'type' => '$registration',
    'status' => '$attempted',
    'matching_user_id' => 'ca1242f498', // Optional
    'params' => [
      'email' => $_POST['email']
  // fetch device.fingerprint, scores.bot.score, etc

} catch (Castle_InvalidRequestTokenError $e) {
  // Deny attempt. Likely a bad actor bypassing fingerprinting
} catch (Castle_Error $e) {
  // Allow attempt. Data missing or invalid, or a server or timeout error
    token = request.form['castle_request_token'] # Using Flask
    context = ContextPrepare.call(request)

    client = Client()

    res = client.filter({
        'request_token': token,
        'context': {
          'ip': context['ip'],
          'headers': context['headers']
        'type': '$registration',
        'status': '$attempted',
        'matching_user_id': 'ca1242f498', # Optional
        'params': {
          'email': request.form['email']

    # fetch device.fingerprint, scores.bot.score, etc

except InvalidRequestTokenError:
    # Deny attempt. Likely a bad actor bypassing fingerprinting
except CastleError as e:
    # Allow attempt. Data missing or invalid, or a server or timeout error
String token = request.getParameter("castle_request_token");

Castle castle = Castle.initialize();
CastleContextBuilder context = castle.contextBuilder().fromHttpServletRequest(request)

try {
  CastleResponse response = castle.client().filter(ImmutableMap.builder()
    .put(Castle.KEY_REQUEST_TOKEN, token)
    .put(Castle.KEY_CONTEXT, ImmutableMap.builder()
      .put(Castle.KEY_IP, context.getIp())
      .put(Castle.KEY_HEADERS, context.getHeaders())
    .put("type", "$registration")
    .put("status", "$attempted")
    .put("matching_user_id", "ca1242f498") // Optional
    .put("params", ImmutableMap.builder()
      .put("email", request.getParameter("email"))
  // fetch device.fingerprint, scores.bot.score, etc
  // float botScore = response.json().getAsJsonObject().get("...").getAsFloat();
} catch (CastleApiInvalidRequestTokenException requestTokenException) {
    // IMPLEMENT: Deny attempt. Likely a bad actor
} catch (CastleRuntimeException runtimeException) {
    // Data missing or invalid. Needs to be fixed
try {
  var token = request.body["castle_request_token"]; // Using Express

  const castle = new Castle({ apiSecret: 'YOUR SECRET HERE' });
  const context = ContextPrepareService.call(request, {}, castle.configuration);

  const res = castle.filter({
    request_token: token,
    context: {
      ip: context.ip,
      headers: context.headers
    type: '$registration',
    status: '$attempted',
    matching_user_id: 'ca1242f498', // Optional
    params: {
      email: request.body["email"]

  // fetch device.fingerprint, scores.bot.score, etc
} catch (e) {
  if (e instanceof InvalidRequestTokenError) {
     // IMPLEMENT: Deny attempt. Likely a bad actor
  } else if (e instanceof APIError) {
     // Allow attempt. Data missing or invalid, or a server or timeout error


Tip for higher accuracy

The Filter API above is used for sending anonymous user activity, for instance an attempted registration or login, but before the user is authenticated. There's also the Risk API which is highly recommended to use over the Filter API whenever possible since it has user context, resulting in higher accuracy. Here's a guide on how to pass successful logins to the Risk API. This example also outlines a more complete set of data that can be sent to the API.

That's it! :rocket:

Now check out the dashboard to see what the event and device details look like. If things look accurate, you're good to deploy to production.

However, if you're curious how Castle can also be used as an end-to-end abuse monitoring platform, check out the section on A complete integration to learn about all the events and APIs available.