Java Module

Java Module

Install

Add the dependency to the project configuration:

Maven

1
2
3
4
5
<dependency>
    <groupId>com.botbye</groupId>
    <artifactId>java-module</artifactId>
    <version>3.0.1</version>
</dependency>

or

Gradle

1
implementation("com.botbye:java-module:3.0.1")

Configuration

Create BotbyeConfig with your server-key (available inside your Project):

1
2
3
4
5
BotbyeConfig config = new BotbyeConfig.Builder()
        .serverKey("00000000-0000-0000-0000-000000000000") // Use your project server-key
        .build();

Botbye botbye = new Botbye(config);

Settings

BotbyeConfig contains next configurable parameters:

Setting Description Required Default Value
botbyeEndpoint Host of the API Server no https://verify.botbye.com
serverKey Your BotBye server-key yes -
contentType Content type for API requests no application/json
readTimeout Read timeout for HTTP client no Duration.ofSeconds(2)
writeTimeout Write timeout for HTTP client no Duration.ofSeconds(2)
connectionTimeout Connection timeout for HTTP client no Duration.ofSeconds(2)
callTimeout Total call timeout no Duration.ofSeconds(5)
maxIdleConnections Max idle connections in the pool no 250
keepAliveDuration Keep-alive duration no Duration.ofSeconds(300)
maxRequestsPerHost Max requests per host no 1500
maxRequests Max requests total no 1500

Custom HTTP transport

The BotbyeConfig settings above tune the built-in OkHttp client. If you need a different HTTP stack entirely — your framework's own client, a shared pool, an outbound proxy — the SDK talks to BotBye only through the BotbyeHttpClient interface (OkHttp is just the default, OkHttpBotbyeClient). Implement the interface and pass it to the client; a transport you supply is caller-owned, so the SDK never closes it.

1
2
3
4
5
6
7
8
9
10
11
12
import com.botbye.common.http.BotbyeHttpClient;
import com.botbye.common.http.BotbyeHttpRequest;
import com.botbye.common.http.BotbyeHttpResponse;

class MyHttpClient implements BotbyeHttpClient {
    public String type() { return "my-client"; }
    public BotbyeHttpResponse call(BotbyeHttpRequest request) throws IOException { /* ... */ }
    public CompletableFuture<BotbyeHttpResponse> callAsync(BotbyeHttpRequest request) { /* ... */ }
}

// Pass it to either client (or the withExtractor factory):
Botbye botbye = new Botbye(config, new MyHttpClient());

Usage

There are two ways to call evaluate. Pick based on how request data reaches your code:

  • Explicit events — you build a BotbyeValidationEvent yourself and call botbye.evaluate(event). Use when there is no single framework request object to bind to, or you assemble ip/token/headers from disparate sources.
  • Request extractor — bind a BotbyeRequestExtractor once via Botbye.withExtractor(...), then pass only your raw request to evaluateValidation / evaluateRiskScoring / evaluateFull. Use when one request object carries everything (the typical framework integration).

