[{"data":1,"prerenderedAt":103},["ShallowReactive",2],{"doc:\u002Fdocs\u002Fsdks\u002Frust":3},{"page":4,"toc":95,"updated":102},{"path":5,"title":6,"seoTitle":7,"description":8,"blocks":9},"\u002Fdocs\u002Fsdks\u002Frust","Rust","Rust SDK: Async Event Reads and Webhook Verification","Read identification events with a secret key and verify signed webhooks from Rust, with an async client built on Tokio and typed events.",[10,13,18,23,26,37,40,42,47,49,52,54,58,61,63,67,70,93],{"type":11,"text":12},"p","The `fingerly` crate reads stored events by request ID and verifies webhook signatures. The client is async, built on Tokio and `reqwest`, cheap to clone, and safe to share across tasks.",{"type":14,"level":15,"text":16,"id":17},"heading",2,"Requirements","requirements",{"type":19,"items":20},"list",[21,22],"Rust 1.75 or newer, with the Tokio runtime.","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},"Cargo","bash","cargo add fingerly",{"label":34,"lang":35,"code":36},"Cargo.toml","toml","[dependencies]\nfingerly = \"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":45,"code":46},"rust","let fingerly = fingerly::Client::new(std::env::var(\"FINGERLY_SECRET_KEY\")?);\nlet event = fingerly.events().get(\"01a0a84b-e6a2-7c09-9f51-0b3d7a26c8e4\").await?;",{"type":11,"text":48},"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":50,"id":51},"Verify a checkout","verify-a-checkout",{"type":11,"text":53},"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":55},[56],{"label":6,"lang":45,"code":57},"async fn decide(fingerly: &fingerly::Client, order_id: &str, request_id: &str) -> Result\u003CDecision, fingerly::Error> {\n    let event = match fingerly.events().get(request_id).await {\n        Ok(event) => event,\n        Err(fingerly::Error::Api { status: 404, .. }) => return Ok(Decision::Refuse),\n        Err(error) => return Err(error),\n    };\n\n    if event.tag.as_deref() != Some(&format!(\"checkout:{order_id}\")) {\n        return Ok(Decision::Refuse);\n    }\n    if chrono::Utc::now() - event.occurred_at > chrono::Duration::minutes(2) {\n        return Ok(Decision::Refuse);\n    }\n\n    Ok(match event.suspect_level {\n        Some(Level::High) => Decision::Review,\n        Some(Level::Medium) => Decision::Challenge,\n        _ => Decision::Allow,\n    })\n}",{"type":14,"level":15,"text":59,"id":60},"Verify a webhook","verify-a-webhook",{"type":11,"text":62},"Check the signature over the raw request body before parsing it. The helper rejects timestamps more than five minutes from now.",{"type":27,"samples":64},[65],{"label":6,"lang":45,"code":66},"async fn fingerly_webhook(State(state): State\u003CAppState>, headers: HeaderMap, body: Bytes) -> StatusCode {\n    let header = |name| headers.get(name).and_then(|v| v.to_str().ok()).unwrap_or_default();\n\n    if !fingerly::webhook::verify(\n        &state.webhook_secret,\n        &body,\n        header(\"x-fingerly-timestamp\"),\n        header(\"x-fingerly-signature\"),\n    ) {\n        return StatusCode::BAD_REQUEST;\n    }\n\n    let event: fingerly::WebhookEvent = serde_json::from_slice(&body).unwrap();\n    state.queue.enqueue(event.id.clone(), body).await;   \u002F\u002F deduplicate on the event ID\n    StatusCode::NO_CONTENT\n}",{"type":14,"level":15,"text":68,"id":69},"API","api",{"type":71,"columns":72,"rows":76},"table",[73,74,75],"Member","Returns","Notes",[77,81,85,89],[78,79,80],"`fingerly::Client::new(secret_key)`","`Client`","`Client::builder()` sets an endpoint or a `reqwest::Client`.",[82,83,84],"`client.events().get(request_id).await`","`Result\u003CEvent, fingerly::Error>`","A non-2xx response is `Error::Api { status, .. }`.",[86,87,88],"`client.events().list(&ListEvents).await`","`Result\u003CEventPage, fingerly::Error>`","`ListEvents` has `from`, `to`, `page`, `limit`, `visitor`, `level`.",[90,91,92],"`fingerly::webhook::verify(secret, body, timestamp, signature)`","`bool`","Five minutes of tolerance.",{"type":11,"text":94},"Events deserialize with `serde`. Optional fields are `Option`s, and `suspect_level` is a `Level` enum.",[96,97,98,99,100,101],{"id":17,"text":16,"level":15},{"id":25,"text":24,"level":15},{"id":39,"text":38,"level":15},{"id":51,"text":50,"level":15},{"id":60,"text":59,"level":15},{"id":69,"text":68,"level":15},"2026-09-17T08:28:36.000Z",1789667797835]