Signing & Verification
Authenticate incoming webhook requests using HMAC-SHA256 signatures.
Moviie signs every delivery so you can verify that the request came from the platform and that the body has not been tampered with in transit.
Request headers
Every dispatch includes the following HTTP headers:
| Header | Meaning |
|---|---|
Content-Type | Always application/json. |
User-Agent | Moviie-Webhooks/1.0. |
X-Moviie-Event | Canonical event identifier (for example video.upload.started). |
X-Moviie-Event-Id | Stable envelope id (evt_ + UUID). Same value as data.id. |
X-Moviie-Delivery-Id | Persisted delivery row id in Moviie's database. |
X-Moviie-Attempt | Retry counter: 1 on the first try, then increments with each automatic retry. |
X-Moviie-Signature | HMAC-SHA256 over the raw body: present only when a signing secret is configured. |
Depending on the authentication mode you configure for the endpoint, Moviie also sends
Authorization: Bearer <token> or appends authorization=<token> to the query string.
No other query parameters are added by the dispatcher.
Verifying the signature
When your endpoint has a signing secret configured, each delivery includes
X-Moviie-Signature: sha256=<hex> computed over the raw request body. If no secret is
set, the header is absent: in that case, rely on your URL allowlist and transport security
until you configure signing.
Verify the signature before parsing the JSON body. Never rely on the data fields
alone to authenticate a request.
Node.js
import { createHmac, timingSafeEqual } from "crypto"
function verifyWebhookSignature(
rawBody: string,
signatureHeader: string,
secret: string
): boolean {
const expected = "sha256=" + createHmac("sha256", secret).update(rawBody).digest("hex")
const sigBuf = Buffer.from(signatureHeader)
const expBuf = Buffer.from(expected)
if (sigBuf.length !== expBuf.length) return false
return timingSafeEqual(sigBuf, expBuf)
}
// In your handler: load the secret from env or a secrets manager:
const signingSecret = process.env.MOVIIE_WEBHOOK_SECRET!
const rawBody = await request.text()
const sig = request.headers.get("x-moviie-signature") ?? ""
if (!verifyWebhookSignature(rawBody, sig, signingSecret)) {
return new Response("Unauthorized", { status: 401 })
}
const payload = JSON.parse(rawBody)Python
import hashlib
import hmac
def verify_signature(raw_body: bytes, signature_header: str, secret: str) -> bool:
mac = hmac.new(secret.encode(), raw_body, hashlib.sha256)
expected = "sha256=" + mac.hexdigest()
return hmac.compare_digest(signature_header, expected)Go
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
)
func verifySignature(body []byte, signatureHeader, secret string) bool {
mac := hmac.New(sha256.New, []byte(secret))
mac.Write(body)
expected := "sha256=" + hex.EncodeToString(mac.Sum(nil))
return hmac.Equal([]byte(signatureHeader), []byte(expected))
}Managing your signing secret
Your signing secret is available in Settings → Webhooks → [Endpoint] → Signing secret. Use Regenerate to rotate it: all deliveries dispatched after regeneration use the new secret immediately.