End-user Notifications

End-user Notifications is an effective way of letting your users resolve their own risky activity without involving analysts or support people.

This guide is for building a custom integration of Security Notification by subscribing to Castle's webhooks and hosting your own email delivery and website landing pages.

For a one-click, brandable version of Security Notifications, use the out-of-the-box version hosted by Castle.

Subscribing to review webhooks

Head over to the webhook settings in the Castle Dashboard and subscribe to the $review.opened event. This event will be triggered whenever the risk of a device exceeds 30, and will only trigger once per device.

Webhooks configuration

Integration flow

Once you've set up webhooks, there are four steps to implement:

  1. When a new review is opened, send the user an email with a link to review their activity.
  2. Set up a review landing page for the user to review their activity, using the review link.
  3. Display the review landing page
  4. Based on the user’s action, you either escalate or resolve the review.

Webhooks flow

Step 1. Implement the webhook handler

Start by implementing a backend endpoint that will accept incoming webhooks sent from Castle.

A simple Ruby implementation that receives a webhook and calls a method to send the email notifications will look something like this:

post "/webhooks/notifications" do
  # Retrieve the request's body and parse it as JSON
  webhook = JSON.parse(request.body.read)

  # Fetch the user from your database
  user = User.find(webhook['data']['user_id'])

  # Implement an email handler. `context` will be used to customize the message
  user.send_notification_email(user, webhook['data']['context'])

  status 200
end

Payload for $review.opened

The data field is pointing to a Review object, where data.id is the Review identifier.

{
  "created_at": "2012-17-02T00:30:08.276Z",
  "type": "$review.opened",
  "api_version": "v1",
  "data": {
    "id": "xh6eyJxQ3AqouYqnACWYy2pe9NvLp59W",
    "trigger": "$login.succeeded",
    "user_id": "joH8NAVsYwRW4yBvPddS6TzydMmTjuzF",
    "context": {
      "ip": "69.181.35.4",
      "user_agent": {
        "raw": "Mozilla\/5.0 (Macintosh; Intel Mac OS X 10_9_5) ...",
        "browser": "Chrome",
        "mobile": false,
        "os": "Mac OS X 10.9.5",
        "version": "42.0.2311"
      },
      "location": {
        "country": "United States",
        "country_code": "US",
        "region": "California",
        "region_code": "CA",
        "city": "San Francisco",
        "lon": -55.654325,
        "lat": 13.043243
      }
    }
  }
}

Once you're done implementing, click “Test” in the Castle Dashboard to make sure the webhook is received and parsed correctly.

See the webhooks documentation for more details on how to parse incoming webhooks.

Security tip: Before going live, make sure you secure your webhooks.

Step 2. Implement the review email

You should now create an security notification email based on the context from the webhook, and set the email subject to something like "New login from Safari on Mac OS X".

UX tip: These emails will occasionally go out to people that aren't aware of their activity being picked up as anomalous, so try to keep the tone of the email context neutral.

Review email

Depending on the HTML templating language you're using to insert the event context, the email body could look like:

We noticed your account was accessed using <%= context['user_agent']['browser'] %> on <%= context['user_agent']['os'] %> at <%= Time.now.to_s %> from <%= context['location']['city'] %>, <%= context['location']['region_code'] %>, <%= context['location']['country'] %>.

Now, generate a “reviewing the activity” link using the Review identifier, data.id. The link will be pointing to a review landing page that we'll build in the next integration step:

https://yoursite.com/reviews/<%= data['id'] %>

Step 3. Implement the review landing page

When the user clicks the review link in the email, they should end up on a review landing page hosted by your website. This is where the user gets to submit feedback on whether it was them or a malicious actor accessing their account.

Facebook chose to implement their review landing page like this:

Review landing page

Implement a URL handler that takes the review_id that is passed along in the url and fetches the review context from the Reviews API and renders it to the web view.

get "/reviews/:id" do
  review = castle.fetch_review(params[:id])
  erb :page, locals: { review: review }
end

Review payload

{
  "user_id": "joH8NAVsYwRW4yBvPddS6TzydMmTjuzF",
  "context": {
    "ip": "69.181.35.4",
    "user_agent": {
      "raw": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_5) ...",
      "browser": "Chrome",
      "mobile": false,
      "os": "Mac OS X 10.9.5",
      "version": "42.0.2311"
    },
    "location": {
      "country": "United States",
      "country_code": "US",
      "region": "California",
      "region_code": "CA",
      "city": "San Francisco",
      "lon": -55.654325,
      "lat": 13.043243
    }
  }
}

Add feedback links pointing to the URL routes we'll be implementing in the final step.

<html>
  ...
  <a href=”/reviews/{{@review_id}}/escalate”>This Wasn't Me</a>
  <a href=”/reviews/{{@review_id}}/resolve”>This Was Me</a>
</html>

Step 4. Implement the review confirmation page

The final step is to track the user's feedback. If they click "This Wasn't Me", the review will be escalated into an Incident, notifying you over email and triggering a $incident.confirmed webhook.

get '/reviews/:id/escalate' do
  castle.track(
    event: '$review.escalated',
    review_id: params[:id]
  )
end

If the user clicks "This Was Me", the review will get resolved and the risk for the corresponding device will go back to 0.

get '/reviews/:id/resolve' do
  castle.track(
    event: '$review.resolved',
    review_id: params[:id]
  )
end

Closing the loop: As a final action you should set up the $incident.confirmed webhook to initiate an account reset flow. You now have an end-to-end process in place for mitigating account takeovers!