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>3.0.1</version>
</dependency>
or
Gradle
1
implementation("com.botbye:java-module:3.0.1")
Configuration
Create a configuration class using your server-key (available inside your Project). Build the client with Botbye.withExtractor(...) and bind a servlet extractor once: the SDK pulls ip/token/headers/method/uri out of each HttpServletRequest, so your handlers pass only the raw request to the evaluate* methods and never assemble events by hand. The type parameter is your framework request type.
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
@Configuration
public class AppConfig {
@Bean
public Botbye<HttpServletRequest> botbye() {
BotbyeConfig config = new BotbyeConfig.Builder()
.serverKey("00000000-0000-0000-0000-000000000000") // Use your project server-key
.build();
return Botbye.withExtractor(config, AppConfig::toRequestInfo);
}
// Maps a servlet request to BotbyeRequestInfo: multi-value headers (the SDK owns the
// flattening/normalization), the botbye token, plus ip/method/uri.
private static BotbyeRequestInfo toRequestInfo(HttpServletRequest request) {
Map<String, List<String>> headers = Collections.list(request.getHeaderNames()).stream()
.collect(Collectors.toMap(h -> h, h -> Collections.list(request.getHeaders(h))));
return new BotbyeRequestInfo(
request.getRemoteAddr(),
request.getParameter("botbye_token"), // token: wherever you pass it — query param, header, body, etc.
new Headers(headers),
request.getMethod(),
request.getRequestURI()
);
}
}
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
Per-Controller
Add evaluate in your controller for granular control over specific endpoints. Inject Botbye<HttpServletRequest> and hand it the raw request — the bound extractor does the rest:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@RestController
@RequestMapping("/api/demo")
public class DemoController {
private final Botbye<HttpServletRequest> botbye;
@Autowired
public DemoController(Botbye<HttpServletRequest> botbye) {
this.botbye = botbye;
}
@PostMapping
public ResponseEntity<Object> post(HttpServletRequest request) {
BotbyeEvaluateResponse response = botbye.evaluateValidation(request);
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
@Component
public class BotbyeFilter extends OncePerRequestFilter {
private final Botbye<HttpServletRequest> botbye;
public BotbyeFilter(Botbye<HttpServletRequest> botbye) {
this.botbye = botbye;
}
@Override
protected void doFilterInternal(
HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain
) throws ServletException, IOException {
var result = botbye.evaluateValidation(request);
if (result.isBlocked()) {
response.setStatus(403);
return;
}
filterChain.doFilter(request, response);
}
}
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
@RestController
@RequestMapping("/api/demo")
public class DemoController {
private final Botbye<HttpServletRequest> botbye;
@Autowired
public DemoController(Botbye<HttpServletRequest> botbye) {
this.botbye = botbye;
}
@PostMapping
public ResponseEntity<Object> post(HttpServletRequest request) {
BotbyeEvaluateResponse response = botbye.evaluateValidation(request);
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. Pass the same HttpServletRequest down to the service so the extractor can attach request context to the risk event:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Service
public class AuthService {
private final Botbye<HttpServletRequest> botbye;
@Autowired
public AuthService(Botbye<HttpServletRequest> botbye) {
this.botbye = botbye;
}
public void onLoginAttempt(HttpServletRequest request, String userId, String email, boolean loginSucceeded) {
BotbyeUserInfo user = new BotbyeUserInfo(userId, null, email, null);
BotbyeEvaluateResponse response = botbye.evaluateRiskScoring(
request,
user,
"login",
loginSucceeded ? BotbyeEventStatus.SUCCESSFUL : BotbyeEventStatus.FAILED
);
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
// e.g. in a Spring filter or controller handler
BotbyeEvaluateResponse edgeResponse = botbye.evaluateValidation(request);
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 (the overload with explicit botbyeResult / customFields):
1
2
3
4
5
6
7
8
9
// e.g. in AuthService.onLoginAttempt()
BotbyeEvaluateResponse riskResponse = botbye.evaluateRiskScoring(
request,
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
@RestController
@RequestMapping("/auth")
public class LoginController {
private final Botbye<HttpServletRequest> botbye;
@Autowired
public LoginController(Botbye<HttpServletRequest> botbye) {
this.botbye = botbye;
}
@PostMapping("/login")
public ResponseEntity<Object> login(HttpServletRequest request) {
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.evaluateFull(
request,
user,
"login",
loginSucceeded ? BotbyeEventStatus.SUCCESSFUL : BotbyeEventStatus.FAILED
);
if (response.isBlocked()) {
return ResponseEntity.status(403).body("Access denied");
}
return ResponseEntity.ok().body("Login successful");
}
}
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'" }
}