# Java

> Read identification events with a secret key and verify signed webhooks from Java 11 and newer, and from Kotlin and Scala on the JVM.

Last updated: 2026-09-17

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.

## Requirements

- Java 11 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

```kotlin Gradle
implementation("io.fingerly:fingerly-server:0.1.0")
```

```xml Maven
<dependency>
  <groupId>io.fingerly</groupId>
  <artifactId>fingerly-server</artifactId>
  <version>0.1.0</version>
</dependency>
```

## 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.

```java Java
import io.fingerly.server.FingerlyClient;

FingerlyClient fingerly = FingerlyClient.builder()
    .secretKey(System.getenv("FINGERLY_SECRET_KEY"))
    .build();

Event 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).

```java Java
public String decide(String orderId, String requestId) {
    Event event;
    try {
        event = fingerly.events().get(requestId);
    } catch (FingerlyApiException e) {
        if (e.getStatus() == 404) return "refuse";
        throw e;
    }

    if (!("checkout:" + orderId).equals(event.getTag())) return "refuse";
    if (event.getOccurredAt().isBefore(Instant.now().minus(Duration.ofMinutes(2)))) return "refuse";

    return switch (String.valueOf(event.getSuspectLevel())) {
        case "high" -> "review";
        case "medium" -> "challenge";
        default -> "allow";
    };
}
```

## Verify a webhook

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

```java Java
@PostMapping("/webhooks/fingerly")
public ResponseEntity<Void> receive(
        @RequestBody byte[] body,
        @RequestHeader("x-fingerly-timestamp") String timestamp,
        @RequestHeader("x-fingerly-signature") String signature) {

    if (!Webhooks.verify(webhookSecret, body, timestamp, signature)) {
        return ResponseEntity.badRequest().build();
    }

    WebhookEvent event = Webhooks.parse(body);
    events.enqueue(event.getId(), body);   // deduplicate on the event ID
    return ResponseEntity.noContent().build();
}
```

> **Tip:** Receive webhook bodies as `byte[]` rather than a parsed object, so the signature is checked over the exact bytes Fingerly sent.

## API

| Member | Returns | Notes |
| --- | --- | --- |
| `FingerlyClient.builder().secretKey(key).build()` | `FingerlyClient` | Also `.endpoint(url)` and `.httpClient(client)`. |
| `events().get(requestId)` | `Event` | Throws `FingerlyApiException` with `getStatus()` for a non-2xx response. |
| `events().list(EventListParams)` | `EventPage` | `EventListParams.builder()` takes `from`, `to` (`Instant`), `page`, `limit`, `visitor`, `level`. |
| `Webhooks.verify(secret, body, timestamp, signature)` | `boolean` | Five minutes of tolerance. |
