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

# Verifying Webhooks

To prevent undesired requests to your webhooks, you can verify the webhook signature with a unique secret for your team. This secret can always be used to verify that the webhook sender is valid.

## Why verify webhooks?

A webhook is an HTTP POST request from an API service to a URL you control. You might want to verify these requests to ensure they're from pyannoteAI and not from a malicious actor.

## How to verify webhooks

Each webhook request includes 2 HTTP headers that you can use to verify the request:

* `X-Signature`: The base64 encoded signature of the webhook request.
* `X-Request-Timestamp`: The timestamp of the webhook request in seconds since the Unix epoch.

### 1. Creating the signed content

As the webhook receiver, you need to create the signed content by concatenating the `timestamp` and the raw `body` of the request with a colon (`:`) and prefixing the result with `v0:`.

In code, this looks like:

```python Python theme={null}
signed_content = f"v0:{timestamp}:{body}"
```

<Warning>
  Make sure to use the raw body of the request (without headers or other
  metadata) before serializing to JSON or any other format.
</Warning>

### 2. Retrieving your webhook secret

You can find your webhook secret in the pyannoteAI dashboard.

1. Sign in to your dashboard at [https://dashboard.pyannote.ai](https://dashboard.pyannote.ai)
2. Click on the "Webhooks" page in the sidebar.
3. Click on the button with the eye icon to reveal your webhook secret.
4. Copy the secret to your clipboard.

On the webhook page, you can also rotate your webhook secret. This is useful if you think your secret has been compromised.

### 3. Determining the expected signature

pyannoteAI uses the HMAC-SHA256 algorithm to sign webhook requests. To determine the expected signature, you need to:

1. Create the signed content by concatenating the `timestamp` from the HTTP header and the raw `body` with a colon (`:`), then prefix the result with `v0:`.
2. Compute the HMAC-SHA256 hash of the result using your webhook secret as the key.
3. Base64 encode the result.

In code, this looks like:

```python Python theme={null}
import hmac
import base64
import hashlib

def compute_signature(timestamp, body, secret):
    signed_content = f"v0:{timestamp}:{body}"
    signature = hmac.new(
        key=secret.encode('utf-8'),
        msg=signed_content.encode('utf-8'),
        digestmod=hashlib.sha256
    ).hexdigest()
    return signature
```

### 4. Verifying the signature

Then, simply compare the computed signature with the signature you received in the `X-Signature` header.

```python Python theme={null}
computed_signature = compute_signature(timestamp, body, secret)
signature = request.headers.get("X-Signature")

if computed_signature == signature:
    print("The signature is valid")
else:
    print("The signature is invalid")
```

<Warning>Reject the request if the signature is invalid.</Warning>

### Example FastAPI server

Here's an example of how you can verify a webhook request in a FastAPI server:

```python Python theme={null}
import os
import hmac
import hashlib
from fastapi import FastAPI, Request, HTTPException

app = FastAPI()

# Get key from your environment or edit default value
key = os.getenv("PYANNOTEAI_WEBHOOK_SIGNING_SECRET", "whs_....")


@app.post("/webhook")
async def validate_signature(request: Request):
    body = await request.body()
    headers = request.headers
    timestamp = headers.get("x-request-timestamp")
    received_signature = headers.get("x-signature")

    if not timestamp or not received_signature:
        raise HTTPException(status_code=400, detail="Missing headers")

    signed_content = f"v0:{timestamp}:{body.decode('utf-8')}"
    calculated_signature = hmac.new(
        key=key.encode("utf-8"),
        msg=signed_content.encode("utf-8"),
        digestmod=hashlib.sha256,
    ).hexdigest()

    if not hmac.compare_digest(calculated_signature, received_signature):
        raise HTTPException(status_code=403, detail="Invalid signature")

    # Do something with the payload, now that we know it's valid

    return {"status": "success"}
```
