Introduction
The Botbye PHP SDK provides comprehensive bot protection for PHP applications. Built with modern PHP standards (PSR-18/PSR-17), it works with any HTTP client — Guzzle, Symfony HttpClient, Buzz, and others.
Requirements
- PHP 8.1 or higher
- Composer
- Any PSR-18 compatible HTTP client
Installation
Install the SDK via Composer:
1
composer require botbye/botbye-php-sdk
You also need a PSR-18 HTTP client and PSR-17 factories. Install one of the ready-made implementations:
Guzzle (most common):
1
composer require guzzlehttp/guzzle
Symfony HttpClient:
1
composer require symfony/http-client nyholm/psr7
Or write a custom adapter for your HTTP transport (e.g. WordPress wp_remote_request) — in that case you only need a PSR-17 factory:
1
composer require nyholm/psr7
Configuration
Basic Configuration
Initialize the Botbye client with your server-key (available inside your Project) and a PSR-18 HTTP client:
With Guzzle:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
use Botbye\Client\BotbyeClient;
use Botbye\Client\BotbyeConfig;
use GuzzleHttp\Client;
use GuzzleHttp\Psr7\HttpFactory;
$config = new BotbyeConfig(
serverKey: '00000000-0000-0000-0000-000000000000' // Use your project server-key
);
$httpClient = new Client(['timeout' => 2.0]);
$psr17Factory = new HttpFactory();
$client = new BotbyeClient(
config: $config,
httpClient: $httpClient,
requestFactory: $psr17Factory,
streamFactory: $psr17Factory,
);
With Symfony HttpClient:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
use Botbye\Client\BotbyeClient;
use Botbye\Client\BotbyeConfig;
use Symfony\Component\HttpClient\Psr18Client;
use Nyholm\Psr7\Factory\Psr17Factory;
$config = new BotbyeConfig(
serverKey: '00000000-0000-0000-0000-000000000000' // Use your project server-key
);
$httpClient = new Psr18Client();
$psr17Factory = new Psr17Factory();
$client = new BotbyeClient(
config: $config,
httpClient: $httpClient,
requestFactory: $psr17Factory,
streamFactory: $psr17Factory,
);
Custom adapter (e.g. WordPress wp_remote_request):
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
<?php
use Botbye\Client\BotbyeClient;
use Botbye\Client\BotbyeConfig;
use Nyholm\Psr7\Factory\Psr17Factory;
use Psr\Http\Client\ClientInterface;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
// Wrap any HTTP transport as a PSR-18 client
class WpHttpClient implements ClientInterface
{
public function sendRequest(RequestInterface $request): ResponseInterface
{
$response = wp_remote_request((string) $request->getUri(), [
'method' => $request->getMethod(),
'headers' => array_map(
fn(array $v) => implode(', ', $v),
$request->getHeaders(),
),
'body' => (string) $request->getBody(),
'timeout' => 2,
]);
if (is_wp_error($response)) {
throw new \RuntimeException($response->get_error_message());
}
$psr17 = new Psr17Factory();
$psrResponse = $psr17->createResponse(
wp_remote_retrieve_response_code($response),
);
return $psrResponse->withBody(
$psr17->createStream(wp_remote_retrieve_body($response)),
);
}
}
$config = new BotbyeConfig(
serverKey: '00000000-0000-0000-0000-000000000000' // Use your project server-key
);
$psr17Factory = new Psr17Factory();
$client = new BotbyeClient(
config: $config,
httpClient: new WpHttpClient(),
requestFactory: $psr17Factory,
streamFactory: $psr17Factory,
);
Timeouts
Timeouts are configured on the HTTP client you provide:
1
2
3
4
5
6
7
8
9
<?php
// Guzzle
$httpClient = new \GuzzleHttp\Client(['timeout' => 2.0, 'connect_timeout' => 1.0]);
// Symfony HttpClient
$httpClient = new Psr18Client(\Symfony\Component\HttpClient\HttpClient::create([
'timeout' => 2.0,
'max_duration' => 3.0,
]));
PSR-3 Logger Integration
Integrate with any PSR-3 compatible logger (e.g., Monolog):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?php
use Botbye\Client\BotbyeClient;
use Botbye\Client\BotbyeConfig;
use GuzzleHttp\Client;
use GuzzleHttp\Psr7\HttpFactory;
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
// Use your project server-key
$config = new BotbyeConfig(serverKey: '00000000-0000-0000-0000-000000000000');
$httpClient = new Client(['timeout' => 2.0]);
$factory = new HttpFactory();
$logger = new Logger('botbye');
$logger->pushHandler(new StreamHandler('/var/log/botbye.log', Logger::WARNING));
$client = new BotbyeClient(
config: $config,
httpClient: $httpClient,
requestFactory: $factory,
streamFactory: $factory,
logger: $logger,
);
Usage
Evaluate incoming requests to detect bot activity:
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
<?php
use Botbye\Model\BotbyeValidationEvent;
use Botbye\Model\Headers;
$headers = Headers::fromArray(getallheaders());
// Extract the token from wherever you pass it: query param, header, body, etc.
$token = $_GET['botbye_token'] ?? '';
$response = $client->evaluate(new BotbyeValidationEvent(
ip: $_SERVER['REMOTE_ADDR'],
token: $token,
headers: $headers->jsonSerialize(),
requestMethod: $_SERVER['REQUEST_METHOD'],
requestUri: $_SERVER['REQUEST_URI'],
));
if ($response->isBlocked()) {
http_response_code(403);
echo 'Access denied';
exit;
}
// Request is allowed, continue processing
echo 'Welcome!';
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 — 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
<?php
use Botbye\Model\BotbyeValidationEvent;
use Botbye\Model\Headers;
$headers = Headers::fromArray(getallheaders());
// Extract the token from wherever you pass it: query param, header, body, etc.
$token = $_GET['botbye_token'] ?? '';
$response = $client->evaluate(new BotbyeValidationEvent(
ip: $_SERVER['REMOTE_ADDR'],
token: $token,
headers: $headers->jsonSerialize(),
requestMethod: $_SERVER['REQUEST_METHOD'],
requestUri: $_SERVER['REQUEST_URI'],
));
if ($response->isBlocked()) {
http_response_code(403);
echo 'Access denied';
exit;
}
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
<?php
use Botbye\Model\BotbyeRiskScoringEvent;
use Botbye\Model\BotbyeUserInfo;
use Botbye\Model\EventStatus;
use Botbye\Model\Headers;
$headers = Headers::fromArray(getallheaders());
$response = $client->evaluate(new BotbyeRiskScoringEvent(
ip: $_SERVER['REMOTE_ADDR'],
headers: $headers->jsonSerialize(),
user: new BotbyeUserInfo(
accountId: $userId,
username: $username,
email: $email,
phone: $phone,
),
eventType: 'login',
eventStatus: $loginSucceeded ? EventStatus::SUCCESSFUL : EventStatus::FAILED,
botbyeResult: $previousBotbyeResult ?? 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 (gateway, middleware, or entry point): run validate and capture the result:
1
2
3
4
5
6
7
8
9
10
11
<?php
// e.g. in a front controller or request handler
$edgeResponse = $client->evaluate(new BotbyeValidationEvent(
ip: $_SERVER['REMOTE_ADDR'],
token: $token,
headers: $headers->jsonSerialize(),
requestMethod: $_SERVER['REQUEST_METHOD'],
requestUri: $_SERVER['REQUEST_URI'],
));
$edgeBotbyeResult = $edgeResponse->botbyeResult;
// Pass $edgeBotbyeResult downstream — a request variable, 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 an auth service after login attempt
$riskResponse = $client->evaluate(new BotbyeRiskScoringEvent(
ip: $_SERVER['REMOTE_ADDR'],
headers: $headers->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
<?php
use Botbye\Model\BotbyeFullEvent;
use Botbye\Model\BotbyeUserInfo;
use Botbye\Model\EventStatus;
use Botbye\Model\Headers;
$headers = Headers::fromArray(getallheaders());
$token = $_GET['botbye_token'] ?? '';
$response = $client->evaluate(new BotbyeFullEvent(
ip: $_SERVER['REMOTE_ADDR'],
token: $token,
headers: $headers->jsonSerialize(),
requestMethod: $_SERVER['REQUEST_METHOD'],
requestUri: $_SERVER['REQUEST_URI'],
user: new BotbyeUserInfo(
accountId: $userId,
username: $username,
email: $email,
phone: $phone,
),
eventType: 'login',
eventStatus: $loginSucceeded ? EventStatus::SUCCESSFUL : EventStatus::FAILED,
));
if ($response->isBlocked()) {
http_response_code(403);
echo 'Access denied';
exit;
}
Methods Reference
BotbyeClient
evaluate()
Evaluates an incoming request for bot activity.
Parameters:
- event (BotbyeValidationEvent|BotbyeRiskScoringEvent|BotbyeFullEvent) - The event describing the request
BotbyeValidationEvent Properties:
- ip (string) - Client IP address
- token (string) - The Botbye token from the client
- headers (array) - Request headers (use Headers::fromArray()->jsonSerialize())
- requestMethod (string) - HTTP method (GET, POST, etc.)
- requestUri (string) - Request URI
Returns: BotbyeEvaluateResponse
BotbyeEvaluateResponse
Represents the evaluation result.
Properties:
- decision (Decision) - The decision: Decision::ALLOW, Decision::CHALLENGE, or Decision::BLOCK
- riskScore (float) - Risk score from 0.0 to 1.0
- signals (array) - List of detected signals
- scores (array) - Detailed scores by category
Methods:
- isBlocked() (bool) - Returns true if the decision is BLOCK
Headers
Represents HTTP headers.
Static Methods:
- fromArray(array $headers) - Create Headers from an associative array
Error Handling
Error Handling Best Practices:
1. Fail Open - If Botbye service is unavailable, allow requests to proceed
2. Log Errors - Always log errors for monitoring and debugging
3. Graceful Degradation - Handle API errors without breaking user experience
4. Timeout Configuration - Set appropriate timeouts on your HTTP client to avoid blocking requests
Best Practices
Production Usage
1. Environment Variables - Store server keys in environment variables:
1
2
3
4
<?php
$config = new BotbyeConfig(
serverKey: $_ENV['BOTBYE_SERVER_KEY']
);
Performance
1. Connection Pooling - Use default connection pool settings for optimal performance
2. Timeouts - Keep timeouts low (1-2 seconds) on your HTTP client to avoid blocking requests
3. Selective Protection - Only evaluate critical endpoints (login, checkout, etc.)
Security
1. Rate Limiting - Implement rate limiting alongside Botbye protection (can find more about it in Project Overview
2. Custom Fields - Use custom fields to link requests with user sessions for better analysis
Settings
Configuration parameters for BotbyeConfig:
| Setting | Description | Required | Default Value |
|---|---|---|---|
| serverKey | Your BotBye server-key | yes | - |
| botbyeEndpoint | Host of the API Server | no | https://verify.botbye.com |
| contentType | Content type for requests | no | application/json |
Demo
GitHub Repository - Full SDK source code with examples and tests.
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" }
}