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

# Signature verification

> Verify that webhook requests come from Juo

Each webhook request includes headers that allow you to verify the signature and prevent replay attacks.

**Signature headers:**

| Header           | Description                                 |
| ---------------- | ------------------------------------------- |
| `svix-id`        | Unique message ID                           |
| `svix-timestamp` | Unix timestamp of when the message was sent |
| `svix-signature` | Base64-encoded HMAC-SHA256 signature        |

## Verifying with the Svix SDK

The easiest way to verify signatures is with an official Svix library.

<CodeGroup>
  ```typescript Node.js theme={null}
  import { Webhook } from "svix";

  const secret = process.env.SVIX_SIGNING_SECRET; // from Svix dashboard

  export async function handleWebhook(request: Request): Promise<Response> {
    const wh = new Webhook(secret);

    const payload = await request.text();
    const headers = {
      "svix-id": request.headers.get("svix-id") ?? "",
      "svix-timestamp": request.headers.get("svix-timestamp") ?? "",
      "svix-signature": request.headers.get("svix-signature") ?? "",
    };

    try {
      const event = wh.verify(payload, headers);
      // Process event...
      return new Response("OK", { status: 200 });
    } catch (err) {
      return new Response("Invalid signature", { status: 400 });
    }
  }
  ```

  ```python Python theme={null}
  from svix.webhooks import Webhook, WebhookVerificationError
  import os

  secret = os.environ["SVIX_SIGNING_SECRET"]

  def handle_webhook(body: bytes, headers: dict) -> dict:
      wh = Webhook(secret)
      try:
          event = wh.verify(body, headers)
          return event
      except WebhookVerificationError:
          raise ValueError("Invalid signature")
  ```

  ```go Go theme={null}
  import (
      svix "github.com/svix/svix-webhooks/go"
      "os"
  )

  func handleWebhook(body []byte, headers http.Header) (interface{}, error) {
      secret := os.Getenv("SVIX_SIGNING_SECRET")
      wh, err := svix.NewWebhook(secret)
      if err != nil {
          return nil, err
      }
      return wh.Verify(body, headers)
  }
  ```
</CodeGroup>

## Getting your signing secret

1. Open the Juo Admin Portal and navigate to **Settings → Webhooks**
2. Navigate to your endpoint
3. Copy the **Signing Secret** shown under the endpoint settings

Each endpoint has its own unique signing secret.

## Manual verification

If you prefer not to use the Svix SDK, you can verify the signature manually:

1. Construct the signed content: `{svix-id}.{svix-timestamp}.{raw-body}`
2. Compute HMAC-SHA256 using the signing secret (base64-decoded)
3. Compare the result against the `svix-signature` header (strip the `v1,` prefix)
4. Reject messages older than 5 minutes using `svix-timestamp`

See the [Svix documentation](https://docs.svix.com/receiving/verifying-payloads/how-manual) for details.
