# Get Webhook Secret

`GET https://api.vultrinference.com/v1/webhooks/secret`

The secret your webhooks are signed with, created on first request. It is separate from your API key, so a webhook receiver never holds a key that can spend.

Each delivery is a `POST` of `{type, created_at, data}` with `data` the job as `GET /v1/videos/{id}` or `GET /v1/music/{id}` returns it. Events: `video.generation.completed`, `video.generation.failed`, `video.generation.cancelled`, `video.generation.expired`, and the same four as `music.generation.*` for music jobs. Headers:

- `X-Vultr-Signature: t=<unix>,v1=<hex>`, the HMAC-SHA256 of `<t>.<raw body>` with this secret. Reject old timestamps. During a rotation a second `v1=` is signed with the previous secret.
- `X-Vultr-Idempotency-Key: <job id>-<status>`; retried deliveries repeat it.

A `2xx` within 10 seconds is delivered; anything else is retried after 30 s, 2 min, 10 min, 1 h and 6 h. Deliveries can arrive more than once or out of order: use the idempotency key, and the job's `status`, rather than arrival order.

**Verifying a delivery.** Compute the HMAC over the raw request body, before any JSON parsing, with the whole secret including its `whsec_` prefix. Accept the delivery when any `v1=` value matches.

```python
import hashlib, hmac, time

def verify(raw_body: bytes, header: str, secret: str, tolerance: int = 300) -> bool:
    pairs = [part.split("=", 1) for part in header.split(",")]
    t = next((v for k, v in pairs if k == "t"), None)
    if t is None or abs(time.time() - int(t)) > tolerance:
        return False
    expected = hmac.new(secret.encode(), t.encode() + b"." + raw_body, hashlib.sha256).hexdigest()
    return any(k == "v1" and hmac.compare_digest(expected, v) for k, v in pairs)
```

```javascript
const crypto = require("crypto");

function verify(rawBody, header, secret, tolerance = 300) {
  const pairs = header.split(",").map((part) => part.split("="));
  const t = pairs.find(([k]) => k === "t")?.[1];
  if (!t || Math.abs(Date.now() / 1000 - Number(t)) > tolerance) return false;
  const expected = crypto.createHmac("sha256", secret).update(`${t}.`).update(rawBody).digest();
  return pairs.some(([k, v]) => k === "v1" && v.length === 64 &&
    crypto.timingSafeEqual(expected, Buffer.from(v, "hex")));
}
```

## Responses

- `200`: OK (webhook-secret-payload)
- `401`: The API key is missing, unknown or not active. Send it as Authorization: Bearer <key>. (error-payload)

## Example

```bash
curl https://api.vultrinference.com/v1/webhooks/secret \
  -H "Authorization: Bearer $VULTR_INFERENCE_API_KEY"
```

```python
import os
import requests

response = requests.get(
    "https://api.vultrinference.com/v1/webhooks/secret",
    headers={"Authorization": f"Bearer {os.environ['VULTR_INFERENCE_API_KEY']}"},
)
print(response.json())
```

```javascript
const response = await fetch("https://api.vultrinference.com/v1/webhooks/secret", {
  headers: {
    Authorization: `Bearer ${process.env.VULTR_INFERENCE_API_KEY}`,
  },
});
console.log(await response.json());
```
