Webhook Security
Signature Verification
Section titled “Signature Verification”Every webhook delivery is signed so you can verify it came from TokenBot. Each request includes these headers:
| Header | Description |
|---|---|
X-TokenBot-Signature | sha256=<hex_digest> HMAC of the payload |
X-TokenBot-Timestamp | Unix seconds when the signature was created |
X-TokenBot-Event | The event type (e.g. trade.executed) |
X-TokenBot-Delivery-Id | Unique delivery ID (use for idempotency) |
How It Works
Section titled “How It Works”- TokenBot builds the string
${timestamp}.${raw_body}. - It computes
HMAC-SHA256(secret, that_string)and sends it asX-TokenBot-Signature: sha256=<hex>, with the timestamp inX-TokenBot-Timestamp. - You recompute the same HMAC and compare in constant time.
- Reject deliveries whose timestamp is older than your tolerance (TokenBot uses a 5-minute replay window).
Node.js Example
Section titled “Node.js Example”const crypto = require('crypto');
function verifyWebhook(rawBody, signature, timestamp, secret, toleranceSeconds = 300) { // Reject stale deliveries const now = Math.floor(Date.now() / 1000); if (Math.abs(now - Number(timestamp)) > toleranceSeconds) return false;
const expected = 'sha256=' + crypto .createHmac('sha256', secret) .update(`${timestamp}.${rawBody}`, 'utf8') .digest('hex');
return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected));}
// Express middleware (note: raw body is required)app.post('/webhooks', express.raw({ type: 'application/json' }), (req, res) => { const ok = verifyWebhook( req.body, req.headers['x-tokenbot-signature'], req.headers['x-tokenbot-timestamp'], process.env.WEBHOOK_SECRET ); if (!ok) return res.status(401).send('Invalid signature');
const event = JSON.parse(req.body); // Process event... res.status(200).send('OK');});Python Example
Section titled “Python Example”import hmac, hashlib, time
def verify_webhook(raw_body: bytes, signature: str, timestamp: str, secret: str, tolerance=300) -> bool: if abs(int(time.time()) - int(timestamp)) > tolerance: return False signed = f"{timestamp}.".encode() + raw_body expected = 'sha256=' + hmac.new(secret.encode(), signed, hashlib.sha256).hexdigest() return hmac.compare_digest(signature, expected)Secrets
Section titled “Secrets”Webhook signing secrets are prefixed whsec_ and are shown only once, when the webhook is created. Rotate a secret with POST /v1/webhooks/:id/rotate-secret (the previous secret keeps working for a short grace window).
Best Practices
Section titled “Best Practices”- Always verify signatures and the timestamp before processing events.
- Use
crypto.timingSafeEqual(Node.js) orhmac.compare_digest(Python) to prevent timing attacks. - Deduplicate using
X-TokenBot-Delivery-Id(or the payload’sevent_id). - Respond with
2xxquickly; process events asynchronously.

