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-Signature — sha256=<hex_digest>X-Xobni-Event — Event name (e.g. email.received)X-Xobni-Delivery — Unique delivery UUIDX-Xobni-Timestamp — Unix timestamp of deliveryHow 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");
// }