Preverus Ruby

Ruby client for Preverus backend enforcement.

Use this gem with Rails, Rack, Sinatra, Hanami, or any Ruby backend that loads the hosted Preverus browser script and needs trusted server-side fraud decisions.

Install

Add to your Gemfile:

gem "preverus"

Then run:

bundle install

Requires Ruby 3.0+.

Browser And Server Flow

Add the hosted script to your layout:

<script
  src="https://cdn.preverus.com/v1/preverus.js"
  data-preverus-key="pk_live_xxx"
  data-preverus-auto="true"
  data-preverus-track-forms="true"
></script>

<form method="POST" action="/register" data-preverus-action="signup">
  <input name="email" type="email">
  <button type="submit">Create account</button>
</form>

Before submit, the script attaches:

preverus_fingerprint
preverus_visitor_id
preverus_risk_session_token
preverus_browser_session_event_id

Your backend sends those values to Preverus with a private server key before approving sensitive actions.

Quick Start

require "preverus"

client = Preverus::Client.new(server_key: ENV.fetch("PREVERUS_SERVER_KEY"))

decision = client.decisions.evaluate(
  {
    event_type: "signup",
    user_id: "acct_42",
    ip: request.remote_ip,
    risk_session_token: params[:preverus_risk_session_token],
    fingerprint: params[:preverus_fingerprint],
    metadata: {
      email: params[:email],
      browser_session_event_id: params[:preverus_browser_session_event_id]
    }
  },
  visitor_id: params[:preverus_visitor_id],
  idempotency_key: "signup:acct_42:request-id"
)

if decision.block?
  head :forbidden
  return
end

if decision.review?
  redirect_to verify_path
  return
end

# Continue signup.

Prefer risk_session_token when available. It links the backend action to the browser session collected moments earlier.

Configuration

client = Preverus::Client.new(
  server_key: ENV.fetch("PREVERUS_SERVER_KEY"),
  endpoint: "https://api.preverus.com",
  timeout: 1.5,
  retries: 2,
  retry_delay: 0.15,
  max_retry_delay: 1.0
)

The gem retries transient network failures and retryable statuses:

408, 409, 425, 429, 500, 502, 503, 504

It does not retry validation or authentication errors such as 400, 401, 403, or 422.

Use idempotency keys for retried POST requests.

Rails Setup

Create an initializer:

# config/initializers/preverus.rb
PREVERUS = Preverus::Client.new(
  server_key: ENV.fetch("PREVERUS_SERVER_KEY"),
  timeout: 1.5,
  retries: 2
)

Use it in a controller:

class RegistrationsController < ApplicationController
  def create
    decision = PREVERUS.decisions.evaluate(
      {
        event_type: "signup",
        user_id: params[:user_id],
        ip: request.remote_ip,
        risk_session_token: params[:preverus_risk_session_token],
        fingerprint: params[:preverus_fingerprint],
        metadata: {
          email: params[:email],
          user_agent: request.user_agent,
          browser_session_event_id: params[:preverus_browser_session_event_id]
        }
      },
      visitor_id: params[:preverus_visitor_id],
      idempotency_key: request.request_id
    )

    return head :forbidden if decision.block?
    return redirect_to verify_path if decision.review?

    # Continue registration.
  end
end

Sensitive Actions

Use decisions synchronously when your app needs an immediate allow/review/block answer:

decision = PREVERUS.decisions.evaluate(
  {
    event_type: "withdraw",
    user_id: current_user.id,
    ip: request.remote_ip,
    risk_session_token: params[:preverus_risk_session_token],
    metadata: {
      payment_address: params[:payment_address]
    }
  },
  visitor_id: params[:preverus_visitor_id],
  idempotency_key: request.request_id
)

return head :forbidden if decision.block?
return redirect_to withdraw_review_path if decision.review?

Good event names include:

signup
login
checkout
password_reset
payout
withdraw
payment_change
account_update

Decisions

decision.recommended_action
decision.allow?
decision.review?
decision.block?
decision.to_h

Recommended handling:

allow  -> proceed
review -> step-up auth, hold, or manual review
block  -> deny or hard challenge

Events

Use events for non-blocking fraud telemetry:

event = PREVERUS.events.create(
  {
    event_type: "login",
    user_id: "acct_42",
    ip: "203.0.113.10",
    fingerprint: "fp_hash",
    metadata: { email: "person@example.com" }
  },
  visitor_id: "v_abc123"
)

In Rails, send non-blocking events from Active Job or a background worker so customer requests do not wait for telemetry.

Lookups

visitor = PREVERUS.visitors.lookup(visitor_id: "v_abc123")
visitor = PREVERUS.visitors.lookup(fingerprint: "fp_hash")

 = PREVERUS..lookup("email", "person@example.com")
graph = PREVERUS..graph("v_abc123")

Use lookups for investigation and context. Use decisions.evaluate for final enforcement.

Webhook Verification

class PreverusWebhooksController < ApplicationController
  skip_before_action :verify_authenticity_token

  def create
    raw_body = request.raw_post
    valid = PREVERUS.webhooks.verify(
      raw_body: raw_body,
      timestamp: request.headers["X-Fraud-Webhook-Timestamp"].to_s,
      signature_header: request.headers["X-Fraud-Webhook-Signature"].to_s,
      secret: ENV.fetch("PREVERUS_WEBHOOK_SECRET")
    )

    return head :bad_request unless valid

    payload = JSON.parse(raw_body)

    # Store payload["id"] or X-Fraud-Webhook-Id for idempotency.

    head :no_content
  end
end

Webhook delivery is at-least-once. Dedupe by X-Fraud-Webhook-Id or payload id.

You can also verify, parse, and dispatch by event type:

event = PREVERUS.webhooks.construct_event(
  raw_body: request.raw_post,
  headers: request.headers,
  secret: ENV.fetch("PREVERUS_WEBHOOK_SECRET")
)

return head :no_content if already_processed?(event.id)

PREVERUS.webhooks.dispatch(event, {
  "decision.high_risk" => ->(event) { open_case(event.to_h) },
  "*" => ->(event) { Rails.logger.info("Preverus webhook #{event.type}") }
})

The gem verifies and parses the event, but your app should store processed event IDs in your database or cache.

Failure Handling

The Ruby core gem raises exceptions for API and network failures:

begin
  decision = PREVERUS.decisions.evaluate(...)
rescue Preverus::ApiError => error
  Rails.logger.warn("Preverus API error: #{error.status_code} #{error.error_code}")
rescue Preverus::NetworkError
  # Apply your app's fail-open, fail-review, or fail-closed policy.
end

For high-risk flows like withdrawals and payouts, a common policy is fail-review. For signup/login/checkout, many businesses prefer fail-open so the site keeps working during transient network failures.

Example fail-review wrapper:

def evaluate_or_review(input, **options)
  PREVERUS.decisions.evaluate(input, **options)
rescue Preverus::Error => error
  Rails.logger.warn("Preverus fallback review: #{error.class}")
  Preverus::DecisionResult.fallback("review", error.class.name)
end

Production Checklist

  • Keep PREVERUS_SERVER_KEY private.
  • Use a browser key only in templates/frontend code.
  • Prefer risk_session_token when present.
  • Include visitor_id through the client argument.
  • Send your real customer account ID as user_id.
  • Include IP and metadata such as email, phone, username, and payment address.
  • Use idempotency keys for retried POST requests.
  • Treat review as step-up/manual review, not as automatic allow.