Spring (Java)
Spring (Java)

Install

Add the dependency to the project configuration:

Maven

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

or

Gradle

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

Configuration

Create a configuration class using your server-key (available inside your Project):

1
2
3
4
5
6
7
8
9
10
11
12
@Configuration
public class AppConfig {

    @Bean
    public Botbye botbye() {
        BotbyeConfig config = new BotbyeConfig.Builder()
                .serverKey("00000000-0000-0000-0000-000000000000") // Use your project server-key
                .build();

        return 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

Usage

Per-Controller

Add evaluate in your controller for granular control over specific endpoints:

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
@RestController
@RequestMapping("/api/demo")
public class DemoController {
    private final Botbye botbye;

    @Autowired
    public DemoController(Botbye botbye) {
        this.botbye = botbye;
    }

    @PostMapping
    public ResponseEntity<Object> post(HttpServletRequest request) {
        Map<String, String> headers = Collections.list(request.getHeaderNames()).stream()
            .collect(Collectors.toMap(h -> h, request::getHeader));

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

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

        if (response.isBlocked()) {
            return ResponseEntity.status(403).body("Access denied");
        }

        return ResponseEntity.ok().body("hello world!");
    }
}

Global Filter

To protect all requests, create a Spring Boot filter:

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
35
@Component
public class BotbyeFilter extends OncePerRequestFilter {
    private final Botbye botbye;

    public BotbyeFilter(Botbye botbye) {
        this.botbye = botbye;
    }

    @Override
    protected void doFilterInternal(
            HttpServletRequest request,
            HttpServletResponse response,
            FilterChain filterChain
    ) throws ServletException, IOException {
        Map<String, String> headers = Collections.list(request.getHeaderNames()).stream()
            .collect(Collectors.toMap(h -> h, request::getHeader));

        // Extract the token from wherever you pass it: query param, header, body, etc.
        var result = botbye.evaluate(BotbyeValidationEvent.of(
            request.getRemoteAddr(),
            request.getParameter("botbye_token"),
            headers,
            request.getMethod(),
            request.getRequestURI(),
            Collections.emptyMap()
        ));

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

        filterChain.doFilter(request, response);
    }
}

Sample of this code on GitHub

There are three event types — validate, risk, and full — each suited for a different layer of your application.

validate — edge-level bot check

Use at the edge — controller, filter, interceptor — 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
25
26
27
28
29
30
31
32
33
@RestController
@RequestMapping("/api/demo")
public class DemoController {
    private final Botbye botbye;

    @Autowired
    public DemoController(Botbye botbye) {
        this.botbye = botbye;
    }

    @PostMapping
    public ResponseEntity<Object> post(HttpServletRequest request) {
        Map<String, String> headers = Collections.list(request.getHeaderNames()).stream()
            .collect(Collectors.toMap(h -> h, request::getHeader));

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

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

        if (response.isBlocked()) {
            return ResponseEntity.status(403).body("Access denied");
        }

        return ResponseEntity.ok().body("hello world!");
    }
}

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
21
22
23
24
25
26
27
@Service
public class AuthService {
    private final Botbye botbye;

    @Autowired
    public AuthService(Botbye botbye) {
        this.botbye = botbye;
    }

    public void onLoginAttempt(String ip, Map<String, String> headers, String userId, String email, boolean loginSucceeded) {
        BotbyeUserInfo user = new BotbyeUserInfo(userId, null, email, null);

        BotbyeEvaluateResponse response = botbye.evaluate(new BotbyeRiskScoringEvent(
            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
            null  // customFields
        ));

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

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 (controller, filter, interceptor): run validate and capture the result:

1
2
3
4
5
6
7
8
9
10
11
// e.g. in a Spring filter or controller handler
BotbyeEvaluateResponse edgeResponse = botbye.evaluate(BotbyeValidationEvent.of(
    request.getRemoteAddr(),
    token,
    headers,
    request.getMethod(),
    request.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(new BotbyeRiskScoringEvent(
    ip,
    headers,
    user,
    "login",
    loginSucceeded ? BotbyeEventStatus.SUCCESSFUL : BotbyeEventStatus.FAILED,
    edgeBotbyeResult, // botbyeResult
    null              // 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
35
36
37
38
39
40
41
42
43
@RestController
@RequestMapping("/auth")
public class LoginController {
    private final Botbye botbye;

    @Autowired
    public LoginController(Botbye botbye) {
        this.botbye = botbye;
    }

    @PostMapping("/login")
    public ResponseEntity<Object> login(HttpServletRequest request) {
        Map<String, String> headers = Collections.list(request.getHeaderNames()).stream()
            .collect(Collectors.toMap(h -> h, request::getHeader));

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

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

        BotbyeEvaluateResponse response = botbye.evaluate(new BotbyeFullEvent(
            request.getRemoteAddr(),
            token,
            headers,
            request.getMethod(),
            request.getRequestURI(),
            user,
            "login",
            loginSucceeded ? BotbyeEventStatus.SUCCESSFUL : BotbyeEventStatus.FAILED,
            null
        ));

        if (response.isBlocked()) {
            return ResponseEntity.status(403).body("Access denied");
        }

        return ResponseEntity.ok().body("Login successful");
    }
}

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", "token": "..." }
}

Invalid server-key:

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