# Ruby

> Read identification events with a secret key and verify signed webhooks from Ruby 3.1 and newer, with a Rails example.

Last updated: 2026-09-17

The `fingerly` gem reads stored events by request ID and verifies webhook signatures. It has no runtime dependencies beyond the standard library.

## Requirements

- Ruby 3.1 or newer.
- A [secret key](https://docs.fingerly.io/docs/api-keys), and a webhook signing secret if you receive [webhooks](https://docs.fingerly.io/docs/webhooks).

## Install

```bash Bundler
bundle add fingerly
```

```ruby Gemfile
gem "fingerly", "~> 0.1"
```

## Read an event

Create one client with your secret key and reuse it. The key decides the regional API and the environment the client reads.

```ruby Ruby
require "fingerly"

fingerly = Fingerly::Client.new(secret_key: ENV.fetch("FINGERLY_SECRET_KEY"))
event = fingerly.events.get("01a0a84b-e6a2-7c09-9f51-0b3d7a26c8e4")
```

An event has the fields listed in [Get an event](https://docs.fingerly.io/reference/get-event#response). `suspect_score` is `null` when the request was not scored.

## Verify a checkout

Read the event your client identified, check it belongs to this action and is recent, then decide on its level. See [server-side verification](https://docs.fingerly.io/docs/server-side-verification).

```ruby Ruby
def decide(order_id, request_id)
  event = fingerly.events.get(request_id)

  return "refuse" unless event.tag == "checkout:#{order_id}"
  return "refuse" if event.occurred_at < Time.now - 120

  case event.suspect_level
  when "high" then "review"
  when "medium" then "challenge"
  else "allow"
  end
rescue Fingerly::APIError => e
  raise unless e.status == 404
  "refuse"
end
```

## Verify a webhook

Check the signature over the raw request body before parsing it. The helper rejects timestamps more than five minutes from now.

```ruby Ruby
class FingerlyWebhooksController < ActionController::API
  def create
    payload = request.raw_post
    valid = Fingerly::Webhook.verify(
      secret: ENV.fetch("FINGERLY_WEBHOOK_SECRET"),
      payload: payload,
      timestamp: request.headers["x-fingerly-timestamp"],
      signature: request.headers["x-fingerly-signature"],
    )
    return head :bad_request unless valid

    event = JSON.parse(payload)
    FingerlyEventJob.perform_later(event)   # deduplicate on event["id"]
    head :no_content
  end
end
```

## Rails

```ruby config/initializers/fingerly.rb
FINGERLY = Fingerly::Client.new(secret_key: Rails.application.credentials.dig(:fingerly, :secret_key))
```

## API

| Member | Returns | Notes |
| --- | --- | --- |
| `Fingerly::Client.new(secret_key:, endpoint: nil, timeout: 10)` | client | Thread-safe; create one per process. |
| `events.get(request_id)` | `Fingerly::Event` | Raises `Fingerly::APIError` with `#status` for a non-2xx response. |
| `events.list(from: nil, to: nil, page: 1, limit: 10, visitor: nil, level: nil)` | `Fingerly::EventPage` | Has `rows`, `page` and `page_size`. |
| `Fingerly::Webhook.verify(secret:, payload:, timestamp:, signature:)` | `true` or `false` | Five minutes of tolerance. |
