Signature verification
Every delivery includes a Nativpost-Signature header. Verify it before acting on the payload. Requests that fail verification should be discarded, not merely logged.
Header format
Section titled “Header format”Nativpost-Signature: t=1747663200,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bdtis the Unix timestamp (seconds) when we signed the request.v1is a hex-encoded HMAC-SHA256 signature.
Multiple v1= pairs may be present during a key rotation. Accept the delivery if any of them verifies.
Signed payload
Section titled “Signed payload”The value fed into the HMAC is:
<timestamp>.<raw_request_body>Use the raw bytes of the body exactly as they arrived. Do not decode the JSON and re-serialise. Extra whitespace changes the signature.
Verifying (Node.js)
Section titled “Verifying (Node.js)”import crypto from "node:crypto";
export function verify(rawBody, signatureHeader, secret) { const parts = Object.fromEntries( signatureHeader.split(",").map((p) => p.split("=", 2)), ); const timestamp = Number(parts.t); const signature = parts.v1;
const skewSeconds = Math.abs(Date.now() / 1000 - timestamp); if (skewSeconds > 300) throw new Error("timestamp outside tolerance");
const expected = crypto .createHmac("sha256", secret) .update(`${timestamp}.${rawBody}`) .digest("hex");
const ok = crypto.timingSafeEqual( Buffer.from(expected, "hex"), Buffer.from(signature, "hex"), ); if (!ok) throw new Error("bad signature");}Verifying (Python)
Section titled “Verifying (Python)”import hmac, hashlib, time
def verify(raw_body: bytes, signature_header: str, secret: str) -> None: parts = dict(p.split("=", 1) for p in signature_header.split(",")) timestamp = int(parts["t"]) signature = parts["v1"]
if abs(time.time() - timestamp) > 300: raise ValueError("timestamp outside tolerance")
expected = hmac.new( secret.encode(), f"{timestamp}.".encode() + raw_body, hashlib.sha256, ).hexdigest()
if not hmac.compare_digest(expected, signature): raise ValueError("bad signature")Timestamp tolerance
Section titled “Timestamp tolerance”The recommended tolerance is 5 minutes (300 seconds). This absorbs clock skew while stopping simple replay attacks. Tighten to 60 seconds for higher-stakes integrations.
Rotating the secret
Section titled “Rotating the secret”Call POST /webhooks/{id}/rotate to generate a new secret. Redeploy your verifier with the new value. Old signatures are rejected immediately after rotation, so cut over during a low-traffic window.