Introduction
Integrate Botbye bot protection into your Laravel application using middleware. This guide shows how to protect your Laravel routes with minimal configuration.
Installation
Install the SDK via Composer:
1
composer require botbye/botbye-php-sdk
You also need a PSR-18 HTTP client. Guzzle is the most common choice for Laravel:
1
composer require guzzlehttp/guzzle
Configuration
Register the Botbye client in your AppServiceProvider:
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
<?php
namespace App\Providers;
use Botbye\Client\BotbyeClient;
use Botbye\Client\BotbyeConfig;
use GuzzleHttp\Client;
use GuzzleHttp\Psr7\HttpFactory;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
public function register(): void
{
$this->app->singleton(BotbyeClient::class, function ($app) {
$config = new BotbyeConfig(
// Use your project server-key
serverKey: '00000000-0000-0000-0000-000000000000'
);
$httpClient = new Client(['timeout' => 2.0]);
$factory = new HttpFactory();
return new BotbyeClient(
config: $config,
httpClient: $httpClient,
requestFactory: $factory,
streamFactory: $factory,
);
});
}
public function boot(): void
{
//
}
}
Usage
1. Create a middleware to evaluate requests:
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
<?php
namespace App\Http\Middleware;
use Botbye\Client\BotbyeClient;
use Botbye\Model\BotbyeValidationEvent;
use Botbye\Model\Headers;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class BotbyeMiddleware
{
public function __construct(private BotbyeClient $botbye) {}
public function handle(Request $request, Closure $next): Response
{
$headers = Headers::fromArray($request->headers->all());
// Extract the token from wherever you pass it: query param, header, body, etc.
$token = $request->query('botbye_token', '');
$response = $this->botbye->evaluate(new BotbyeValidationEvent(
ip: $request->ip(),
token: $token,
headers: $headers->jsonSerialize(),
requestMethod: $request->method(),
requestUri: $request->getRequestUri(),
));
if ($response->isBlocked()) {
abort(403, 'Access denied');
}
return $next($request);
}
}
2. Add the middleware to app/Http/Kernel.php:
1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
namespace App\Http;
use Illuminate\Foundation\Http\Kernel as HttpKernel;
class Kernel extends HttpKernel
{
protected $middlewareAliases = [
// ... other middleware
'botbye' => \App\Http\Middleware\BotbyeMiddleware::class,
];
}
3. Apply the middleware to specific routes:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
use Illuminate\Support\Facades\Route;
// Protect a single route
Route::post('/api/checkout', [CheckoutController::class, 'process'])
->middleware('botbye');
// Protect a group of routes
Route::middleware(['botbye'])->group(function () {
Route::post('/api/login', [AuthController::class, 'login']);
Route::post('/api/register', [AuthController::class, 'register']);
Route::post('/api/checkout', [CheckoutController::class, 'process']);
});
// Protect all API routes
Route::middleware(['api', 'botbye'])->prefix('api')->group(function () {
// Your API routes
});
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 — middleware, route handler — when you just want to know: was this request made by a bot? No user or domain context needed. This is the middleware shown above:
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
<?php
namespace App\Http\Middleware;
use Botbye\Client\BotbyeClient;
use Botbye\Model\BotbyeValidationEvent;
use Botbye\Model\Headers;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class BotbyeMiddleware
{
public function __construct(private BotbyeClient $botbye) {}
public function handle(Request $request, Closure $next): Response
{
$headers = Headers::fromArray($request->headers->all());
// Extract the token from wherever you pass it: query param, header, body, etc.
$token = $request->query('botbye_token', '');
$response = $this->botbye->evaluate(new BotbyeValidationEvent(
ip: $request->ip(),
token: $token,
headers: $headers->jsonSerialize(),
requestMethod: $request->method(),
requestUri: $request->getRequestUri(),
));
if ($response->isBlocked()) {
abort(403, 'Access denied');
}
return $next($request);
}
}
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.
event and user are the key fields here — they define what action is being performed and who is performing it, which is what drives the risk score.
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
<?php
namespace App\Services;
use Botbye\Client\BotbyeClient;
use Botbye\Model\BotbyeRiskScoringEvent;
use Botbye\Model\BotbyeUserInfo;
use Botbye\Model\EventStatus;
use Botbye\Model\Headers;
use Illuminate\Http\Request;
class BotbyeRiskService
{
public function __construct(private BotbyeClient $botbye) {}
public function evaluateLogin(Request $request, string $userId, string $email, bool $loginSucceeded): void
{
$headers = Headers::fromArray($request->headers->all());
$response = $this->botbye->evaluate(new BotbyeRiskScoringEvent(
ip: $request->ip(),
headers: $headers->jsonSerialize(),
user: new BotbyeUserInfo(
accountId: $userId,
email: $email,
),
eventType: 'login',
eventStatus: $loginSucceeded ? EventStatus::SUCCESSFUL : EventStatus::FAILED,
botbyeResult: null,
));
if ($response->decision === 'BLOCK') {
// 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 (middleware or route handler): run validate and capture the result:
1
2
3
4
5
6
7
8
9
10
11
<?php
// e.g. in BotbyeMiddleware::handle()
$edgeResponse = $this->botbye->evaluate(new BotbyeValidationEvent(
ip: $request->ip(),
token: $request->query('botbye_token', ''),
headers: Headers::fromArray($request->headers->all())->jsonSerialize(),
requestMethod: $request->method(),
requestUri: $request->getRequestUri(),
));
$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
10
<?php
// e.g. in AuthService or BotbyeRiskService
$riskResponse = $this->botbye->evaluate(new BotbyeRiskScoringEvent(
ip: $request->ip(),
headers: Headers::fromArray($request->headers->all())->jsonSerialize(),
user: new BotbyeUserInfo(accountId: $userId, email: $email),
eventType: 'login',
eventStatus: $loginSucceeded ? EventStatus::SUCCESSFUL : EventStatus::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.
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
44
45
46
47
48
<?php
namespace App\Http\Controllers;
use Botbye\Client\BotbyeClient;
use Botbye\Model\BotbyeFullEvent;
use Botbye\Model\BotbyeUserInfo;
use Botbye\Model\EventStatus;
use Botbye\Model\Headers;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class AuthController extends Controller
{
public function __construct(private BotbyeClient $botbye) {}
public function login(Request $request): JsonResponse
{
$email = $request->input('email');
$password = $request->input('password');
$user = User::where('email', $email)->first();
$loginSucceeded = $user && Hash::check($password, $user->password);
$headers = Headers::fromArray($request->headers->all());
$result = $this->botbye->evaluate(new BotbyeFullEvent(
ip: $request->ip(),
token: $request->query('botbye_token', ''),
headers: $headers->jsonSerialize(),
requestMethod: $request->method(),
requestUri: $request->getRequestUri(),
user: new BotbyeUserInfo(
accountId: $user?->id ?? 'unknown',
email: $email,
),
eventType: 'login',
eventStatus: $loginSucceeded ? EventStatus::SUCCESSFUL : EventStatus::FAILED,
));
if ($result->isBlocked()) {
abort(403, 'Access denied');
}
// Proceed with login
return response()->json(['status' => 'success']);
}
}
Advanced Configuration
Custom HTTP Client
Configure a custom HTTP client with specific timeouts:
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
<?php
namespace App\Providers;
use Botbye\Client\BotbyeClient;
use Botbye\Client\BotbyeConfig;
use GuzzleHttp\Client;
use GuzzleHttp\Psr7\HttpFactory;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
public function register(): void
{
$this->app->singleton(BotbyeClient::class, function ($app) {
$config = new BotbyeConfig(
serverKey: '00000000-0000-0000-0000-000000000000',
);
$httpClient = new Client([
'timeout' => 2.0,
'connect_timeout' => 1.0,
]);
$factory = new HttpFactory();
return new BotbyeClient(
config: $config,
httpClient: $httpClient,
requestFactory: $factory,
streamFactory: $factory,
);
});
}
}
Logging Integration
Integrate with Laravel's logging system:
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
<?php
namespace App\Providers;
use Botbye\Client\BotbyeClient;
use Botbye\Client\BotbyeConfig;
use GuzzleHttp\Client;
use GuzzleHttp\Psr7\HttpFactory;
use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Facades\Log;
class AppServiceProvider extends ServiceProvider
{
public function register(): void
{
$this->app->singleton(BotbyeClient::class, function ($app) {
$config = new BotbyeConfig(
serverKey: '00000000-0000-0000-0000-000000000000'
);
$httpClient = new Client(['timeout' => 2.0]);
$factory = new HttpFactory();
// Use Laravel's PSR-3 compatible logger
return new BotbyeClient(
config: $config,
httpClient: $httpClient,
requestFactory: $factory,
streamFactory: $factory,
logger: Log::channel('botbye'),
);
});
}
}
Configure the log channel in config/logging.php:
1
2
3
4
5
6
7
8
9
10
'channels' => [
// ... other channels
'botbye' => [
'driver' => 'daily',
'path' => storage_path('logs/botbye.log'),
'level' => 'warning',
'days' => 14,
],
],
Best Practices
1. Selective Protection - Only apply middleware to routes that need protection
2. Custom Fields - You can pass user context for better analysis
3. Logging - Monitor Botbye errors and blocked requests
Testing
Mock the Botbye client in your tests:
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
44
45
46
47
48
49
50
51
<?php
namespace Tests\Feature;
use Botbye\Client\BotbyeClient;
use Botbye\Model\BotbyeEvaluateResponse;
use Botbye\Model\Decision;
use Tests\TestCase;
class ProtectedRouteTest extends TestCase
{
public function test_allowed_request()
{
$mockClient = $this->mock(BotbyeClient::class);
$mockClient->shouldReceive('evaluate')
->once()
->andReturn(new BotbyeEvaluateResponse(
decision: Decision::ALLOW,
riskScore: 0.05,
signals: [],
scores: ['bot' => 0.05],
));
$response = $this->post('/api/checkout', [
'item' => 'test',
]);
$response->assertStatus(200);
}
public function test_blocked_request()
{
$mockClient = $this->mock(BotbyeClient::class);
$mockClient->shouldReceive('evaluate')
->once()
->andReturn(new BotbyeEvaluateResponse(
decision: Decision::BLOCK,
riskScore: 0.95,
signals: ['AutomationTool'],
scores: ['bot' => 0.95],
));
$response = $this->post('/api/checkout', [
'item' => 'test',
]);
$response->assertStatus(403);
}
}
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" }
}