[{"data":1,"prerenderedAt":109},["ShallowReactive",2],{"doc:\u002Fdocs\u002Fsdks\u002Fruby":3},{"page":4,"toc":100,"updated":108},{"path":5,"title":6,"seoTitle":7,"description":8,"blocks":9},"\u002Fdocs\u002Fsdks\u002Fruby","Ruby","Ruby SDK: Read Events and Verify Webhooks","Read identification events with a secret key and verify signed webhooks from Ruby 3.1 and newer, with a Rails example.",[10,13,18,23,26,37,40,42,46,48,51,53,57,60,62,66,69,74,77],{"type":11,"text":12},"p","The `fingerly` gem reads stored events by request ID and verifies webhook signatures. It has no runtime dependencies beyond the standard library.",{"type":14,"level":15,"text":16,"id":17},"heading",2,"Requirements","requirements",{"type":19,"items":20},"list",[21,22],"Ruby 3.1 or newer.","A [secret key](\u002Fdocs\u002Fapi-keys), and a webhook signing secret if you receive [webhooks](\u002Fdocs\u002Fwebhooks).",{"type":14,"level":15,"text":24,"id":25},"Install","install",{"type":27,"samples":28},"code",[29,33],{"label":30,"lang":31,"code":32},"Bundler","bash","bundle add fingerly",{"label":34,"lang":35,"code":36},"Gemfile","ruby","gem \"fingerly\", \"~> 0.1\"",{"type":14,"level":15,"text":38,"id":39},"Read an event","read-an-event",{"type":11,"text":41},"Create one client with your secret key and reuse it. The key decides the regional API and the environment the client reads.",{"type":27,"samples":43},[44],{"label":6,"lang":35,"code":45},"require \"fingerly\"\n\nfingerly = Fingerly::Client.new(secret_key: ENV.fetch(\"FINGERLY_SECRET_KEY\"))\nevent = fingerly.events.get(\"01a0a84b-e6a2-7c09-9f51-0b3d7a26c8e4\")",{"type":11,"text":47},"An event has the fields listed in [Get an event](\u002Freference\u002Fget-event#response). `suspect_score` is `null` when the request was not scored.",{"type":14,"level":15,"text":49,"id":50},"Verify a checkout","verify-a-checkout",{"type":11,"text":52},"Read the event your client identified, check it belongs to this action and is recent, then decide on its level. See [server-side verification](\u002Fdocs\u002Fserver-side-verification).",{"type":27,"samples":54},[55],{"label":6,"lang":35,"code":56},"def decide(order_id, request_id)\n  event = fingerly.events.get(request_id)\n\n  return \"refuse\" unless event.tag == \"checkout:#{order_id}\"\n  return \"refuse\" if event.occurred_at \u003C Time.now - 120\n\n  case event.suspect_level\n  when \"high\" then \"review\"\n  when \"medium\" then \"challenge\"\n  else \"allow\"\n  end\nrescue Fingerly::APIError => e\n  raise unless e.status == 404\n  \"refuse\"\nend",{"type":14,"level":15,"text":58,"id":59},"Verify a webhook","verify-a-webhook",{"type":11,"text":61},"Check the signature over the raw request body before parsing it. The helper rejects timestamps more than five minutes from now.",{"type":27,"samples":63},[64],{"label":6,"lang":35,"code":65},"class FingerlyWebhooksController \u003C ActionController::API\n  def create\n    payload = request.raw_post\n    valid = Fingerly::Webhook.verify(\n      secret: ENV.fetch(\"FINGERLY_WEBHOOK_SECRET\"),\n      payload: payload,\n      timestamp: request.headers[\"x-fingerly-timestamp\"],\n      signature: request.headers[\"x-fingerly-signature\"],\n    )\n    return head :bad_request unless valid\n\n    event = JSON.parse(payload)\n    FingerlyEventJob.perform_later(event)   # deduplicate on event[\"id\"]\n    head :no_content\n  end\nend",{"type":14,"level":15,"text":67,"id":68},"Rails","rails",{"type":27,"samples":70},[71],{"label":72,"lang":35,"code":73},"config\u002Finitializers\u002Ffingerly.rb","FINGERLY = Fingerly::Client.new(secret_key: Rails.application.credentials.dig(:fingerly, :secret_key))",{"type":14,"level":15,"text":75,"id":76},"API","api",{"type":78,"columns":79,"rows":83},"table",[80,81,82],"Member","Returns","Notes",[84,88,92,96],[85,86,87],"`Fingerly::Client.new(secret_key:, endpoint: nil, timeout: 10)`","client","Thread-safe; create one per process.",[89,90,91],"`events.get(request_id)`","`Fingerly::Event`","Raises `Fingerly::APIError` with `#status` for a non-2xx response.",[93,94,95],"`events.list(from: nil, to: nil, page: 1, limit: 10, visitor: nil, level: nil)`","`Fingerly::EventPage`","Has `rows`, `page` and `page_size`.",[97,98,99],"`Fingerly::Webhook.verify(secret:, payload:, timestamp:, signature:)`","`true` or `false`","Five minutes of tolerance.",[101,102,103,104,105,106,107],{"id":17,"text":16,"level":15},{"id":25,"text":24,"level":15},{"id":39,"text":38,"level":15},{"id":50,"text":49,"level":15},{"id":59,"text":58,"level":15},{"id":68,"text":67,"level":15},{"id":76,"text":75,"level":15},"2026-09-17T08:28:36.000Z",1789667797829]