TUMR DOCS
DocsguidesHMAC Webhook Verification

# HMAC Webhook Verification Guide

To protect your application against spoofing and replay attacks, Tumr signs every outgoing webhook notification with a cryptographic HMAC-SHA256 digest using your endpoint's signing_secret.

Tumr transmits the signature in the X-Tumr-Signature HTTP header: ``http X-Tumr-Signature: sha256=d523674681f21132a87401c107297e...

Verification Algorithm 1. Extract the signature string from the X-Tumr-Signature header. 2. Read the raw, unparsed request body as bytes. 3. Compute the HMAC-SHA256 hash of the raw payload using your webhook secret. 4. Perform a constant-time string comparison between your calculated hash and the received signature.

---

Node.js / Express Implementation

import crypto from 'crypto';

const app = express();

// Important: Read raw buffer to prevent JSON re-serialization differences app.post('/webhooks/tumr', express.raw({ type: 'application/json' }), (req, res) => { const signature = req.headers['x-tumr-signature']; const secret = process.env.TUMR_WEBHOOK_SECRET;

const expectedSignature = 'sha256=' + crypto .createHmac('sha256', secret) .update(req.body) .digest('hex');

if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expectedSignature))) { console.warn('Invalid signature received!'); return res.status(401).send('Invalid signature'); }

const event = JSON.parse(req.body.toString()); console.log('Event received:', event.type, event.data);

// Return 200 OK immediately res.status(200).json({ received: true }); }); ```

---

Python / FastAPI Implementation

import hmac import hashlib

app = FastAPI() WEBHOOK_SECRET = "whsec_your_signing_secret_here"

@app.post("/webhooks/tumr") async def handle_tumr_webhook(request: Request): signature = request.headers.get("X-Tumr-Signature", "") raw_body = await request.body()

expected_signature = "sha256=" + hmac.new( key=WEBHOOK_SECRET.encode("utf-8"), msg=raw_body, digestmod=hashlib.sha256 ).hexdigest()

if not hmac.compare_digest(signature, expected_signature): raise HTTPException(status_code=401, detail="Invalid signature")

payload = await request.json() print("Received event:", payload["type"]) return {"status": "success"} ```