🚀 Developer Quickstart

The goal of this guide is to get you quickly up and running with sending data to Castle with focus on getting 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 and works in general, and will set you up for taking on a full integration.

Before you begin

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


New to Castle?

If you haven't already, sign up for a free trial to get your API keys

You will need access to the backend and frontend source code of the application you're installing Castle onto. To get a sense of what a simple integration looks like, you can check out this Ruby example.


By default, a new Castle organization is created with two environments, one for Sandbox and one for Production. We recommend starting out with the Sandbox environment while developing code to complete the integration. Once you're ready with the integration and data has been verified, you can simply switch to the production environment by replacing the API key.

Once you have rolled out Castle in production, we recommend keeping the Sandbox environment for developing purposes to prevent polluting the production environment with invalid data.

Finding the API keys

To complete the integration, you need both the "App ID" and the "API Secret".

The "App ID" is a non-secret, 16-digit number, that is used to identify your application on the frontend part of the integration. You'll find it in the Dashboard Settings:

The API secret is a 32 character long string comprised of random numbers and letters. You'll find it in the same Dashboard Settings page as the App ID:

:warning: Make sure to not share your API keys in publicly accessible areas such as GitHub or client-side code

Installing Castle


For the rest of this guide, we recommend using the Sandbox environment

By now, you are ready to add Castle to your application code and start sending your first event. If you're not yet ready to modify your application code, but just want a quick way to see how data looks in Castle, check out these demo requests that you can try from the command line or Postman. If you're a developer, you can download and run this simple example.

The rest of this section will assume that you're working in a real web application.

Regardless of which use-case you're using Castle for, the installation consists of one client side (i.e. browser or mobile) integration and one backend side integration. Here are the steps involved to get data flowing to Castle:

Step 1. Install the client-side code

In this section, you'll add the client side SDK responsible for fingerprinting, and ultimately create a REQUEST TOKEN that is analyzed by Castle.


Web and Mobile

In this guide, we'll focus on adding the client side code to a web application, but Castle SDKs also supports native iOS and Android apps. See the complete list of SDKs

The recommended way of installing the Castle client-side code is by using our NPM package.

To install the package in your application using NPM, just run:

npm install --save @castleio/castle-js

or with Yarn:

yarn add @castleio/castle-js

After installation, make sure that Castle is included and initialized in a code bundle that is available on all pages of your app.

:rocket: If you just want to quickly try out the client-side code without installing the package, you can use our CDN hosted version. The Castle tag should be added near the top of the <head> tag and before any other script or CSS tags. Add it to every page on your site, not just the signup or login page. :warning: The CDN version is NOT recommended for production environments

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

<!-- NOTE: CDN is recommended for quick testing only. Include the following script in the <head> tag on all pages of your site //-->
<script src="//cdn.castle.io/v2/castle.js?YOUR_CASTLE_APP_ID"></script>

:bulb: To learn more about castle.js and the web based integration, see this guide.

Step 2. Create a request token

The request token is used to transfer the results from the client side profiling to Castle via the server-side API calls. See guides on the client-side SDKs for in-depth tutorials on how to create and transfer the request token.


The request token is required for the Risk and Filter endpoints. An error will be returned if omitted

Typically, in a Web application, the request token can be inserted as a form parameter, e.g. called castle_request_token, but ultimately you can choose a way that suits your workflow as long as the token is passed correctly to the backend. For mobile applications, we recommend forwarding the request token as a request header, X-Castle-Request-Token to every request to your API.

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

