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, and a webhook signing secret if you receive webhooks.
Install
implementation("io.fingerly:fingerly-server:0.1.0")
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.
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. 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.
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.
@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();
}
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. |