> ## Documentation Index
> Fetch the complete documentation index at: https://docs.shieldlabs.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# PHP

> Read and evaluate a stored identification, then verify raw-body webhook signatures in your backend.

Read and evaluate a stored identification, then verify raw-body webhook signatures in your backend. This guide uses the supported ShieldLabs package API, not a standalone generated client.

## Before you start

PHP 8.1+, Composer and a Private API Key. Include vendor/autoload.php in your server entrypoint.

Register and verify the website domain in your [analytics dashboard](https://app.shieldlabs.ai/). Use credentials from the same domain and environment as the browser check. For a fresh account, start with [Quick Start](/quickstart).

## Get your credentials

Copy the domain's **Private API Key** from **Integration > API keys** and store it as `SHIELDLABS_API_KEY` in your server environment. For webhook verification, also set `SHIELDLABS_WEBHOOK_SECRET` from **Integration > Webhooks**. Never expose either secret to the browser.

## Install the SDK

```bash theme={null}
composer require shieldlabs/shieldlabs-php
```

## Add the integration

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:

```php theme={null}
<?php

require __DIR__ . '/vendor/autoload.php';

use ShieldLabs\Exception\ShieldLabsException;
use ShieldLabs\Exception\ValidationException;
use ShieldLabs\Risk;
use ShieldLabs\ShieldLabs;

function respond(int $status, array $body): never
{
    http_response_code($status);
    header('Content-Type: application/json');
    echo json_encode($body);
    exit;
}

// One identification authorizes one action, so remember the request IDs you accepted.
// Marker files keep this snippet self-contained; see "Storing used request IDs" below
// for a database version.
function markRequestIdUsed(string $requestId): bool
{
    $marker = sys_get_temp_dir() . '/shieldlabs-used-' . hash('sha256', $requestId);

    return @fopen($marker, 'x') !== false; // mode "x" fails when the file already exists
}

// The requestId arrives as a form field or in a JSON body.
$json = json_decode((string) file_get_contents('php://input'), true);
$requestId = $_POST['requestId'] ?? (is_array($json) ? ($json['requestId'] ?? null) : null);

$shieldlabs = new ShieldLabs(['api_key' => 'sec_your_private_key']); // or new ShieldLabs() to read SHIELDLABS_API_KEY

try {
    // Polls the History API until the verdict is stored (usually 1-3 seconds after
    // the browser call), for up to 10 seconds by default.
    $identification = $shieldlabs->identifications->get(is_string($requestId) ? $requestId : '');
} catch (ValidationException) {
    respond(400, ['error' => 'requestId must be a UUID']);
} catch (ShieldLabsException) {
    respond(503, ['error' => 'verification unavailable']); // unverified is never "clean"
}

$evaluation = Risk::evaluate($identification, [
    'is_replay' => fn(string $id): bool => !markRequestIdUsed($id),
]);

if (!$evaluation->ok) {
    respond(403, ['error' => 'signup refused', 'reason' => $evaluation->reason?->value]);
}

// Create the account here.
respond(200, ['ok' => true]);
```

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

```php theme={null}
<?php

require __DIR__ . '/vendor/autoload.php';

use ShieldLabs\Event\IdentificationScoredEvent;
use ShieldLabs\Exception\SignatureVerificationException;
use ShieldLabs\Exception\WebhookParseException;
use ShieldLabs\Webhook;

$payload = (string) file_get_contents('php://input'); // the raw body, byte for byte
$signature = $_SERVER['HTTP_X_SHIELD_SIGNATURE'] ?? null;

try {
    $event = Webhook::constructEvent($payload, $signature, 'whsec_your_signing_secret'); // or getenv('SHIELDLABS_WEBHOOK_SECRET')
} catch (SignatureVerificationException) {
    http_response_code(401); // not signed with this endpoint's secret
    exit;
} catch (WebhookParseException) {
    http_response_code(400); // signed, but not a webhook event
    exit;
}

if ($event instanceof IdentificationScoredEvent) {
    $identification = $event->data; // the same Identification model the History API returns
    // Store it keyed by $identification->request_id so a repeated delivery is harmless.
}

http_response_code(200); // answer fast: the sender waits at most 1 second
```

A complete runnable app is in [`examples/`](https://github.com/ShieldLabs-ai/shieldlabs-php/tree/20e9e21f63d97101c7551996e55c1a493fc183a0/examples/).

## Test the complete flow

1. Use the Request ID from a real browser check on the same domain as the Private API Key.
2. Confirm the server retrieves that identification and its Risk Score.
3. Test an invalid or missing ID and an unavailable API: none should be treated as a clean identification.
4. For webhooks, test the original raw body with its signature, then change one byte and confirm rejection.

In-memory replay stores in examples are demonstrations, not shared production storage. Claim accepted IDs atomically in a durable database or cache, enforce freshness, authenticate the action, and check the expected domain and user association. Reading a valid identification alone does not authorize a business action.

## Troubleshooting

* No History row: confirm the registered domain, credential/environment match and that the browser remained open while collectors posted.
* Missing or pending verdict: scoring is asynchronous. The server helper waits within a bounded budget; handle a missing result and API errors explicitly.
* Authentication error: use the Private API Key for History, not a Public Key, Management Secret Key or MCP OAuth token.
* Invalid webhook signature: verify the original raw bytes with the endpoint's full signing secret, before trusting parsed JSON.

## Next steps

* [Runnable example](https://github.com/ShieldLabs-ai/shieldlabs-php/tree/20e9e21f63d97101c7551996e55c1a493fc183a0/examples)
* [SDK reference and changelog](https://github.com/ShieldLabs-ai/shieldlabs-php)
* [Identification flow](/api/identification-flow)
* [Server API](/api/server-api)
* [Webhook setup](/setup/webhooks)
* [Content Security Policy](/setup/csp)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.