# PHP

> Read identification events with a secret key and verify signed webhooks from PHP 8.1 and newer, with Laravel and Symfony examples.

Last updated: 2026-09-17

The `fingerly/fingerly-php` package reads stored events by request ID and verifies webhook signatures. It uses any PSR-18 HTTP client and falls back to Guzzle.

## Requirements

- PHP 8.1 or newer.
- A [secret key](https://docs.fingerly.io/docs/api-keys), and a webhook signing secret if you receive [webhooks](https://docs.fingerly.io/docs/webhooks).

## Install

```bash Terminal
composer require fingerly/fingerly-php
```

## Read an event

Create one client with your secret key and reuse it. The key decides the regional API and the environment the client reads.

```php PHP
<?php

$fingerly = new \Fingerly\Client(getenv('FINGERLY_SECRET_KEY'));
$event = $fingerly->events->get('01a0a84b-e6a2-7c09-9f51-0b3d7a26c8e4');
```

An event has the fields listed in [Get an event](https://docs.fingerly.io/reference/get-event#response). `suspect_score` is `null` when the request was not scored.

## Verify a checkout

Read the event your client identified, check it belongs to this action and is recent, then decide on its level. See [server-side verification](https://docs.fingerly.io/docs/server-side-verification).

```php PHP
<?php

use Fingerly\ApiException;

function decide(string $orderId, string $requestId): string
{
    global $fingerly;

    try {
        $event = $fingerly->events->get($requestId);
    } catch (ApiException $e) {
        if ($e->getStatus() === 404) {
            return 'refuse';
        }
        throw $e;
    }

    if ($event->tag !== "checkout:{$orderId}") {
        return 'refuse';
    }
    if ($event->occurredAt < new DateTimeImmutable('-2 minutes')) {
        return 'refuse';
    }

    return match ($event->suspectLevel) {
        'high' => 'review',
        'medium' => 'challenge',
        default => 'allow',
    };
}
```

## Verify a webhook

Check the signature over the raw request body before parsing it. The helper rejects timestamps more than five minutes from now.

```php PHP
<?php

use Fingerly\Webhook;
use Illuminate\Http\Request;

Route::post('/webhooks/fingerly', function (Request $request) {
    $valid = Webhook::verify(
        secret: config('services.fingerly.webhook_secret'),
        payload: $request->getContent(),
        timestamp: $request->header('x-fingerly-timestamp'),
        signature: $request->header('x-fingerly-signature'),
    );
    abort_unless($valid, 400);

    ProcessFingerlyEvent::dispatch($request->json()->all());
    return response()->noContent();
});
```

> **Note:** Exclude the webhook route from CSRF verification. In Laravel, add it to the `except` list of the CSRF middleware.

## API

| Member | Returns | Notes |
| --- | --- | --- |
| `new Client($secretKey)` | `Client` | A second argument takes `endpoint` and an HTTP client. |
| `$client->events->get($requestId)` | `Event` | Throws `ApiException` with `getStatus()` for a non-2xx response. |
| `$client->events->list([...])` | `EventPage` | Keys: `from`, `to`, `page`, `limit`, `visitor`, `level`. |
| `Webhook::verify(secret:, payload:, timestamp:, signature:)` | `bool` | Five minutes of tolerance. |
