Download the PHP package shieldlabs/shieldlabs-php without Composer

On this page you can find all versions of the php package shieldlabs/shieldlabs-php. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.

FAQ

After the download, you have to make one include require_once('vendor/autoload.php');. After that you have to import the classes with use statements.

Example:
If you use only one package a project is not needed. But if you use more then one package, without a project it is not possible to import the classes with use statements.

In general, it is recommended to use always a project to download your libraries. In an application normally there is more than one library needed.
Some PHP packages are not free to download and because of that hosted in private repositories. In this case some credentials are needed to access such packages. Please use the auth.json textarea to insert credentials, if a package is coming from a private repository. You can look here for more information.

  • Some hosting areas are not accessible by a terminal or SSH. Then it is not possible to use Composer.
  • To use Composer is sometimes complicated. Especially for beginners.
  • Composer needs much resources. Sometimes they are not available on a simple webspace.
  • If you are using private repositories you don't need to share your credentials. You can set up everything on our site and then you provide a simple download link to your team member.
  • Simplify your Composer build process. Use our own command line tool to download the vendor folder as binary. This makes your build process faster and you don't need to expose your credentials for private repositories.
Please rate this library. Is it a good library?

Informations about the package shieldlabs-php

ShieldLabs PHP SDK

Read ShieldLabs identification verdicts on your PHP backend, verify webhooks and turn Risk Scores into decisions.

CI Packagist version

ShieldLabs identifies visitors with device intelligence and scores every identification from 300+ risk signals. New to ShieldLabs? Start free and read the documentation.

How it fits

  1. Browser. The ShieldLabs agent (loaded with @shieldlabs-ai/js or one of the framework packages) runs an identification and hands your page a requestId. The browser never sees a Risk Score or a device ID.
  2. Your backend (this SDK). The page sends the requestId with the protected action (signup, login, checkout). Your backend reads the verdict for it from the History API, or receives it in a signed identification.scored webhook.
  3. Decision. Your backend acts on the Risk Score, the risk band, the detection flags and the identifiers (for example: how many accounts one device ID has used).

Install

Requirements: PHP 8.1 or newer, and ext-curl unless your project already has Guzzle 7 or Symfony HttpClient (see HTTP client and timeouts).

Dependency Why it is there
psr/http-client, psr/http-factory, psr/http-message The standard HTTP interfaces, so any PSR-18 client can carry the requests
php-http/discovery Finds a PSR-18 client and PSR-17 factories that your project already has. It ships an optional Composer plugin: Composer may ask whether to allow it, and the SDK works either way
nyholm/psr7 A small PSR-7 and PSR-17 implementation with no dependencies of its own, so the built-in cURL client works in a project without an HTTP stack

Keys come from the ShieldLabs analytics dashboard at app.shieldlabs.ai. Keep all three on the server (the browser only ever uses the Public Key):

Key Used by Environment variable
Private API Key (sec_...), one per domain ShieldLabs (History API) SHIELDLABS_API_KEY
Secret Key and the registered domain ShieldLabsManagement (Management API) SHIELDLABS_SECRET_KEY, SHIELDLABS_DOMAIN
Webhook signing secret (whsec_...), one per endpoint Webhook::constructEvent() SHIELDLABS_WEBHOOK_SECRET

Quick start

Save this as signup.php next to your vendor/ folder. It reads the verdict for the requestId your frontend sent with the form, then decides:

Try it with php -S localhost:8000 signup.php and a request ID from your browser integration: curl -X POST localhost:8000 -d requestId=<requestId>. With the placeholder key the SDK logs a warning that the key does not look like a Private API Key; use your own key.

Verify a webhook delivery (webhook.php):

A complete runnable app is in examples/.

Guide

Waiting for the verdict

Scoring is asynchronous. The History row of an identification appears about 1-3 seconds after the browser call and can be refined for up to about 10 seconds while follow-up checks finish. Start the identification in the browser when the user begins the action (for example with identifyOnInteraction() from @shieldlabs-ai/js, which starts it when the signup form gets focus), not when the form is submitted, so the verdict is usually stored by the time your backend asks for it. identifications->get() polls for the row and returns the first version it sees. Its timeout option (10 seconds by default) is the total time budget of the call:

