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

# PHP SDK for KYC, AML and Transactions

> Quickstart for the official Didit PHP SDK: typed calls for the server APIs over any PSR-18 HTTP client, webhook verification and Authentication token checks.

The official Didit library for PHP. It needs PHP 8.1 or later and works with any PSR-18 HTTP client; Guzzle is the usual choice. See the [server SDKs overview](https://docs.didit.me/integration/server-sdks/overview.md) for what every SDK shares.

## Install

```bash theme={null}
composer require didit-protocol/sdk-php guzzlehttp/guzzle
```

The package is [`didit-protocol/sdk-php` on Packagist](https://packagist.org/packages/didit-protocol/sdk-php). Its source and changelog are in [didit-protocol/sdk-php](https://github.com/didit-protocol/sdk-php) on GitHub.

Token verification for [Authentication](#verify-authentication-tokens) also needs the `openssl` extension.

## Configure the client

`new DiditClient()` reads `DIDIT_API_KEY` from the environment. Pass named arguments to override it, or to set the organization and application ids that application-scoped calls (cases, networks, regulatory reports) need. Request fields are one array with the API's own field names; responses are read-only objects with camelCase properties and `toArray()`.

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

require 'vendor/autoload.php';

use Didit\DiditClient;
use Didit\RequestOptions;

$didit = new DiditClient(
    apiKey: getenv('DIDIT_API_KEY') ?: null,
    organizationId: getenv('DIDIT_ORGANIZATION_ID') ?: null,
    applicationId: getenv('DIDIT_APPLICATION_ID') ?: null,
    timeout: 30.0,
    maxRetries: 2,
);

// Options for a single call.
$decision = $didit->sessions->retrieveDecision(
    '11111111-2222-3333-4444-555555555555',
    [],
    new RequestOptions(timeout: 5.0, maxRetries: 0),
);
echo $decision->status, "\n";
```

## Create a session

Create a [verification session](https://docs.didit.me/sessions-api/create-session.md) for a workflow and send your user to its URL.

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

require 'vendor/autoload.php';

use Didit\DiditClient;

$didit = new DiditClient();

$session = $didit->sessions->create([
    'workflow_id' => '11111111-2222-3333-4444-555555555555',
    'vendor_data' => 'user-123',
    'callback' => 'https://example.com/verification/done',
]);
echo $session->url, "\n";
```

## Read the decision

When the user finishes (or when the webhook arrives), [read the decision](https://docs.didit.me/sessions-api/retrieve-session.md). Every feature result is a list with one entry per workflow node.

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

require 'vendor/autoload.php';

use Didit\DiditClient;

$didit = new DiditClient();

$decision = $didit->sessions->retrieveDecision('11111111-2222-3333-4444-555555555555');
echo $decision->status, "\n";
foreach ($decision->idVerifications ?? [] as $verification) {
    echo $verification->status, "\n";
}
```

## Verify a webhook

Pass the raw body, exactly as received, and the request headers. `verify` throws `WebhookVerificationException` for anything that is not a valid, fresh delivery, and returns a typed event otherwise.

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

require 'vendor/autoload.php';

use Didit\DiditClient;
use Didit\Exception\WebhookVerificationException;
use Didit\Webhooks\Events\StatusUpdatedEvent;
use Psr\Http\Message\MessageInterface;

$didit = new DiditClient();

/** @param array<string, string|list<string>>|MessageInterface $headers */
function handleWebhook(DiditClient $didit, string $rawBody, array|MessageInterface $headers, string $secret): int
{
    try {
        $event = $didit->webhooks->verify($rawBody, $headers, $secret);
    } catch (WebhookVerificationException $e) {
        error_log('rejected: ' . $e->reason);
        return 400;
    }
    if ($event instanceof StatusUpdatedEvent) {
        error_log($event->sessionId . ' ' . $event->status);
    }
    return 200;
}

http_response_code(handleWebhook(
    $didit,
    (string) file_get_contents('php://input'),
    getallheaders(),
    (string) getenv('DIDIT_WEBHOOK_SECRET'),
));
```

In Laravel and Symfony, pass `$request->getContent()` and `$request->headers->all()`; with a PSR-7 request, pass the request itself as the headers. While you rotate the secret, pass both: `[$newSecret, $oldSecret]`. See [Webhooks](https://docs.didit.me/integration/webhooks.md) for the events and the signature scheme.

## Screen a transaction

Submit a payment, withdrawal or crypto transfer to [transaction monitoring](https://docs.didit.me/transaction-monitoring/overview.md); the response carries its status and risk score.

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

require 'vendor/autoload.php';

use Didit\DiditClient;

$didit = new DiditClient();

$transaction = $didit->transactions->create([
    'transaction_id' => 'txn-1001',
    'transaction_category' => 'finance',
    'transaction_details' => ['direction' => 'outbound', 'amount' => 1500.0, 'currency' => 'EUR'],
    'subject' => ['entity_type' => 'individual', 'vendor_data' => 'user-123', 'full_name' => 'Maria Garcia'],
]);
echo $transaction->status, ' ', $transaction->score, "\n";
```

## Verify Authentication tokens

`Authentication` checks the access tokens your [Authentication](https://docs.didit.me/authentication/overview.md) issuer signs, in your backend, without calling Didit for each request. Pass a PSR-16 cache so the issuer's keys are fetched once, not on every request. It also exchanges codes, refreshes tokens, checks and revokes sessions, and runs step-ups.

> [!NOTE]
>
> Token verification makes the checks in [Verify a token on your server](https://docs.didit.me/authentication/tokens-and-jwks.md#verify-a-token-on-your-server), against your application's issuer and signing keys.

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

require 'vendor/autoload.php';

use Didit\Authentication\Authentication;
use Didit\Authentication\TokenVerificationException;

// The publishable key (pk_live_... or pk_test_...) carries your issuer URL.
$auth = Authentication::fromPublishableKey('pk_live_...');

function currentUser(Authentication $auth, string $accessToken): ?string
{
    try {
        $claims = $auth->verifyAccessToken($accessToken, audience: 'your-application-id');
    } catch (TokenVerificationException $e) {
        error_log('rejected: ' . $e->reason);
        return null;
    }
    return $claims->sub;
}
```

## Errors

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

require 'vendor/autoload.php';

use Didit\DiditClient;
use Didit\Exception\ApiStatusException;
use Didit\Exception\NotFoundException;

$didit = new DiditClient();

try {
    $didit->sessions->retrieveDecision('11111111-2222-3333-4444-555555555555');
} catch (NotFoundException $e) {
    echo "no such session\n";
} catch (ApiStatusException $e) {
    echo $e->status, ' ', $e->requestId, "\n";
}
```

## Lists and files

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

require 'vendor/autoload.php';

use Didit\DiditClient;
use Didit\File;

$didit = new DiditClient();

// Lists page through every result.
foreach ($didit->sessions->list(['status' => 'Approved']) as $session) {
    echo $session->sessionId, "\n";
}

// Uploads take File::fromPath, File::fromBytes or File::fromStream.
$result = $didit->idVerification->verify([
    'front_image' => File::fromPath('id-front.jpg'),
    'back_image' => File::fromPath('id-back.jpg'),
]);
print_r($result->toArray());
```

Server endpoint pages of the [API reference](https://docs.didit.me/api-reference/overview.md) have a PHP sample for their operation, except for the endpoints and fields that no 1.0.1 release has yet, named in the [overview](https://docs.didit.me/integration/server-sdks/overview.md).


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