Castle analyzes user behavior in web and mobile apps to stop fraud before it happens.
See the documentation for how to use this SDK with the Castle APIs
PHP 7.2 or newer, with the curl and json extensions. The library is tested
against PHP 7.2 through 8.5.
Install the latest version with Composer:
composer require castle/castle-phpThen load Composer's autoloader and configure the library with your Castle API secret:
require_once 'vendor/autoload.php';
Castle::setApiKey('YOUR_API_SECRET');As of 4.0 the library lives under the Castle\ namespace, e.g. Castle\Castle,
Castle\Webhook, Castle\RequestContext, Castle\ApiError. This is the
canonical API:
use Castle\Castle;
use Castle\Webhook;
use Castle\WebhookVerificationError;
Castle::setApiKey('YOUR_API_SECRET');
$verdict = Castle::filter([
'request_token' => $requestToken,
'name' => '$registration',
'user' => ['id' => '1234'],
]);
try {
Webhook::verify();
} catch (WebhookVerificationError $e) {
// reject the request
}The historic global class names (Castle, Castle_*, RestModel) are kept as
aliases of their namespaced counterparts, so existing integrations keep working
without changes. Castle_ApiError and Castle\ApiError are the same class, so
instanceof checks and catch blocks work with either name:
try {
Castle::risk([/* ... */]);
} catch (Castle_ApiError $e) {
// still catches the namespaced Castle\ApiError thrown by the library
}New code should prefer the namespaced names; the global aliases are retained for compatibility.
Set the per-request timeout in milliseconds (applied to both connection and
transfer). Defaults to 1000:
Castle::setRequestTimeout(1500);Set the failover strategy used when a risk or filter request cannot be
completed (see Failover):
Castle::setFailoverStrategy(Castle\Failover::ALLOW);Set a specified list of request headers to include with event context (optional, not recommended):
Castle::setUseAllowlist($headers)By default, Castle extracts all the necessary information, such as IP and request headers, from the PHP globals in order to build and send the requests to the Castle API. However in some cases you want to track data to Castle from a context where these globals are not available, eg. when tracking async in a background worker. In this case you can build the request context manually.
By default, the SDK extracts the contextual client IP address from headers in the following priority:
X-Forwarded-ForX-Real-IpREMOTE_ADDR
If the true client IP address is not specified in the above headers, you can manually set the IP address like so:
Castle_RequestContext['ip'] = '1.1.1.1'
$context = Castle_RequestContext::extractJson();Manage lists and their items:
$list = Castle::createList([
'name' => 'Blocklist',
'color' => '$red',
'primary_field' => 'user.email',
]);
$lists = Castle::getAllLists();
$list = Castle::getList($list['id']);
Castle::updateList($list['id'], ['name' => 'Renamed']);
Castle::deleteList($list['id']);
Castle::queryList(['filters' => [['field' => 'name', 'op' => '$eq', 'value' => 'Blocklist']]]);List items:
$item = Castle::createListItem($list['id'], [
'author' => 'user:123',
'primary_value' => 'user@example.com',
]);
Castle::getListItem($list['id'], $item['id']);
Castle::updateListItem($list['id'], $item['id'], ['comment' => 'Flagged for review']);
Castle::queryListItems($list['id'], ['filters' => []]);
Castle::countListItems($list['id'], ['filters' => []]);
Castle::archiveListItem($list['id'], $item['id']);
Castle::unarchiveListItem($list['id'], $item['id']);
Castle::createListItems($list['id'], ['items' => [/* ... */]]);Request or delete the data Castle stores for a user:
Castle::requestUserData([
'identifier' => 'user@example.com',
'identifier_type' => '$email',
]);
Castle::deleteUserData([
'identifier' => 'user@example.com',
'identifier_type' => '$email',
]);When a risk or filter request cannot be completed because of a network
error, timeout or a 5xx response from the Castle API, the SDK returns a
synthetic decision instead of throwing, so your authentication flow keeps
working. The decision is controlled by the failover strategy:
Castle::setFailoverStrategy(Castle\Failover::ALLOW); // default
// Castle\Failover::DENY
// Castle\Failover::CHALLENGE
// Castle\Failover::THROW -- re-raise the underlying exception insteadA failed-over response carries failover => true and a failover_reason:
$verdict = Castle::risk([
'request_token' => $requestToken,
'name' => '$login',
'user' => ['id' => '1234'],
]);
$verdict->failover; // true when the request failed over
$verdict->action; // 'allow', 'deny' or 'challenge'
$verdict->policy['action'];Successful responses include failover => false. Client errors (4xx, such as
422 invalid_request_token) are never failed over and always raise.
Disable outbound tracking calls, for example in staging or test environments.
While disabled, risk, filter and log return an allow response without
contacting the API:
Castle::disableTracking();
Castle::tracked(); // false
Castle::enableTracking();Query event data:
Castle::eventsSchema();
Castle::queryEvents(['filters' => [['field' => 'name', 'op' => '$eq', 'value' => '$login']]]);
Castle::groupEvents(['filters' => [], 'group_by' => 'name']);Verify the authenticity of incoming Castle webhooks. By default the raw body is
read from php://input and the signature from the X-Castle-Signature header:
try {
Castle_Webhook::verify();
// handle the webhook payload
} catch (Castle_WebhookVerificationError $e) {
http_response_code(404);
}The body and signature can also be passed explicitly:
Castle_Webhook::verify($rawBody, $signatureHeader);Whenever something unexpected happens, an exception is thrown to indicate what went wrong.
| Name | Description |
|---|---|
Castle_Error |
A generic error |
Castle_RequestError |
A request failed. Probably due to a network error |
Castle_ApiError |
An unexpected error for the Castle API |
Castle_InternalServerError |
The Castle API returned a 5xx response (triggers failover) |
Castle_ConfigurationError |
The Castle secret API key has not been set |
Castle_UnauthorizedError |
Wrong Castle API secret key |
Castle_BadRequest |
The request was invalid. For example if a challenge is created without the user having MFA enabled. |
Castle_ForbiddenError |
The user has entered the wrong code too many times and a new challenge has to be requested. |
Castle_NotFoundError |
The resource requestd was not found. For example if a session has been revoked. |
Castle_InvalidParametersError |
One or more of the supplied parameters are incorrect. Check the response for more information. |
Castle_InvalidRequestTokenError |
The request token parameter is missing or invalid |
Castle_WebhookVerificationError |
An incoming webhook could not be verified against the X-Castle-Signature header |
Install the dev dependencies and run the suite with:
composer install
composer test