[{"data":1,"prerenderedAt":106},["ShallowReactive",2],{"doc:\u002Fdocs\u002Fsdks\u002Fjava":3},{"page":4,"toc":98,"updated":105},{"path":5,"title":6,"seoTitle":7,"description":8,"blocks":9},"\u002Fdocs\u002Fsdks\u002Fjava","Java","Java SDK: Read Events and Verify Webhooks","Read identification events with a secret key and verify signed webhooks from Java 11 and newer, and from Kotlin and Scala on the JVM.",[10,13,18,23,26,38,41,43,48,50,53,55,59,62,64,68,72,75],{"type":11,"text":12},"p","The Java library reads stored events by request ID and verifies webhook signatures. It uses the JDK's `java.net.http` client, and the client is thread-safe: build one and share it.",{"type":14,"level":15,"text":16,"id":17},"heading",2,"Requirements","requirements",{"type":19,"items":20},"list",[21,22],"Java 11 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,34],{"label":30,"lang":31,"code":32,"check":33},"Gradle","kotlin","implementation(\"io.fingerly:fingerly-server:0.1.0\")",false,{"label":35,"lang":36,"code":37},"Maven","xml","\u003Cdependency>\n  \u003CgroupId>io.fingerly\u003C\u002FgroupId>\n  \u003CartifactId>fingerly-server\u003C\u002FartifactId>\n  \u003Cversion>0.1.0\u003C\u002Fversion>\n\u003C\u002Fdependency>",{"type":14,"level":15,"text":39,"id":40},"Read an event","read-an-event",{"type":11,"text":42},"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":44},[45],{"label":6,"lang":46,"code":47},"java","import io.fingerly.server.FingerlyClient;\n\nFingerlyClient fingerly = FingerlyClient.builder()\n    .secretKey(System.getenv(\"FINGERLY_SECRET_KEY\"))\n    .build();\n\nEvent event = fingerly.events().get(\"01a0a84b-e6a2-7c09-9f51-0b3d7a26c8e4\");",{"type":11,"text":49},"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":51,"id":52},"Verify a checkout","verify-a-checkout",{"type":11,"text":54},"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":56},[57],{"label":6,"lang":46,"code":58},"public String decide(String orderId, String requestId) {\n    Event event;\n    try {\n        event = fingerly.events().get(requestId);\n    } catch (FingerlyApiException e) {\n        if (e.getStatus() == 404) return \"refuse\";\n        throw e;\n    }\n\n    if (!(\"checkout:\" + orderId).equals(event.getTag())) return \"refuse\";\n    if (event.getOccurredAt().isBefore(Instant.now().minus(Duration.ofMinutes(2)))) return \"refuse\";\n\n    return switch (String.valueOf(event.getSuspectLevel())) {\n        case \"high\" -> \"review\";\n        case \"medium\" -> \"challenge\";\n        default -> \"allow\";\n    };\n}",{"type":14,"level":15,"text":60,"id":61},"Verify a webhook","verify-a-webhook",{"type":11,"text":63},"Check the signature over the raw request body before parsing it. The helper rejects timestamps more than five minutes from now.",{"type":27,"samples":65},[66],{"label":6,"lang":46,"code":67},"@PostMapping(\"\u002Fwebhooks\u002Ffingerly\")\npublic ResponseEntity\u003CVoid> receive(\n        @RequestBody byte[] body,\n        @RequestHeader(\"x-fingerly-timestamp\") String timestamp,\n        @RequestHeader(\"x-fingerly-signature\") String signature) {\n\n    if (!Webhooks.verify(webhookSecret, body, timestamp, signature)) {\n        return ResponseEntity.badRequest().build();\n    }\n\n    WebhookEvent event = Webhooks.parse(body);\n    events.enqueue(event.getId(), body);   \u002F\u002F deduplicate on the event ID\n    return ResponseEntity.noContent().build();\n}",{"type":69,"tone":70,"text":71},"callout","tip","Receive webhook bodies as `byte[]` rather than a parsed object, so the signature is checked over the exact bytes Fingerly sent.",{"type":14,"level":15,"text":73,"id":74},"API","api",{"type":76,"columns":77,"rows":81},"table",[78,79,80],"Member","Returns","Notes",[82,86,90,94],[83,84,85],"`FingerlyClient.builder().secretKey(key).build()`","`FingerlyClient`","Also `.endpoint(url)` and `.httpClient(client)`.",[87,88,89],"`events().get(requestId)`","`Event`","Throws `FingerlyApiException` with `getStatus()` for a non-2xx response.",[91,92,93],"`events().list(EventListParams)`","`EventPage`","`EventListParams.builder()` takes `from`, `to` (`Instant`), `page`, `limit`, `visitor`, `level`.",[95,96,97],"`Webhooks.verify(secret, body, timestamp, signature)`","`boolean`","Five minutes of tolerance.",[99,100,101,102,103,104],{"id":17,"text":16,"level":15},{"id":25,"text":24,"level":15},{"id":40,"text":39,"level":15},{"id":52,"text":51,"level":15},{"id":61,"text":60,"level":15},{"id":74,"text":73,"level":15},"2026-09-17T16:59:11.000Z",1789667797824]