Kotlin Module
Install
Add the dependency to the project configuration:
Maven
1
2
3
4
5
<dependency>
<groupId>com.botbye</groupId>
<artifactId>kotlin-module</artifactId>
<version>3.0.1</version>
</dependency>
or
Gradle
1
implementation("com.botbye:kotlin-module:3.0.1")
Configuration
Create BotbyeConfig with your server-key (available inside your Project):
1
2
3
val config = BotbyeConfig(serverKey = "00000000-0000-0000-0000-000000000000") // Use your project server-key
val botbye = Botbye(config)
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
// Approach 1 — explicit event (no extractor): build the event in the handler
suspend fun doGet(req: HttpServletRequest, resp: HttpServletResponse) {
val headers = Headers(req.headerNames.toList()
.associateWith { req.getHeaders(it).toList() })
// Extract the token from wherever you pass it: query param, header, body, etc.
val token = req.getParameter("botbye_token") ?: ""
val response = botbye.evaluate(BotbyeValidationEvent(
ip = req.remoteAddr,
token = token,
headers = headers,
requestMethod = req.method,
requestUri = req.requestURI,
))
if (response.isBlocked) {
resp.status = 403
return
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// Approach 2 — request extractor: bind the mapping once, pass only the raw request
val botbye: Botbye<HttpServletRequest> = Botbye.withExtractor(config) { req ->
BotbyeRequestInfo(
ip = req.remoteAddr,
token = req.getParameter("botbye_token") ?: "",
headers = Headers(req.headerNames.toList().associateWith { req.getHeaders(it).toList() }),
requestMethod = req.method,
requestUri = req.requestURI,
)
}
suspend fun doGet(req: HttpServletRequest, resp: HttpServletResponse) {
val response = botbye.evaluateValidation(req)
if (response.isBlocked) {
resp.status = 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 — API gateway, route handler, middleware — 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
suspend fun doGet(req: HttpServletRequest, resp: HttpServletResponse) {
val headers = Headers(req.headerNames.toList()
.associateWith { req.getHeaders(it).toList() })
// Extract the token from wherever you pass it: query param, header, body, etc.
val token = req.getParameter("botbye_token") ?: ""
val response = botbye.evaluate(BotbyeValidationEvent(
ip = req.remoteAddr,
token = token,
headers = headers,
requestMethod = req.method,
requestUri = req.requestURI,
))
if (response.isBlocked) {
resp.status = 403
return
}
}
// With an extractor: val 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
suspend fun onLoginAttempt(ip: String, userId: String, email: String, loginSucceeded: Boolean) {
val response = botbye.evaluate(BotbyeRiskScoringEvent(
ip = ip,
headers = Headers(emptyMap()),
user = BotbyeUserInfo(
accountId = userId,
email = email,
),
eventType = "login",
eventStatus = if (loginSucceeded) BotbyeEventStatus.SUCCESSFUL else BotbyeEventStatus.FAILED,
botbyeResult = null, // if a validate call was made earlier, pass response.botbyeResult here to link the requests; omit if there was no prior validate
))
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 (filter, interceptor, gateway handler): run validate and capture the result:
1
2
3
4
5
6
7
8
9
10
// e.g. in a filter or interceptor
val edgeResponse = botbye.evaluate(BotbyeValidationEvent(
ip = req.remoteAddr,
token = token,
headers = headers,
requestMethod = req.method,
requestUri = req.requestURI,
))
val edgeBotbyeResult = edgeResponse.botbyeResult
// 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
// e.g. in AuthService.onLoginAttempt()
val riskResponse = botbye.evaluate(BotbyeRiskScoringEvent(
ip = ip,
headers = Headers(emptyMap()),
user = BotbyeUserInfo(accountId = userId, email = email),
eventType = "login",
eventStatus = if (loginSucceeded) BotbyeEventStatus.SUCCESSFUL else BotbyeEventStatus.FAILED,
botbyeResult = edgeBotbyeResult,
))
botbyeResult is null when absent — in that case, omit or pass null 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.
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
suspend fun handleLogin(req: HttpServletRequest, resp: HttpServletResponse) {
val headers = Headers(req.headerNames.toList()
.associateWith { req.getHeaders(it).toList() })
val token = req.getParameter("botbye_token") ?: ""
val email = req.getParameter("email") ?: ""
val user = findUser(email)
val loginSucceeded = user != null && checkPassword(user, req.getParameter("password") ?: "")
val response = botbye.evaluate(BotbyeFullEvent(
ip = req.remoteAddr,
token = token,
headers = headers,
requestMethod = req.method,
requestUri = req.requestURI,
user = BotbyeUserInfo(
accountId = user?.id ?: "unknown",
email = email,
),
eventType = "login",
eventStatus = if (loginSucceeded) BotbyeEventStatus.SUCCESSFUL else BotbyeEventStatus.FAILED,
))
if (response.isBlocked) {
resp.status = 403
return
}
}
// With an extractor: botbye.evaluateFull(req, user, "login", status)
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
import com.botbye.common.http.BotbyeHttpClient
import com.botbye.common.http.BotbyeHttpRequest
import com.botbye.common.http.BotbyeHttpResponse
class MyHttpClient : BotbyeHttpClient {
override val type = "my-client"
override suspend fun call(request: BotbyeHttpRequest): BotbyeHttpResponse { /* ... */ }
}
// Pass it to either client (or the withExtractor factory):
val botbye = Botbye(config, client = MyHttpClient())
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 — not a known project (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 server key" }
}
Evaluation error — request could not be processed (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'" }
}
SDK never received a decision (fail-open):
1
2
3
4
{
"decision": "ALLOW",
"error": { "message": "connection error" }
}