Protecting registration

How to send $registration activity to Castle and use the response to prevent bad actors from signing up for new accounts.

The registration activity is sent to Castle whenever a user registers for an account on your platform. It doesn't necessarily mean that the user onboarding process is completed, but at least that the user account was created.

The inline response from the Risk endpoint can be used to detect and fake accounts, or trigger additional verification when too many behavioral trigger.

How to add the $registration activity:

  1. A user submits a form with desired account information, e.g. name, email and password
  2. The email (or other identifier) is checked against the user database for whether it already exists or not.
  3. If the account doesn't exist, create the account record as you normally would and send $registration with $succeeded status.
  4. If the already exists, send $registration with $succeeded status.
Where in the registration flow to send the different statusesWhere in the registration flow to send the different statuses

Where in the registration flow to send the different statuses

Sending successful registration activity

Use the Risk API to send information about the user at the time of account creation.

castle = ::Castle::Client.new

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

  res = castle.risk(
    event: '$registration',
    status: '$succeeded',
    user: {
      id: 'ca1242f498', # Required. A unique, persistent user identifier
      email: '[email protected]', # Required
      phone: '+1415232183', # E.164 format
      name: 'Mike Gray',
      traits: { # Custom user data for visualization purposes
        nationality: 'US',
        birth_date: '1976-02-02'
      }
    },
    authentication_method: { # Optional. See link below
      type: '$password' # The most common type
    },
    request_token: token,
    context: {
      ip: context[:ip],
      headers: context[:headers]
    },
    properties: { # Custom event data for visualization purposes
      solved_captcha: true,
      attempts: 3
    }
  )

  if res[:risk] > 0.9
    # IMPLEMENT: Deny attempt
  end
rescue Castle::Error => e
  # Handle error
end
// NOTE: See the Ruby example for a more comprehensive set of parameters

try {
  $token = $_POST['castle_request_token'];

  $res = Castle::risk([
    'event' => '$registration',
    'status' => '$succeeded',
    'request_token' => $token,
    'user' => [
      'id' => $user->id,
      'email' => $user->email,
      'phone' => $user->phone // Optional
    ],
    'context' => [
      'ip' => Castle_RequestContext::extractIp(),
      'headers' => Castle_RequestContext::extractHeaders()
    ]
  ]);

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

} catch (Castle_Error $e) {
  // Handle error
}
# NOTE: See the Ruby example for a more comprehensive set of parameters

try:
    token = request.form['castle_request_token'] # Using Flask
    context = ContextPrepare.call(request)

    client = Client()

    res = client.risk({
        'event': '$registration',
        'status': '$succeeded',
        'request_token': token,
        'user': {
            'id': user.id,
            'email': user.email
        },
        'context': {
          'ip': context['ip'],
          'headers': context['headers']
        }
    })

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

except CastleError as e:
     # Handle error
// NOTE: See the Ruby example for a more comprehensive set of parameters

String token = request.getParameter("castle_request_token");

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

try {
  CastleResponse response = castle.client().risk(ImmutableMap.builder()
    .put(Castle.KEY_EVENT, "$registration")
    .put(Castle.KEY_STATUS, "$succeeded")
    .put(Castle.KEY_CONTEXT, ImmutableMap.builder()
      .put(Castle.KEY_IP, context.getIp())
      .put(Castle.KEY_HEADERS, context.getHeaders())
      build()
    )
    .put(Castle.KEY_USER, ImmutableMap.builder()
      .put(Castle.KEY_USER_ID, user.getId())
      .put(Castle.KEY_EMAIL, user.getEmail())
      .put("phone", user.getPhone())
      .build()
    )
    .put(Castle.KEY_REQUEST_TOKEN, token)
    .build()
  );
} catch (CastleRuntimeException runtimeException) {
  // Handle error
}

float risk = response.json()
   .getAsJsonObject()
   .get("risk")
   .getAsFloat();

if (risk > 0.9) {
  // IMPLEMENT: Deny attempt
};
// NOTE: See the Ruby example for a more comprehensive set of parameters

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.risk({
    event: '$registration',
    status: '$succeeded',
    request_token: token,
    user: {
      id: user.id,
      email: user.email,
      phone: user.phone // optional
    },
    context: {
      ip: context.ip,
      headers: context.headers
    }
  });

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

:notebook-with-decorative-cover: See the documentation for authentication_method to see all the available options, such as how to specify account creation with an email or SMS magic link.

Taking action

The response from the call to the Risk API can then be used to take the appropriate action:

  • As a starting point, we recommend putting the newly created account through additional verification whenever the risk score is above 0.9. If the user fails to successfully complete the additional verification within a desired timeframe, you should delete the account, as it will otherwise just occupy unnecessary space in your database as well as potentially skew your product metrics.
  • The stricter recommendation is to prompt the user for additional verification when then risk score is above 0.6, and for responses with a score of 0.9 and above, drop the request and mark the account for deletion.

Sending failed registration activity

The Filter API is used for sending anonymous user activity, such as a failed login attempt. The event will appear on the timeline of any existing user with the same email as the user.email, but it will not create users nor devices, nor contribute to the risk score of any existing users. Note that user.id is not supported for Filter events.

castle = ::Castle::Client.new

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

  res = castle.filter(
    event: '$registration',
    status: '$failed',
    user: {
      email: '[email protected]'
    },
    request_token: token,
    context: {
      ip: context[:ip],
      headers: context[:headers]
    }
  )
rescue Castle::Error => e
  # Handle error
end
try {
  $token = $_POST['castle_request_token'];

  $res = Castle::filter([
    'event' => '$registration',
    'status' => 'failed',
    'user' => [
      'email' => '[email protected]'
    ],
    'request_token' => $token,
    'context' => [
      'ip' => Castle_RequestContext::extractIp(),
      'headers' => Castle_RequestContext::extractHeaders()
    ]
  ]);

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

    client = Client()

    res = client.filter({
        'event': '$registration',
        'status': '$failed',
        'user': {
          'email': '[email protected]'
        },
        'request_token': token,
        'context': {
          'ip': context['ip'],
          'headers': context['headers']
        }
    })
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, "$registration")
    .put(Castle.KEY_STATUS, "$failed")
    .put(Castle.KEY_CONTEXT, ImmutableMap.builder()
      .put(Castle.KEY_IP, context.getIp())
      .put(Castle.KEY_HEADERS, context.getHeaders())
      build()
    )
    .put(Castle.KEY_USER, ImmutableMap.builder()
      .put(Castle.KEY_EMAIL, "[email protected]")
      .build()
    )
    .put(Castle.KEY_REQUEST_TOKEN, token)
    .build()
  );
} catch (CastleRuntimeException runtimeException) {
  // Handle error
}
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: '$registration',
    status: '$failed',
    request_token: token,
    user: {
      email: '[email protected]'
    },
    context: {
      ip: context.ip,
      headers: context.headers
    }
  });
} catch (e) {
  console.error(e);
}

Did this page help you?