PHPackages                             botbye/botbye-php-sdk - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Security](/categories/security)
4. /
5. botbye/botbye-php-sdk

ActiveLibrary[Security](/categories/security)

botbye/botbye-php-sdk
=====================

Botbye PHP SDK for bot detection and account takeover protection

v3.0.1(1w ago)030MITPHPPHP &gt;=8.1CI passing

Since Nov 21Pushed 2w agoCompare

[ Source](https://github.com/botbye/botbye-php-sdk)[ Packagist](https://packagist.org/packages/botbye/botbye-php-sdk)[ Docs](https://github.com/botbye/botbye-php-sdk)[ RSS](/packages/botbye-botbye-php-sdk/feed)WikiDiscussions main Synced today

READMEChangelog (10)Dependencies (20)Versions (14)Used By (0)

BotBye PHP SDK
==============

[](#botbye-php-sdk)

PHP SDK for the [BotBye](https://botbye.com) Unified Protection Platform — unifying fraud prevention and real-time event monitoring in one platform.

BotBye goes beyond fixed bot/ATO checks. Risk dimensions and metrics are fully dynamic — you define what to measure and what rules to apply per project. This means the same platform covers bot detection, account takeover, multi-accounting, payment fraud, promotion abuse, or any custom fraud scenario specific to your business.

Requirements
------------

[](#requirements)

- PHP 8.1 or higher
- Composer
- Any PSR-18 compatible HTTP client (Guzzle, Symfony HttpClient, Buzz, etc.)

Installation
------------

[](#installation)

```
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):

```
composer require guzzlehttp/guzzle
```

**Symfony HttpClient**:

```
composer require symfony/http-client nyholm/psr7
```

**Buzz**:

```
composer require kriswallsmith/buzz 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:

```
composer require nyholm/psr7
```

Overview
--------

[](#overview)

The SDK provides three request types for different integration levels:

Request TypeUse CaseWhere It Runs`BotbyeValidationEvent`**Level 1** — Bot filteringProxy or middleware, before user identity is known`BotbyeRiskScoringEvent`**Level 2** — Risk scoring &amp; event loggingApplication layer, when user identity is known`BotbyeFullEvent`**Level 1+2 combined**Application layer when no separate proxy existsAll requests go to a single endpoint (`POST /api/v1/protect/evaluate`) and return a unified response with a decision (`ALLOW`, `CHALLENGE`, `BLOCK`), risk scores per dimension, and triggered signals. Dimensions are dynamic — the platform ships with built-in ones (`bot`, `ato`, `abuse`) but you can define custom dimensions (e.g., `payment_fraud`, `promotion_abuse`) per project without code changes.

Every evaluation call is also recorded as a **protection event** — logged to the analytics pipeline and used to compute real-time metrics that feed the rules engine. Metrics are fully configurable per project: the platform ships with built-in ones (failed logins, distinct IPs per account, device reuse, etc.) and you can define custom metrics for your specific use case (e.g., "failed transactions over $1000 per account in 1 hour"). This means `BotbyeRiskScoringEvent` serves a dual purpose: it both evaluates risk **and** logs the event for future analysis and metric aggregation.

Quick Start
-----------

[](#quick-start)

### 1. Initialize the Client

[](#1-initialize-the-client)

The SDK uses PSR-18 / PSR-17 interfaces — bring your own HTTP client and message factories.

**With Guzzle:**

```
use Botbye\Protection\BotbyeClient;
use Botbye\Protection\BotbyeConfig;
use GuzzleHttp\Client;
use GuzzleHttp\Psr7\HttpFactory;

$config = new BotbyeConfig(
    serverKey: 'your-server-key' // from https://botbye.com/docs/dashboard/project
);

$httpClient = new Client(['timeout' => 2.0]);
$psr17Factory = new HttpFactory();

$client = new BotbyeClient(
    config: $config,
    httpClient: $httpClient,
    requestFactory: $psr17Factory,
    streamFactory: $psr17Factory,
);
```

**With Symfony HttpClient:**

```
use Symfony\Component\HttpClient\Psr18Client;
use Nyholm\Psr7\Factory\Psr17Factory;

$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`):

```
use Botbye\Protection\BotbyeClient;
use Botbye\Protection\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)),
        );
    }
}

$psr17Factory = new Psr17Factory();

$client = new BotbyeClient(
    config: $config,
    httpClient: new WpHttpClient(),
    requestFactory: $psr17Factory,
    streamFactory: $psr17Factory,
);
```

### 2. Bot Validation (Level 1)

[](#2-bot-validation-level-1)

Validate device tokens where user identity is not yet available — at the proxy layer or in a middleware before authentication.

```
use Botbye\Protection\Model\BotbyeValidationEvent;
use Botbye\Common\Headers;

$headers = Headers::fromArray(getallheaders());

$response = $client->evaluate(new BotbyeValidationEvent(
    ip: $_SERVER['REMOTE_ADDR'],
    token: $_GET['botbye_token'] ?? '',
    headers: $headers->jsonSerialize(),
    requestMethod: $_SERVER['REQUEST_METHOD'],
    requestUri: $_SERVER['REQUEST_URI'],
));

if ($response->isBlocked()) {
    http_response_code(403);
    exit('Access denied');
}
```

### 3. Risk Scoring &amp; Event Logging (Level 2)

[](#3-risk-scoring--event-logging-level-2)

Evaluate risk and log events when user identity is known. Each call both scores the request **and** feeds the real-time metrics engine, so you should call `evaluate()` for every significant user action — not just when you need a decision.

```
use Botbye\Protection\Model\BotbyeRiskScoringEvent;
use Botbye\Protection\Model\BotbyeUserInfo;
use Botbye\Protection\Model\EventStatus;
use Botbye\Protection\Model\Decision;

$response = $client->evaluate(new BotbyeRiskScoringEvent(
    ip: $_SERVER['REMOTE_ADDR'],
    headers: $headers->jsonSerialize(),
    user: new BotbyeUserInfo(
        accountId: $userId,
        email: $userEmail,       // optional
        phone: $userPhone,       // optional
    ),
    eventType: 'LOGIN',
    eventStatus: EventStatus::SUCCESSFUL,
    botbyeResult: $_SERVER['HTTP_X_BOTBYE_RESULT'] ?? null, // from Level 1
));

match ($response->decision) {
    Decision::BLOCK     => abort(403),
    Decision::CHALLENGE => showChallenge($response->challenge),
    Decision::ALLOW     => continueRequest(),
};
```

When `botbyeResult` is `null` (no Level 1 upstream), bot validation is automatically bypassed.

#### Event Types

[](#event-types)

`eventType` is an arbitrary string — the server accepts any value. Pass any string that matches your business domain:

```
'LOGIN'
'REGISTRATION'
'TRANSACTION'
'BONUS_CLAIM'
'PASSWORD_RESET'
'WITHDRAWAL'
```

#### Using Level 2 for Event Logging

[](#using-level-2-for-event-logging)

Even when you don't need to act on the decision, sending events builds the metrics profile for the account. This enables rules like "more than 5 failed logins in 10 minutes" or "distinct devices per account in 1 hour":

```
// Log a failed login attempt — feeds metrics even if you don't act on the decision
$client->evaluate(new BotbyeRiskScoringEvent(
    ip: $_SERVER['REMOTE_ADDR'],
    headers: $headers->jsonSerialize(),
    user: new BotbyeUserInfo(accountId: $userId),
    eventType: 'LOGIN',
    eventStatus: EventStatus::FAILED,
));

// Log a custom business event
$client->evaluate(new BotbyeRiskScoringEvent(
    ip: $_SERVER['REMOTE_ADDR'],
    headers: $headers->jsonSerialize(),
    user: new BotbyeUserInfo(accountId: $userId),
    eventType: 'BONUS_CLAIM',
    eventStatus: EventStatus::SUCCESSFUL,
    customFields: ['bonus_id' => 'welcome_100'],
));
```

### 4. Full Evaluation (Level 1+2 Combined)

[](#4-full-evaluation-level-12-combined)

Use when there is no separate proxy layer — validates the device token and evaluates risk in a single call.

```
use Botbye\Protection\Model\BotbyeFullEvent;

$response = $client->evaluate(new BotbyeFullEvent(
    ip: $_SERVER['REMOTE_ADDR'],
    token: $_GET['botbye_token'] ?? '',
    headers: $headers->jsonSerialize(),
    user: new BotbyeUserInfo(accountId: $userId),
    eventType: 'LOGIN',
    eventStatus: EventStatus::FAILED,
));
```

### 5. Phishing Image Tracking

[](#5-phishing-image-tracking)

The phishing tracking pixel is embedded on a protected site; when a phishing clone copies the markup, the pixel is requested with the clone's `Origin`, which lets BotBye record a phishing candidate.

Phishing lives in its own dedicated `BotbyePhishingClient` — **separate from the evaluate `BotbyeClient`**. The project is identified by a public, browser-safe `clientKey` in the URL path, so the client needs **no server key**; you can construct it standalone (it only needs a PSR-18 client and a PSR-17 request factory). On construction it fires a best-effort server-integration init handshake (`POST /api/v1/phishing/init-request/v1/{clientKey}`, guarded to run once per process) reporting this module, and `fetchImage` proxies the pixel via the server `/server` route so the backend can attribute it to this module even when the browser never reaches BotBye directly.

```
use Botbye\Phishing\BotbyePhishingClient;
use Botbye\Phishing\BotbyePhishingConfig;

$phishing = new BotbyePhishingClient(
    new BotbyePhishingConfig(
        endpoint: 'https://verify.botbye.com', // default
        clientKey: '',
    ),
    $httpClient,     // PSR-18 ClientInterface
    $requestFactory, // PSR-17 RequestFactoryInterface
);

// Proxy the browser's pixel request: forward its original query verbatim (it carries
// format / image_id and the JS tag's module_name / module_version).
$res = $phishing->fetchImage($_SERVER['HTTP_ORIGIN'] ?? null, $_GET);

$res->status;   // 200
$res->headers;  // ['Content-Type' => 'image/png', ...]
$res->body;     // string — raw image bytes to relay back to the browser
$res->error;    // ?BotbyeError — non-null on transport failure
```

`fetchImage` returns `BotbyePhishingResponse`:

FieldTypeDescription`status``int`Upstream HTTP status (`0` on transport failure)`headers``array`Response headers (e.g. `Content-Type`)`body``string`Raw image bytes (PNG or SVG, per the forwarded `format` query param)`error``?BotbyeError`Normalized transport error: `timeout`, `connection error`, or `invalid json response`Response
--------

[](#response)

`BotbyeEvaluateResponse` contains:

FieldTypeDescription`requestId``?string`Request UUID`decision``Decision``ALLOW`, `CHALLENGE`, or `BLOCK``riskScore``?float`Overall risk score (0–1)`scores``?array`Per-dimension scores (`bot`, `ato`, `abuse`, ...)`signals``?array`Triggered signal names (e.g., `BruteForce`, `ImpossibleTravel`)`challenge``?BotbyeChallenge`Challenge type (when decision is `CHALLENGE`)`extraData``?BotbyeExtraData`Enriched device data (IP, country, browser, device, etc.)`error``?BotbyeError`Error details (on fallback)`botbyeResult``?string`Encoded result for Level 1→2 propagation```
$response->decision;              // Decision::ALLOW
$response->isBlocked();           // false
$response->riskScore;             // 0.72
$response->scores;                // ['bot' => 0.15, 'ato' => 0.72, 'abuse' => 0.05]
$response->signals;               // ['BruteForce', 'ImpossibleTravel']
$response->challenge?->type;      // 'captcha'
$response->extraData?->country;   // 'US'
```

Level 1 to Level 2 Propagation
------------------------------

[](#level-1-to-level-2-propagation)

When using both levels, propagate the Level 1 result to Level 2 via the `botbyeResult` field from the response. This allows the platform to link both evaluations by `requestId` and combine bot score from Level 1 with risk scores from Level 2 into a single unified result:

```
// Level 1 (proxy) — validate and get result
$l1Response = $client->evaluate(new BotbyeValidationEvent(...));

// Pass botbyeResult to Level 2 (e.g. via header or directly)
$l2Response = $client->evaluate(new BotbyeRiskScoringEvent(
    // ...
    botbyeResult: $l1Response->botbyeResult,
));
```

Configuration
-------------

[](#configuration)

```
$config = new BotbyeConfig(
    serverKey: 'your-server-key', // from https://app.botbye.com
    botbyeEndpoint: 'https://verify.botbye.com', // default
);
```

Timeouts are configured on the HTTP client you provide:

```
// Guzzle
$httpClient = new \GuzzleHttp\Client(['timeout' => 2.0, 'connect_timeout' => 1.0]);

// Symfony
$httpClient = new Psr18Client(HttpClient::create(['timeout' => 2.0, 'max_duration' => 3.0]));
```

### PSR-3 Logger

[](#psr-3-logger)

```
use Monolog\Logger;
use Monolog\Handler\StreamHandler;

$logger = new Logger('botbye');
$logger->pushHandler(new StreamHandler('/var/log/botbye.log', Logger::WARNING));

$client = new BotbyeClient(
    config: $config,
    httpClient: $httpClient,
    requestFactory: $psr17Factory,
    streamFactory: $psr17Factory,
    logger: $logger,
);
```

Error Handling
--------------

[](#error-handling)

The SDK follows a **fail-open** strategy. On network or server errors, `evaluate()` returns a default response (`Decision::ALLOW` with error details) instead of throwing:

```
$response = $client->evaluate($event);

if ($response->error !== null) {
    // Evaluation failed, request was allowed by default
    log($response->error->message);
}
```

`BotbyeException` is only thrown for unrecoverable errors during `sendPayload()` — the client catches these internally and returns the bypass response.

Request Extractors (framework integration)
------------------------------------------

[](#request-extractors-framework-integration)

Instead of building events field-by-field at every call site, describe **once** how to turn your framework's request into a `BotbyeRequestInfo`, then pass only the raw request to the `evaluate*`methods. Build the client with `BotbyeClient::withExtractor(...)` — the extractor is any `callable(mixed): BotbyeRequestInfo`:

```
use Botbye\Protection\BotbyeClient;
use Botbye\Protection\Model\BotbyeRequestInfo;
use Botbye\Common\Headers;
use Illuminate\Http\Request;

$client = BotbyeClient::withExtractor(
    config: $config,
    httpClient: $httpClient,
    requestFactory: $psr17Factory,
    streamFactory: $psr17Factory,
    requestInfoExtractor: fn (Request $request) => new BotbyeRequestInfo(
        ip: $request->ip(),
        headers: Headers::fromArray($request->headers->all())->jsonSerialize(),
        token: $request->query('botbye_token'),
        requestMethod: $request->method(),
        requestUri: $request->getRequestUri(),
    ),
);
```

Now call sites pass only the raw request (plus user/event for Level 2):

```
use Botbye\Protection\Model\BotbyeUserInfo;
use Botbye\Protection\Model\EventStatus;

// Level 1 — bot validation
$l1 = $client->evaluateValidation($request);

// Level 2 — risk scoring & event logging
$l2 = $client->evaluateRiskScoring(
    request: $request,
    user: new BotbyeUserInfo(accountId: $userId),
    eventType: 'LOGIN',
    eventStatus: EventStatus::SUCCESSFUL,
    botbyeResult: $l1->botbyeResult,
);

// Level 1+2 combined (no separate proxy)
$full = $client->evaluateFull($request, new BotbyeUserInfo(accountId: $userId), 'LOGIN', EventStatus::FAILED);
```

An explicit `token` argument overrides the one returned by the extractor. The explicit-event API (`new BotbyeClient(...)` + `evaluate(new BotbyeValidationEvent(...))`) remains available with no extractor.

### Laravel Middleware

[](#laravel-middleware)

```
namespace App\Http\Middleware;

use Botbye\Protection\BotbyeClient;
use Closure;
use Illuminate\Http\Request;

class BotbyeMiddleware
{
    public function __construct(private BotbyeClient $botbye) {}

    public function handle(Request $request, Closure $next)
    {
        if ($this->botbye->evaluateValidation($request)->isBlocked()) {
            abort(403, 'Access denied');
        }

        return $next($request);
    }
}
```

Register the extractor-bound `BotbyeClient` in a service provider:

```
// AppServiceProvider.php
use Botbye\Protection\Model\BotbyeRequestInfo;
use Botbye\Common\Headers;
use Illuminate\Http\Request;

$this->app->singleton(BotbyeClient::class, function ($app) {
    $httpClient = new \GuzzleHttp\Client(['timeout' => 2.0]);
    $factory = new \GuzzleHttp\Psr7\HttpFactory();

    return BotbyeClient::withExtractor(
        config: new BotbyeConfig(serverKey: config('services.botbye.key')),
        httpClient: $httpClient,
        requestFactory: $factory,
        streamFactory: $factory,
        requestInfoExtractor: fn (Request $request) => new BotbyeRequestInfo(
            ip: $request->ip(),
            headers: Headers::fromArray($request->headers->all())->jsonSerialize(),
            token: $request->query('botbye_token'),
            requestMethod: $request->method(),
            requestUri: $request->getRequestUri(),
        ),
    );
});
```

### Symfony Event Subscriber

[](#symfony-event-subscriber)

```
namespace App\EventSubscriber;

use Botbye\Protection\BotbyeClient;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\HttpKernel\KernelEvents;

// Build $botbye via BotbyeClient::withExtractor(..., requestInfoExtractor: fn (Request $r) => new BotbyeRequestInfo(...))
class BotbyeSubscriber implements EventSubscriberInterface
{
    public function __construct(private BotbyeClient $botbye) {}

    public static function getSubscribedEvents(): array
    {
        return [KernelEvents::REQUEST => 'onKernelRequest'];
    }

    public function onKernelRequest(RequestEvent $event): void
    {
        if ($this->botbye->evaluateValidation($event->getRequest())->isBlocked()) {
            throw new AccessDeniedHttpException('Access denied');
        }
    }
}
```

### Phishing from a raw request

[](#phishing-from-a-raw-request)

The phishing client mirrors the same pattern — bind an `Origin` extractor once, then pass the raw request to `fetchImageFromRequest`:

```
use Botbye\Phishing\BotbyePhishingClient;
use Botbye\Phishing\BotbyePhishingConfig;

$phishing = BotbyePhishingClient::withExtractor(
    config: new BotbyePhishingConfig(clientKey: ''),
    httpClient: $httpClient,
    requestFactory: $requestFactory,
    originExtractor: fn ($request) => $request->headers->get('Origin'),
);

// Origin via the extractor; forward the browser's pixel query for attribution
$res = $phishing->fetchImageFromRequest($request, $request->getQueryParams());
```

Helpers
-------

[](#helpers)

HelperDescription`BotbyeEvaluateResponse::bypass($message)`Build a fail-open response (`ALLOW` + `error`) for your own short-circuit paths.`BotbyeErrors`Normalized error message constants: `SDK_ERROR`, `UNKNOWN_ERROR`, `TIMEOUT_ERROR`, `CONNECTION_ERROR`, `JSON_ERROR`.Testing
-------

[](#testing)

```
composer install
vendor/bin/phpunit
```

License
-------

[](#license)

MIT

Support
-------

[](#support)

For support, visit [botbye.com](https://botbye.com) or contact .

###  Health Score

43

—

FairBetter than 89% of packages

Maintenance97

Actively maintained with recent releases

Popularity7

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity51

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~21 days

Recently: every ~13 days

Total

11

Last Release

7d ago

Major Versions

v1.0.4 → v2.0.02026-04-28

v2.2.0 → v3.0.02026-06-16

PHP version history (2 changes)v1.0.0PHP &gt;=8.2

v1.0.1PHP &gt;=8.1

### Community

Maintainers

![](https://www.gravatar.com/avatar/6af7f6729e71d7564a8e307f4a00854f2521a7b19cbc46694fa2f373e7c762ff?d=identicon)[botbye](/maintainers/botbye)

---

Top Contributors

[![botbye](https://avatars.githubusercontent.com/u/151032916?v=4)](https://github.com/botbye "botbye (16 commits)")

---

Tags

securitybot-detectionbotbyeato-protection

###  Code Quality

TestsPHPUnit

### Embed Badge

![Health badge](/badges/botbye-botbye-php-sdk/health.svg)

```
[![Health](https://phpackages.com/badges/botbye-botbye-php-sdk/health.svg)](https://phpackages.com/packages/botbye-botbye-php-sdk)
```

###  Alternatives

[tempest/framework

The PHP framework that gets out of your way.

2.2k34.4k15](/packages/tempest-framework)[flow-php/flow

PHP ETL - Extract Transform Load - Data processing framework

85036.3k](/packages/flow-php-flow)[cakephp/cakephp

The CakePHP framework

8.9k19.5M1.8k](/packages/cakephp-cakephp)[sylius/sylius

E-Commerce platform for PHP, based on Symfony framework.

8.5k5.9M737](/packages/sylius-sylius)[drupal/core-recommended

Locked core dependencies; require this project INSTEAD OF drupal/core.

6942.5M421](/packages/drupal-core-recommended)[shopware/platform

The Shopware e-commerce core

3.4k1.5M3](/packages/shopware-platform)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