Castle.createRequestToken().then( (requestToken) => {
  // TODO: Include requestToken in the request sent to server

// or
const requestToken = await Castle.createRequestToken();
<!-- Use the form helper to automatically insert the request token on form submit //-->
<form onsubmit="_castle('onFormSubmit', event)">
  // ....

Step 3. Add server-side code

Depending on the use-case you want to protect, Castle offers two endpoints that return a risk assessment: Risk and Filter. In this example, we're going to use the Risk endpoint to protect the login endpoint from account takeover attempts.

Install and configure the server side SDK that fits your environment and use it to send a request to the Risk endpoint:

castle = ::Castle::Client.new

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

  res = castle.risk(
    event: '$login',
    status: '$succeeded',
    request_token: token,
    context: {
      ip: context[:ip],
      headers: context[:headers] # Forward all headers from the originating request
    user: {
      id: user.id,
      email: user.email
    authentication_method: {
      type: '$password' # Email+password login

  if res[:risk] > 0.9
    # IMPLEMENT: Deny attempt

rescue Castle::Error => e
  # Handle error
try {
  $token = $_POST['castle_request_token'];

  $res = Castle::risk([
    'event' => '$login',
    'status' => '$succeeded',
    'request_token' => $token,
    'context' => [
      'ip' => Castle_RequestContext::extractIp(),
      'headers' => Castle_RequestContext::extractHeaders() // Forward all headers from the originating request
    'user' => [
      'id' => $user->id,
      'email' => $user->email
    'authentication_method' => [
       'type' => '$password'

  if ($res->risk > 0.9) {
    // IMPLEMENT: Deny attempt

} catch (Castle_Error $e) {
  // Handle error
    token = request.form['castle_request_token'] # Using Flask
    context = ContextPrepare.call(request)

    client = Client()

    res = client.risk({
        'event': '$login',
        'status': '$succeeded'
        'request_token': token,
        'context': {
          'ip': context['ip'],
          'headers': context['headers'] # Forward all headers from the originating request
        'user': {
            'id': user.id,
            'email': user.email
        'authentication_method': {
            'type': '$password'

    if res['risk'] > 0.9:
        # IMPLEMENT: Deny attempt

except CastleError as e:
     # Handle 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_EVENT, "$login")
    .put(Castle.KEY_STATUS, "$succeeded")
    .put(Castle.KEY_REQUEST_TOKEN, token)
    .put(Castle.KEY_CONTEXT, ImmutableMap.builder()
      .put(Castle.KEY_IP, context.getIp())
      .put(Castle.KEY_HEADERS, context.getHeaders()) // Forward all headers from the originating request
    .put(Castle.KEY_USER, ImmutableMap.builder()
      .put(Castle.KEY_USER_ID, user.getId())
      .put(Castle.KEY_EMAIL, user.getEmail())
    .put("authentication_method", ImmutableMap.builder()
      .put("type", "$password")
} catch (CastleRuntimeException runtimeException) {
  // Handle error

float risk = response.json()

if (risk > 0.9) {
  // IMPLEMENT: Deny attempt
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({
    event: '$login',
    status: '$succeeded',
    request_token: token,
    context: {
      ip: context.ip,
      headers: context.headers // Forward all headers from the originating request
    user: {
      id: user.id,
      email: user.email
    authentication_method: {
      type: '$password'

  if (res.risk > 0.9) {
    // IMPLEMENT: Deny attempt
} catch (e) {

A note regarding forwarding headers. For best results, we recommend forwarding all headers from the originating request in the context.headers field. When using Castle SDKs, sensitive values in Authentication and Cookie headers, are automatically redacted. At a minimum, the following headers should be forwarded: Host, User-Agent, Accept, Accept-Encoding, Accept-Language.


When you're testing or debugging the server-side integration, you can use Test Tokens to emulate different devices instead of having to forward a fresh request_token with each request.


  "risk": 0.95, // Risk score between 0-1, higher number means higher risk
  "signals": {  // Key risk indicators detected by Castle
    "bot_behavior": {},
    "proxy_ip": {},
    "disposable_email": {},
    "spoofed_device": {}
  "policy": {  // Information about the Policy that got triggered
    "action": "deny",
    "name": "Block bots",
    "id": "e14c5a8d-c682-4a22-bbca-04fa6b98ad0c",
    "revision_id": "b5cf794e-88c0-426e-8276-037ba1e7ceca"

The response returned from the Risk endpoint is a JSON payload with actionable data:


A risk score between 0-1, where a higher number means higher probability of a bad request. As a starting point, we recommend that you deny any attempts where the risk score is above 0.9.
:bulb: Learn more about the Castle Risk Scoring


A list of key risk indicators detected by Castle.
:bulb: See the full list of Signals


Information about which Policy that triggered.
:bulb: Learn more about Policies

Test that it's working

Once you have sent at least one request to Castle, you can use the Debugger to check that it was processed correctly. The debugger is a real time tool that will highlight any problems you need to fix, as well as provide tips and suggestions for additional data to track.


Debugger limitations

Note that the debugger is a limited view of the data you're tracking, and will only display the last 100 requests. To look at historic data, please use the Events view instead.

Once you have confirmed that Castle is processing the requests without errors, you can head over to the Events view to inspect how Castle enriches and renders the data:

Any successful request to Risk or Filter APIs will show up a searchable entry in the Events view of the Castle DashboardAny successful request to Risk or Filter APIs will show up a searchable entry in the Events view of the Castle Dashboard

Any successful request to Risk or Filter APIs will show up a searchable entry in the Events view of the Castle Dashboard

Since you included the user.id in the request, as required by the Risk endpoint, you can head over to the Users view to see that a user has now been created in Castle:

A user will be created when the Risk endpoint is used.A user will be created when the Risk endpoint is used.

A user will be created when the Risk endpoint is used.

:tada: Congratulations! You have now installed Castle into your app and sent your first event, so by now you should have a hang of the basics of how Castle ingests and displays data. As next steps, we recommend adding protection for all the required + applicable activities by following the guide for a full integration

Did this page help you?