PHP Server SDK
Official PHP server-side SDK — published as captchala/captchala-php on Packagist.
Three jobs the SDK handles for you:
- Validate — verify a
pt_pass token from the browser SDK. - Issue — mint a one-time
sct_server token to bind the upcoming challenge to a specific action / IP / UID. - Moderate — multi-modal (text + image) content moderation against the same OpenAI-compatible pipeline the dashboard uses.
Install
bash
composer require captchala/captchala-phpRequires PHP ≥ 8.0 with ext-json. Uses cURL when available, falls back to file_get_contents.
Validate (pt_ token)
php
use Captchala\Client;
$client = new Client('your_app_key', 'your_app_secret');
$result = $client->validate($_POST['captcha_token']);
if (!$result->isValid()) {
http_response_code(400);
exit($result->getError()); // e.g. "token_expired", "client_ip_mismatch"
}
// Verification passed; proceed with the request.If you issued the original server_token with bind_uid, compare the uid returned with the user you actually expect:
php
if ($result->getUid() !== $expectedUserId) {
http_response_code(400);
exit('user mismatch');
}If you bound the token to an IP, pass the user IP on the verify side too:
php
$client->validate($token, false, $request->ip());Issue a server token
For high-value flows (login, register, payment) the recommended pattern is: backend mints a one-time sct_ token, hands it to the browser, browser passes it as the serverToken prop. Each token is single-use, action-scoped, and optionally bound to IP / UID at issuance time.
php
$issue = $client->issueServerToken(
action: 'login',
bindingIp: $request->ip(),
ttl: 300, // seconds; default 300
maxUses: 5, // SDK retry budget
bindUid: $user->id, // pair with ValidateResult::getUid() on verify
);
if (!$issue->isOk()) {
return ['error' => $issue->getError()]; // rate_limit_exceeded, invalid_action, ...
}
return ['server_token' => $issue->getToken()]; // hand to browserModerate content
Multi-modal — accepts a mix of text and image URLs in OpenAI-compatible format:
php
$result = $client->moderationCheck([
['type' => 'text', 'text' => $userComment],
['type' => 'image_url', 'image_url' => ['url' => $uploadedImageUrl]],
], userId: $user->id);
if (!$result->isOk()) {
// request error: invalid_credentials, no_content, transport failure, ...
return ['error' => $result->getError()];
}
if ($result->isFlagged()) {
// upstream model flagged; inspect categories for the why
if ($result->hasCategory('violence', 'csam')) {
// hard block
}
}Plain-text shortcut:
php
$result = $client->moderationText('user comment here', userId: $user->id);Categories are model-defined (e.g. violence, hate, sexual, self-harm); iterate getCategories() defensively rather than hard-coding a fixed set.
Result classes
| Class | Methods |
|---|---|
ValidateResult | isValid(), getError(), getUid(), getChallengeId(), getAction(), isOffline(), isClientOnly(), getWarning(), toArray() |
IssueResult | isOk(), getToken(), getExpiresIn(), getIssuedAt(), getError(), getMessage(), toArray() |
ModerationResult | isOk(), isFlagged(), hasCategory(...$names), getCategories(), getContentType(), getRaw(), getError(), getMessage(), toArray() |