Both funnel into the same evaluate call and return the same response — the extractor just moves the ip/token/headers/method/uri mapping out of every handler and into one place.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// Approach 1 — explicit event (no extractor): build the event in the handler
public void doGet(HttpServletRequest req, HttpServletResponse resp) {
    Headers headers = new Headers(Collections.list(req.getHeaderNames()).stream()
        .collect(Collectors.toMap(h -> h, h -> Collections.list(req.getHeaders(h)))));

    // Extract the token from wherever you pass it: query param, header, body, etc.
    String token = req.getParameter("botbye_token");

    BotbyeEvaluateResponse response = botbye.evaluate(BotbyeValidationEvent.of(
        req.getRemoteAddr(),
        token,
        headers,
        req.getMethod(),
        req.getRequestURI(),
        Collections.emptyMap()
    ));

    if (response.isBlocked()) {
        resp.setStatus(403);
        return;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// Approach 2 — request extractor: bind the mapping once, pass only the raw request
Botbye<HttpServletRequest> botbye = Botbye.withExtractor(config, req -> {
    Map<String, List<String>> headers = Collections.list(req.getHeaderNames()).stream()
        .collect(Collectors.toMap(h -> h, h -> Collections.list(req.getHeaders(h))));
    return new BotbyeRequestInfo(
        req.getRemoteAddr(),
        req.getParameter("botbye_token"),
        new Headers(headers),
        req.getMethod(),
        req.getRequestURI()
    );
});

public void doGet(HttpServletRequest req, HttpServletResponse resp) {
    BotbyeEvaluateResponse response = botbye.evaluateValidation(req);

    if (response.isBlocked()) {
        resp.setStatus(403);
        return;
    }
}

There are three event types — validate, risk, and full — each suited for a different layer of your application. The examples below use the explicit-event API; with an extractor, call evaluateValidation / evaluateRiskScoring / evaluateFull and pass only the raw request instead.

validate — edge-level bot check

Use at the edge — servlet, filter, request handler — when you just want to know: was this request made by a bot? No user or domain context needed.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public void doGet(HttpServletRequest req, HttpServletResponse resp) {
    Headers headers = new Headers(Collections.list(req.getHeaderNames()).stream()
        .collect(Collectors.toMap(h -> h, h -> Collections.list(req.getHeaders(h)))));

    String token = req.getParameter("botbye_token");

    BotbyeEvaluateResponse response = botbye.evaluate(BotbyeValidationEvent.of(
        req.getRemoteAddr(),
        token,
        headers,
        req.getMethod(),
        req.getRequestURI(),
        Collections.emptyMap()
    ));

    if (response.isBlocked()) {
        resp.setStatus(403);
        return;
    }

    // proceed normally
}

// With an extractor: BotbyeEvaluateResponse response = botbye.evaluateValidation(req);

risk — domain-level risk scoring

Use inside services that already know the user: auth, payments, account management, etc. The purpose shifts from "is this a bot?" to "is something suspicious happening for this user?" — credential stuffing, account takeover, account sharing, logins from a new geo.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// Inside an auth service, after a login attempt
public void onLoginAttempt(String ip, Headers headers, String userId, String email, boolean loginSucceeded) {
    BotbyeUserInfo user = new BotbyeUserInfo(userId, null, email, null);

    BotbyeEvaluateResponse response = botbye.evaluate(BotbyeRiskScoringEvent.of(
        ip,
        headers,
        user,
        "login",
        loginSucceeded ? BotbyeEventStatus.SUCCESSFUL : BotbyeEventStatus.FAILED,
        null, // botbyeResult — if a validate call was made earlier, pass response.getBotbyeResult() here to link the requests; omit if there was no prior validate
        Collections.emptyMap()  // customFields
    ));

    if (response.isBlocked()) {
        // Lock account, trigger MFA, send alert, etc.
    }
}

// With an extractor (when a raw request is available): botbye.evaluateRiskScoring(req, user, "login", status);

Linking validate and risk events

When the same request is evaluated at two layers — for example, once at the edge (validate) and then again inside a domain service (risk) — BotBye can link both events and display them as a single event in the dashboard.

Step 1 — edge layer (servlet filter, gateway handler): run validate and capture the result:

1
2
3
4
5
6
7
8
9
10
11
// e.g. in a servlet filter or gateway handler
BotbyeEvaluateResponse edgeResponse = botbye.evaluate(BotbyeValidationEvent.of(
    req.getRemoteAddr(),
    token,
    headers,
    req.getMethod(),
    req.getRequestURI(),
    Collections.emptyMap()
));
String edgeBotbyeResult = edgeResponse.getBotbyeResult();
// Pass edgeBotbyeResult downstream — a request attribute, function argument, shared context, etc.

Step 2 — domain service (auth, payment, account management): pass it as botbyeResult in the risk call:

1
2
3
4
5
6
7
8
9
10
// e.g. in AuthService.onLoginAttempt()
BotbyeEvaluateResponse riskResponse = botbye.evaluate(BotbyeRiskScoringEvent.of(
    ip,
    headers,
    user,
    "login",
    loginSucceeded ? BotbyeEventStatus.SUCCESSFUL : BotbyeEventStatus.FAILED,
    edgeBotbyeResult,       // botbyeResult — links this risk event to the earlier validate
    Collections.emptyMap()  // customFields
));

getBotbyeResult() returns null when the field is absent — in that case, omit or pass null as botbyeResult and the events will be recorded independently.

full — edge check and domain scoring in one call

Use when you have all context at once: raw request, token, user, and event. A login endpoint is a typical example — it receives the HTTP request and immediately knows the user and outcome.

Equivalent to running validate and risk in a single call.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public void doPost(HttpServletRequest req, HttpServletResponse resp) {
    Headers headers = new Headers(Collections.list(req.getHeaderNames()).stream()
        .collect(Collectors.toMap(h -> h, h -> Collections.list(req.getHeaders(h)))));

    String token = req.getParameter("botbye_token");
    String email = req.getParameter("email");
    String userId = authenticate(email, req.getParameter("password"));
    boolean loginSucceeded = userId != null;

    BotbyeUserInfo user = new BotbyeUserInfo(
        loginSucceeded ? userId : "unknown", null, email, null
    );

    BotbyeEvaluateResponse response = botbye.evaluate(BotbyeFullEvent.of(
        req.getRemoteAddr(),
        token,
        headers,
        user,
        "login",
        loginSucceeded ? BotbyeEventStatus.SUCCESSFUL : BotbyeEventStatus.FAILED,
        req.getMethod(),
        req.getRequestURI(),
        Collections.emptyMap()
    ));

    if (response.isBlocked()) {
        resp.setStatus(403);
        return;
    }

    // proceed normally
}

// With an extractor: botbye.evaluateFull(req, user, "login", status);

Synchronous and asynchronous calls

Every evaluate* method blocks the calling thread until BotBye responds. For non-blocking integrations, each has an async twin that takes the same arguments and returns a CompletableFuture<BotbyeEvaluateResponse>:

Synchronous Asynchronous
evaluate(event) evaluateAsync(event)
evaluateValidation(request) evaluateValidationAsync(request)
evaluateRiskScoring(request, user, eventType, status) evaluateRiskScoringAsync(request, user, eventType, status)
evaluateFull(request, user, eventType, status) evaluateFullAsync(request, user, eventType, status)
1
2
3
4
5
6
botbye.evaluateValidationAsync(request)
    .thenAccept(response -> {
        if (response.isBlocked()) {
            // handle the blocked request off the request thread
        }
    });

The fail-open guarantee is identical to the synchronous path: on a network or server error the future completes normally (never exceptionally) with an ALLOW response carrying the error — so a thenApply/thenAccept chain always sees a usable BotbyeEvaluateResponse.

Examples of BotBye API responses

Blocked (bot detected):

1
2
3
4
5
6
7
{
  "request_id": "f77b2abd-c5d7-44f0-be4f-174b04876583",
  "decision": "BLOCK",
  "risk_score": 0.95,
  "scores": { "bot": 0.95 },
  "signals": ["AutomationTool"]
}

Allowed:

1
2
3
4
5
6
7
{
  "request_id": "f77b2abd-c5d7-44f0-be4f-174b04876583",
  "decision": "ALLOW",
  "risk_score": 0.05,
  "scores": { "bot": 0.05, "ato": 0.02 },
  "signals": []
}

Challenge:

1
2
3
4
5
6
7
8
{
  "request_id": "f77b2abd-c5d7-44f0-be4f-174b04876583",
  "decision": "CHALLENGE",
  "risk_score": 0.65,
  "scores": { "bot": 0.65 },
  "signals": ["SuspiciousFingerprint"],
  "challenge": { "type": "CAPTCHA" }
}

Invalid server-key (SDK fail-open):

1
2
3
4
{
  "decision": "ALLOW",
  "error": { "message": "[BotBye] Bad Request: Invalid Server Key" }
}

Evaluation error — backend could not process the request (fail-closed):

1
2
3
4
5
6
7
8
{
  "request_id": "f77b2abd-c5d7-44f0-be4f-174b04876583",
  "decision": "BLOCK",
  "risk_score": 1.0,
  "scores": {},
  "signals": ["evaluation_error"],
  "error": { "message": "Invalid request body: missing or invalid field 'user.account_id'" }
}