The limit per poll applies to the built-in CurlClient, whether the SDK created it or you passed it, and to the Guzzle or Symfony HttpClient that the SDK creates. Another PSR-18 client that you pass as http_client keeps its own timeout (PSR-18 has no way to change it per request), so with such a client the call can end up to one request timeout after the budget.

null also covers an identification that was never stored: when a visitor's IP goes over the per-IP limit of identifications, the browser still receives a request ID, but no row is written for it.

With 'wait' => false the SDK makes one lookup, with the client's timeout and the usual retries. To read the refined version of a row, for example to log the final risk signals, make such a lookup again after about 10 seconds.

The agent also runs background identifications on its own (on clicks and navigation). Their request IDs never reach your page, so the History API can show extra rows for the same visitor. Always look up the request ID that arrived with the action.

Deciding

An Identification carries:

Branch on the score, the band and the flags. Signal names are an open set and weights can be negative, so never add them up yourself.

Risk::evaluate() runs the guard checks from our integration guides. The first failing check gives the reason:

Order Check Reason
1 No identification missing
2 is_replay($requestId) returns true (the SDK stores nothing itself) replayed
3 Older than max_age seconds, by observed_at stale
4 Score above 100 rate_limited
5 Device ID is the all-zero UUID (no usable device signals) no_device_signals
6 A flag from block_flags is set blocked_flag (the flag is in $evaluation->flag)
7 The band is in block_bands blocked_band

Defaults: max_age 300, block_bands [RiskBand::Dangerous], block_flags ['browser_automation', 'javascript_disabled']. They are a starting point; tune them per protected action. For example, a login could ask for a second factor on the suspicious band:

Storing used request IDs

Pass is_replay so that one identification authorizes one action. The SDK stores nothing itself: record each request ID in your database with a unique key, and treat a duplicate-key error as "already used". Keep the rows a little longer than max_age, for example one day: older identifications are refused as stale anyway.

PDO throws exceptions by default since PHP 8.0. With Redis, SET shieldlabs:used:<request ID> 1 NX EX 86400 does the same in one command: a null reply means the request ID was used before.

Searching history for account-abuse checks

Lookup types: ip (dotted IPv4), user_hid, visitor_id, request_id, device_id, session_id and cookie_id. The SDK checks every lookup before sending it: an unknown type, a malformed UUID, an IPv6 address, an empty User HID or a limit outside 1-100 throws ValidationException and sends nothing.

A User HID is sent as one URL path segment in canonical form, so values with @, +, = or spaces (an email address, a base64 string) match exactly. A value that contains /, and the values . and .., cannot be searched and throw ValidationException. User HIDs from UserHid::fromUserId() are 64 hex characters and always work.

history->iterate() returns a lazy generator over every row. It pages with offset, skips rows it already returned (paging while new rows arrive can repeat a row) and stops at the total, at an empty page or after max_items rows (with 'max_items' => 0 it yields nothing and sends no request):

Look up LookupType::UserHid to count the devices behind one account, or LookupType::Ip for one address. Each page is one request, so keep the rate limit below in mind.

Linking identifications to your users

Pass a User HID to the browser agent instead of a raw email or database ID, and compute it on the server:

The result is HMAC-SHA256 as 64 lowercase hex characters: stable for a user and irreversible. Keep the secret on the server; changing it changes every User HID.

Webhooks

Domain profile (Management API)

The Management API allows about 15 requests per minute per client IP and then blocks that IP for 10 minutes. The client never retries a 429: cache the profile rather than reading it on every request.

Errors and retries

Every exception extends ShieldLabs\Exception\ShieldLabsException:

Exception When Retried
ValidationException Invalid arguments or options, before any request No
BadRequestException HTTP 400 No
AuthenticationException HTTP 401 or 403 (wrong, disabled or mismatched key) No
QuotaExceededException HTTP 402 (no identifications left) No
NotFoundException HTTP 404 (often a wrong base URL) No
RateLimitException HTTP 429 History API: yes. Management API: never
ServerException HTTP 5xx, including edge proxy pages Yes
ApiException Any other status (the parent of the classes above) No
ApiConnectionException The request could not be completed Yes
ApiTimeoutException An attempt ran past the timeout Yes
SignatureVerificationException A webhook signature is missing, malformed or wrong Not applicable
WebhookParseException A verified webhook body is not a valid event Not applicable

