Skip to content

Webhook signatures

Every webhook request includes an X-Nuez-Signature header. Verify it before processing the event.

nuez signs each webhook with HMAC-SHA256 using a secret unique to each API key. The signature covers the raw request body.

import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"io"
"net/http"
)
func verifyWebhook(r *http.Request, secret string) bool {
sig := r.Header.Get("X-Nuez-Signature")
body, _ := io.ReadAll(r.Body)
mac := hmac.New(sha256.New, []byte(secret))
mac.Write(body)
expected := "sha256=" + hex.EncodeToString(mac.Sum(nil))
return hmac.Equal([]byte(sig), []byte(expected))
}
import hmac
import hashlib
def verify_webhook(body: bytes, signature: str, secret: str) -> bool:
expected = "sha256=" + hmac.new(
secret.encode(),
body,
hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, signature)
import { createHmac, timingSafeEqual } from "crypto";
function verifyWebhook(body: Buffer, signature: string, secret: string): boolean {
const expected = "sha256=" + createHmac("sha256", secret)
.update(body)
.digest("hex");
return timingSafeEqual(Buffer.from(expected), Buffer.from(signature));
}

In the dashboard, go to API Keys → [key name] → Webhooks. The signing secret is shown there. It starts with nz_whsec_.

Always verify both the signature and the created_at timestamp in the event body. Reject events with created_at more than 5 minutes in the past to prevent replay attacks.