← API docs

Webhook signature verification

Ogni POST webhook è firmato con HMAC SHA-256 del body raw usando il secret che hai visto alla creazione del webhook (prefix whk_…).

Algoritmo: sha256 = HMAC_SHA256(secret, raw_body). L'header X-Syrus-Signature-256 ha formato sha256=<hex_lowercase>. Confronta SEMPRE con constant-time comparison (evita timing attacks).

Test vector

Usa questi valori per verificare la tua implementazione offline:

secret   = "whk_test_secret_abc123"
body     = b'{"event":"lead_qualified","test":true}'
expected = "sha256=a91b91ca1b39a9f7f09e8b0e4fcb5da36b6cf30c0c1c4af6ae7f5c6f4a28f65a"

Python (Flask handler)

import hashlib, hmac, time
from flask import Flask, request, abort, jsonify

app = Flask(__name__)
SECRET = b"whk_...la-tua-chiave..."

@app.post("/syrus/webhook")
def syrus_webhook():
    sig_header = request.headers.get("X-Syrus-Signature-256", "")
    ts = request.headers.get("X-Syrus-Timestamp", "")
    body = request.get_data()  # raw bytes, NON decoded

    # Replay protection
    if not ts.isdigit() or abs(time.time() - int(ts)) > 300:
        abort(400, "timestamp too old")

    expected = "sha256=" + hmac.new(SECRET, body, hashlib.sha256).hexdigest()
    if not hmac.compare_digest(sig_header, expected):
        abort(401, "invalid signature")

    event = request.headers.get("X-Syrus-Event")
    payload = request.get_json()
    # … elabora lead
    return jsonify({"ok": True})

Node.js (Express middleware)

const crypto = require("crypto");
const express = require("express");
const app = express();
const SECRET = process.env.SYRUS_WEBHOOK_SECRET;

app.post("/syrus/webhook",
  express.raw({ type: "application/json" }),  // MUST use raw body
  (req, res) => {
    const ts = req.header("X-Syrus-Timestamp") || "";
    if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) {
      return res.status(400).send("timestamp too old");
    }
    const expected = "sha256=" + crypto
      .createHmac("sha256", SECRET)
      .update(req.body)
      .digest("hex");
    const received = req.header("X-Syrus-Signature-256") || "";
    const valid = received.length === expected.length &&
      crypto.timingSafeEqual(Buffer.from(received), Buffer.from(expected));
    if (!valid) return res.status(401).send("invalid signature");
    const payload = JSON.parse(req.body.toString("utf-8"));
    // … elabora lead
    res.json({ ok: true });
  });

PHP (plain)

<?php
$secret = getenv('SYRUS_WEBHOOK_SECRET');
$body = file_get_contents('php://input');
$ts = $_SERVER['HTTP_X_SYRUS_TIMESTAMP'] ?? '';
if (!ctype_digit($ts) || abs(time() - (int)$ts) > 300) {
    http_response_code(400); exit('timestamp too old');
}
$expected = 'sha256=' . hash_hmac('sha256', $body, $secret);
$received = $_SERVER['HTTP_X_SYRUS_SIGNATURE_256'] ?? '';
if (!hash_equals($expected, $received)) {
    http_response_code(401); exit('invalid signature');
}
$payload = json_decode($body, true);
// … elabora lead
echo json_encode(['ok' => true]);

Best practices