# Python

> Sign user tokens and verify webhooks in Python with the nexafon package.

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

The `nexafon` package needs Python 3.9 or later and has no dependencies.

:::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
pip install nexafon
```

## Token endpoint

```python title="app.py"
def may_use_nexafon(user):
    # Everyone who gets a token may reach your workspace's inbox and calls: answer 403 unless the user is your staff,
    # never a customer who signed up. The sample's user is the teammate you invited.
    return user["is_staff"]


def nexafon_token(user):
    # Signed with NEXAFON_SECRET_KEY (sk_test_… or sk_live_…), which never leaves this server.
    # 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.
    return nexafon.create_user_token(
        user["id"], user["name"], "agent", email=user["email"], email_verified=user["email_verified"]
    )
```

:::caution[Send `email_verified` 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.
:::

Keyword options: `secret_key` (defaults to `NEXAFON_SECRET_KEY`) and `expires_in` (seconds, at most 600). Invalid
input raises `nexafon.NexafonTokenError`.

## Webhook receiver

```python title="app.py"
if self.path == "/hooks/nexafon":
    # The raw body, exactly as received: parsing it first changes the bytes the signature covers.
    raw = self.rfile.read(int(self.headers.get("Content-Length", 0)))
    try:
        event = nexafon.verify_webhook(dict(self.headers), raw, os.environ["NEXAFON_WEBHOOK_SECRET"])
    except nexafon.WebhookVerificationError:
        self.send_response(400)
        self.end_headers()
        return
    # Deliveries can repeat: skip an event["id"] you have already handled.
    print(event["type"], event["id"])
    self.send_response(204)
    self.end_headers()
    return
```

With Flask pass `request.headers` and `request.get_data()`; with Django `request.headers` and `request.body`.

The sample is a complete standard-library server (`python3 app.py`).
