Docs
Get an API key
API reference›Webhooks

Get Webhook Secret

GET /v1/webhooks/secret View .md

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")));
}

OK

application/json · webhook-secret-payload

5 fields
  • object string
    webhook_secret
  • secret string
  • created_at integer
  • rotated_at integer or null

    When the secret was last rotated, in Unix seconds; null until the first rotation.

  • previous_secret_valid_until integer or null

    Until when the previous secret still signs beside this one, in Unix seconds; null outside a rotation.

curl https://api.vultrinference.com/v1/webhooks/secret \
  -H "Authorization: Bearer $VULTR_INFERENCE_API_KEY"
Response · 200
{
    "object": "webhook_secret",
    "secret": "whsec_9a1c...",
    "created_at": 1790000000,
    "rotated_at": 1790000000,
    "previous_secret_valid_until": 0
}