ApiException has getStatusCode(), getBody() (decoded JSON, or the raw text), getRawBody(), getErrorMessage() and getHeaders(); RateLimitException::getRetryAfter() gives the Retry-After value in seconds. Error bodies differ between endpoints (empty, a bare JSON string, an object, plain text), and the SDK parses all of them without failing.

Error messages never include keys or secrets. A key, secret or domain with characters that cannot be sent in an HTTP header (line breaks, NUL, spaces, non-ASCII) throws ValidationException when the client is created, without repeating the value.

Retries use exponential backoff with jitter (0.5 s base, doubling, at most 8 s), for at most max_retries retries (2 by default). When a 429 or 5xx response carries Retry-After, the retry follows it as sent, up to 10 seconds: Retry-After: 0 or a date in the past retries at once. A 429 without Retry-After is retried after at least 1 second, because the History API counts requests per second. identifications->get() sends no retries while it waits: the next poll takes their place, and after a 429 it waits at least 1 second whatever Retry-After says (see Waiting for the verdict).

Rate limits

API Limit What the SDK does
History API About 15 requests per second per domain, shared by everything that uses the domain's key Retries a 429 after Retry-After as sent (up to 10 seconds), or after at least 1 second when it has none. While identifications->get() waits, a 429 waits at least 1 second, or longer when the schedule or Retry-After asks for more
Management API About 15 requests per minute per client IP, then a 10-minute block Never retries a 429; cache the results

HTTP client and timeouts

timeout (10 seconds by default) limits every HTTP attempt the SDK sends. While identifications->get() waits, a poll is also limited to the time left in its budget (at least 1 second) when the SDK can change the client's timeout. With no http_client option, the SDK picks the client that applies it:

  1. Guzzle 7 or Symfony HttpClient, when php-http/discovery finds one of them in your project. The SDK creates its own instance with the timeout set and redirects turned off.
  2. Otherwise its built-in client on ext-curl, ShieldLabs\Http\CurlClient, which never follows redirects and only speaks HTTP and HTTPS.
  3. Without ext-curl, any other PSR-18 client that discovery finds. The SDK cannot pass the timeout to it, so it logs a warning; pass a configured client instead.

A client that you pass as http_client is used as is, with its own settings, so give it a timeout. With the built-in CurlClient a poll still gets the shorter timeout described above; with another client each poll can take that client's full timeout:

The SDK writes its warnings (an API key in an unexpected format, a timeout it cannot apply, a plain http base URL allowed with allow_insecure_http) to the PHP error log with error_log(), so they never end up in a response. Each warning is logged once per process; under PHP-FPM or php -S that is once per request that creates a client.

A client keeps no mutable state after construction: share one instance, for example as a singleton in your service container, including in long-running PHP workers.

Configuration

new ShieldLabs(array $options):

Option Default Notes
api_key SHIELDLABS_API_KEY Private API Key, visible ASCII characters only. Required. The SDK logs a warning (and never fails) when it does not look like sec_xxxxxxxx-xxxxxxxx-xxxxxxxx; the key itself is never logged
base_url SHIELDLABS_API_BASE_URL, else https://account.shieldlabs.ai Origin of the History API; a trailing /api is removed, so requests never go to /api/api/.... It must use https; plain http is accepted for localhost, 127.0.0.1 and [::1]
allow_insecure_http false Accept a plain http base_url on another host, for a test server. The key then travels unencrypted, and the SDK logs a warning. Without it such a URL throws ValidationException
timeout 10 Seconds per HTTP attempt. Applied to the client the SDK creates; an http_client you pass keeps its own settings. While identifications->get() waits, a poll is limited to the time left (at least 1 second) where the client allows it
max_retries 2 Retries for connection errors, timeouts, 429 and 5xx
http_client Guzzle or Symfony HttpClient when installed, else CurlClient Any Psr\Http\Client\ClientInterface, used as is
request_factory discovered Any Psr\Http\Message\RequestFactoryInterface

