Command Palette

Search for a command to run...

Signature Verification

Every webhook delivery includes an HMAC-SHA256 signature. Verify it to ensure the request is authentic.

Signature Headers
These headers are included with every webhook delivery.
X-Xobni-Signaturesha256=<hex_digest>
X-Xobni-EventEvent name (e.g. email.received)
X-Xobni-DeliveryUnique delivery UUID
X-Xobni-TimestampUnix timestamp of delivery
How Signing Works

The signature is computed as:

HMAC-SHA256(secret, "{timestamp}.{json_body}")

Where secret is the signing secret returned when you created the webhook, timestamp is the value of the X-Xobni-Timestamp header, and json_body is the raw request body.

Python Verification
python
import hmac
import hashlib

def verify_webhook(secret: str, timestamp: str, body: bytes, signature: str) -> bool:
    """Verify a Xobni webhook signature."""
    expected = hmac.new(
        secret.encode(),
        f"{timestamp}.".encode() + body,
        hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(f"sha256={expected}", signature)

# In your webhook handler:
# secret = "your-webhook-secret"  (from webhook creation)
# timestamp = request.headers["X-Xobni-Timestamp"]
# body = await request.body()
# signature = request.headers["X-Xobni-Signature"]
# if not verify_webhook(secret, timestamp, body, signature):
#     return Response(status_code=401)
Node.js Verification
javascript
const crypto = require("crypto");

function verifyWebhook(secret, timestamp, body, signature) {
  const expected = crypto
    .createHmac("sha256", secret)
    .update(timestamp + "." + body)
    .digest("hex");
  return signature === "sha256=" + expected;
}

// In your Express handler:
// const secret = "your-webhook-secret";
// const timestamp = req.headers["x-xobni-timestamp"];
// const body = req.rawBody;  // use express.raw() middleware
// const signature = req.headers["x-xobni-signature"];
// if (!verifyWebhook(secret, timestamp, body, signature)) {
//   return res.status(401).send("Invalid signature");
// }