# PHP

> Sign user tokens and verify webhooks in PHP with nexafon/nexafon-php.

Source: https://docs.nexafone.so/v1/server/php/

`nexafon/nexafon-php` needs PHP 8.1 or later with ext-curl, and no framework.

:::note[Not published yet]
The Nexafon packages are not on npm, Packagist or PyPI yet, and `go get` finds the Go module only once the Nexafon
repository is public and tagged, so installing them by name fails for now. Until then,
[install them from a checkout of the repo](/v1/quickstart/#install-from-a-checkout). The install commands shown here
are the ones to use after release.
:::

```sh
composer require nexafon/nexafon-php
```

## Token endpoint

```php title="nexafon-token.php"
if (!nexafon_may_use($user)) {
    http_response_code(403);
    exit;
}
// Signed with NEXAFON_SECRET_KEY (sk_test_… or sk_live_…), which never leaves this server.
$token = Tokens::createUserToken([
    'userId' => $user['id'],
    'name' => $user['name'],
    'role' => 'agent',
    'email' => $user['email'],
    // Nexafon links a first sign-in to the member with this email only when it is verified: never send true for
    // an address your app has not confirmed, or whoever sets a member's email on your app acts as that member.
    'emailVerified' => $user['emailVerified'],
]);
header('Content-Type: text/plain');
header('Cache-Control: no-store');
echo $token;
```

:::caution[Send `emailVerified` as true only for a confirmed address]
Nexafon links a user’s first sign-in to the workspace member with the token’s email only when it is verified. If your
app lets users set an email it has not confirmed, someone could register with a member’s address and act as that
member. Send `true` only when your app has confirmed the address, from your own user record, never as a constant.
:::

Options (second argument): `secretKey` (defaults to `NEXAFON_SECRET_KEY`) and `expiresIn` (seconds, at most 600).
Invalid input throws `Nexafon\TokenException`.

## Webhook receiver

```php title="webhook.php"
// The raw body, exactly as received: parsing it first changes the bytes the signature covers.
$body = file_get_contents('php://input');
try {
    $event = Webhook::verify(getallheaders(), $body, getenv('NEXAFON_WEBHOOK_SECRET'));
} catch (WebhookVerificationException) {
    http_response_code(400);
    exit;
}
// Deliveries can repeat: skip an $event['id'] you have already handled.
error_log($event['type'] . ' ' . $event['id']);
http_response_code(204);
```

`Webhook::verify` takes `getallheaders()`, `$_SERVER` or a PSR-7 request’s `getHeaders()`, and a list of secrets
while you rotate. In Laravel or Symfony pass `$request->getContent()` as the body.

The full files are `docs/snippets/php/nexafon-token.php` and `docs/snippets/php/webhook.php` in the repository.