new ShieldLabsManagement(array $options) takes secret_key (SHIELDLABS_SECRET_KEY, visible ASCII characters only), domain (SHIELDLABS_DOMAIN; an international domain in its punycode form, xn--...), base_url (SHIELDLABS_MANAGEMENT_BASE_URL, else https://api.shieldlabs.ai; a trailing /v1 is removed; https, or plain http on a loopback host) and the same allow_insecure_http, timeout, max_retries, http_client and request_factory. Unknown option names throw ValidationException. A key, domain or URL option that is missing, null or false (what getenv() returns for an unset variable) falls back to its environment variable.

Reference

API Returns
new ShieldLabs(array $options = []) History API client
$client->identifications->get(string $requestId, array $options = []) ?Model\Identification. Options: wait (true), timeout (10, the total budget in seconds), poll_interval (0.25, the first wait; the later waits are 2, 4, 6 and 8 times it, each at most 2 s, or at most poll_interval when that is longer)
$client->history->search(LookupType\|string $type, string $value, array $options = []) Model\HistoryPage. Options: limit (20, 1-100), offset (0). A user_hid value cannot contain /
$client->history->iterate(LookupType\|string $type, string $value, array $options = []) Generator of Model\Identification. Options: page_size (100), max_items (no limit; 0 yields nothing)
new ShieldLabsManagement(array $options = []) Management API client
$management->getProfile() Model\DomainProfile
$management->getDomain(), ShieldLabsManagement::normalizeDomain(string $domain) The normalized domain
Webhook::verifySignature(string $payload, ?string $header, string\|array $secret) bool
Webhook::constructEvent(string $payload, ?string $header, string\|array $secret) Event\IdentificationScoredEvent\|Event\WebhookPingEvent\|Event\UnknownWebhookEvent
Risk::band(int $score), RiskBand::fromScore(int $score) RiskBand
Risk::isRateLimited(int $score) bool
Risk::evaluate(?Model\Identification $identification, array $options = []) Evaluation (ok, reason, band, flag)
UserHid::fromUserId(string $userId, string $secret) string (64 hex characters)
Http\CurlClient(float $timeout = 10.0, ?float $connectTimeout = null) The built-in PSR-18 client. getTimeout(); withTimeout(float $timeout) returns a copy with another timeout
Model Properties
Model\Identification request_id, visitor_id, device_id, session_id, cookie_id, user_hid (?string; "anonymous" for anonymous checks), domain, public_ip and local_ip (IpInfo), connection_type, os, browser, device_type, traffic_source (TrafficSource), risk_score (int), signals (list of Signal), detection_flags (DetectionFlags), observed_at (?DateTimeImmutable, UTC), source ("webhook" or "history"), raw (the payload as received). Methods: riskBand(), isRateLimited(), hasDeviceSignals(), toArray()
Model\IpInfo ip ("" when unknown), country (English country name such as "Germany", "" when unknown)
Model\TrafficSource channel, referrer_domain, landing_url, click_id_type, utm_source, utm_medium, utm_campaign, utm_content, utm_term
Model\Signal name, weight (int, can be negative), description (History API rows only)
Model\DetectionFlags 19 booleans; get(string $name), active(), toArray()
Model\HistoryPage data (list of Identification), total
Model\DomainProfile domain, remaining_identifications, public_key_masked, secret_key_masked, created_at, raw
Event\IdentificationScoredEvent event_type, schema_version, created_at, data (Identification), raw
Event\WebhookPingEvent, Event\UnknownWebhookEvent event_type, schema_version, created_at, raw

Enums: LookupType, RiskBand (Trusted, Suspicious, Dangerous, RateLimited) and EvaluationReason. Constants: SignalName (known signal names), ConnectionType (known connection types), ShieldLabs::VERSION. All classes live in the ShieldLabs\ namespace.

Compatibility

Development

Refresh the generated client when the API description changes. This does not replace the supported library in this repository.

Without a local PHP:

tests/data/ holds the shared test fixtures that every ShieldLabs server SDK passes. See CONTRIBUTING.md.

Support

License

MIT


All versions of shieldlabs-php with dependencies

PHP Build Version
Package Version
Requires php Version ^8.1
ext-json Version *
nyholm/psr7 Version ^1.8
php-http/discovery Version ^1.19
psr/http-client Version ^1.0
psr/http-factory Version ^1.0
psr/http-message Version ^1.1 || ^2.0
Composer command for our command line client (download client) This client runs in each environment. You don't need a specific PHP version etc. The first 20 API calls are free. Standard composer command

The package shieldlabs/shieldlabs-php contains the following files

Loading the files please wait ...