---
title: "sdk/php"
description: "PHP 8.1+ server SDK for networkless JWT verification, PSR-7 request authentication, and webhook signature validation."
locale: "en"
---

> Documentation Index
> Fetch the relevant documentation index at: https://xid.fngx.org/sdks/llms.txt
> Use this file to discover all available pages before exploring further.

# sdk/php

## Status

Implemented and verified locally. Real IdP round-trip verification (JWKS fetch, token sign/verify against a live XID instance) has not been performed yet and must be completed before production use.

XID publishes no packages to any registry. This SDK is distributed as source in the XID repository; use it from a checkout of the repository.

Request authentication is Bearer-only by default. An application-owned JWT cookie is read only when its exact name is configured. The opaque \_\_Host-xid.rt.\* Core cookie is never scanned or verified locally; exchange it by forwarding the complete Cookie header to exact same-origin POST /v1/sessions/token with redirects disabled, and accept only a response containing the token field.

## Install

PHP 8.1+ required. Core dependencies are pulled automatically by Composer.

```shell
{
  "repositories": [
{ "type": "path", "url": "../xid/sdk/php" }
  ],
  "require": {
"xid/xid": "dev-main"
  }
}

# composer update xid/xid
```

## Quick start

```php
use Xid\XidClient;
use Xid\Exception\TokenException;
use Xid\Exception\JwksException;

$xid = new XidClient([
'issuer'   => 'https://xid.dev',
'audience' => 'your-client-id',
'cache'    => $psrSimpleCacheImpl, // PSR-16; null disables JWKS cache
]);

try {
$claims = $xid->verifyToken($jwtString);
echo $claims->sub();    // user ID
echo $claims->scope();  // "openid profile email"
echo implode(',', $claims->amr()); // "phr" / "otp"
} catch (TokenException $e) {
http_response_code(401);
} catch (JwksException $e) {
http_response_code(503);
}
```

## Authenticate a PSR-7 request

```php
$result = $xid->authenticateRequest($psrRequest);

if ($result->isAuthenticated()) {
$userId = $result->claims()->sub();
} else {
// $result->reason() for server-side logs only
http_response_code(401);
}

$token = $xid->exchangeSessionToken(
'https://app.example.com/account',
$psrRequest->getHeaderLine('Cookie'),
$sessionTokenTransport,
);
```

## Verify webhook

```php
use Xid\Exception\WebhookException;

try {
$payload = $xid->verifyWebhook($psrRequest, 'whsec_...');
$type = $payload->type();  // "user.created"
$data = $payload->data();
} catch (WebhookException $e) {
http_response_code(400);
}
```

## XidClient options

| Key | Default | Description |
| --- | --- | --- |
| `issuer` | required | XID issuer URI |
| `audience` | `null` | Expected audience; null skips validation |
| `cache` | `null` | PSR-16 CacheInterface for JWKS caching |
| `jwks_ttl` | `3600` | JWKS cache TTL in seconds |
| `clock_leeway` | `0` | JWT clock skew tolerance in seconds |
| `cookie_name` | `disabled` | Application-owned JWT cookie name; disabled unless explicitly configured |

## XidClient methods

| Method | Returns | Description |
| --- | --- | --- |
| `verifyToken(string $token)` | `Claims` | Verify JWT string; throws on failure |
| `authenticateRequest(ServerRequestInterface $request)` | `AuthResult` | Authenticate PSR-7 request; does not throw |
| `verifyWebhook(ServerRequestInterface $request, string $secret)` | `WebhookPayload` | Validate webhook signature; throws on failure |
| `refreshJwks()` | `void` | Force-refresh the JWKS cache |

## Platform notes

- Uses `firebase/php-jwt` for ES256/RS256 verification. HS256 and `none` alg are rejected.
- Requires a PSR-7 request object for `authenticateRequest` and `verifyWebhook`. Convert framework-native requests with a PSR-7 bridge if needed.
- Exception hierarchy: `XidException` -&gt; `TokenException`, `JwksException`, `WebhookException`.

Source: https://xid.fngx.org/sdks/php/index.mdx
