Audin Docs
Audin Mail

Webhook eventi

Il webhook outbound di Audin Mail — ricevere sugli endpoint dei tuoi sistemi gli eventi delle email inviate (consegne, aperture, click, bounce), verificare la firma HMAC, gestire retry e idempotenza, ruotare il signing secret.

Versione Markdown di questa pagina

Scarica o copia il contenuto di questa pagina in formato .md — utile per fornirlo a un agente AI che integra questa specifica feature.

Scarica .md

Audin Mail può inoltrare in tempo reale a un tuo endpoint HTTPS ogni evento delle email inviate dall'account: consegne, aperture, click, bounce, segnalazioni spam. Utile per CRM esterni, sistemi di BI o automazioni sui tuoi sistemi.

Si configura da Impostazioni → Audin Mail → "Webhook outbound" (l'integrazione deve essere attiva): inserisci l'URL, premi "Configura e genera secret" e salva subito il signing secret — è mostrato una sola volta. Dalla stessa card puoi attivare/disattivare il webhook, modificare l'URL, aggiungere header custom, inviare un evento di test e consultare le ultime consegne con payload e risposta del tuo endpoint.

Requisiti dell'endpoint

  • HTTPS obbligatorio, raggiungibile da internet pubblico.
  • Accetta POST con corpo application/json.
  • Risponde con un 2xx entro 10 secondi (elabora in asincrono se serve).

Cosa ricevi

Gli eventi arrivano in batch: una richiesta può contenere più eventi.

{
  "audin_event_id": "evt_…",
  "audin_account_id": "…",
  "received_at": "2026-08-17T10:15:00.000Z",
  "version": "1",
  "events": [
    {
      "audin_message_id": "8b1f…",
      "sendgrid": {
        "event": "delivered",
        "email": "cliente@esempio.it",
        "timestamp": 1755425700
      }
    }
  ]
}
  • audin_message_id è la chiave di correlazione con l'email inviata da Audin (può essere null per eventi non riconducibili a un invio, es. l'evento di test).
  • sendgrid è il payload originale del fornitore di recapito, inoltrato così com'è: il nome della chiave fa parte del contratto e non cambia. I campi utili sono event (tipo evento), email, timestamp, ed eventuali reason (bounce), url (click).
  • Tipi di evento inoltrati: processed, delivered, open, click, bounce, dropped, deferred, spamreport.
  • Un bump di version (oggi "1") segnalerà eventuali cambi di formato incompatibili.

Header di ogni richiesta

HeaderContenuto
User-AgentAudin-Mail-Webhook/1.0
X-Audin-Webhook-IdIdentificativo fisso del webhook del tuo account
X-Audin-Delivery-IdIdentificativo della singola consegna — usalo come chiave di idempotenza (i retry lo ripetono)
X-Audin-Signature-TimestampUnix timestamp (secondi) usato nella firma
X-Audin-Signaturev1=<hex> — firma HMAC-SHA256 (vedi sotto)

Gli eventuali header custom configurati (max 5) vengono aggiunti a ogni richiesta.

Verifica della firma (obbligatoria)

Ogni richiesta è firmata: verifica sempre la firma prima di fidarti del contenuto.

  1. signed_payload = timestamp + "." + corpo_raw_della_richiesta
  2. atteso = HMAC_SHA256(secret, signed_payload) in esadecimale
  3. confronta (in constant-time) con il valore dopo v1= nell'header
  4. anti-replay: rifiuta richieste con timestamp più vecchio di 5 minuti

La firma va calcolata sul corpo raw della richiesta, non sul JSON ri-serializzato: qualsiasi middleware che fa il parse del body prima della verifica (es. express.json()) la invalida.

const crypto = require("crypto");
const express = require("express");
const app = express();

app.post("/audin/webhook",
  express.raw({ type: "application/json" }), // body RAW, non json()
  (req, res) => {
    const ts = req.header("X-Audin-Signature-Timestamp");
    const sig = req.header("X-Audin-Signature"); // "v1=<hex>"

    if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) {
      return res.status(400).send("timestamp too old");
    }

    const expected = "v1=" + crypto
      .createHmac("sha256", process.env.AUDIN_WEBHOOK_SECRET)
      .update(`${ts}.${req.body.toString("utf8")}`)
      .digest("hex");

    const ok = sig && crypto.timingSafeEqual(
      Buffer.from(sig), Buffer.from(expected));
    if (!ok) return res.status(401).send("bad signature");

    const payload = JSON.parse(req.body.toString("utf8"));
    // elabora payload.events in asincrono, poi:
    res.sendStatus(200);
  });
import hashlib, hmac, os, time
from flask import Flask, request, abort

app = Flask(__name__)

@app.post("/audin/webhook")
def audin_webhook():
    ts = request.headers.get("X-Audin-Signature-Timestamp", "")
    sig = request.headers.get("X-Audin-Signature", "")
    raw = request.get_data()  # body RAW, non request.json

    if abs(time.time() - int(ts or 0)) > 300:
        abort(400)

    expected = "v1=" + hmac.new(
        os.environ["AUDIN_WEBHOOK_SECRET"].encode(),
        f"{ts}.".encode() + raw,
        hashlib.sha256,
    ).hexdigest()

    if not hmac.compare_digest(sig, expected):
        abort(401)

    # elabora in asincrono, poi:
    return "", 200

Risposta attesa e retry

  • Qualunque 2xx = consegna riuscita.
  • 410 Gone = "non inviarmi più questo": la consegna viene marcata fallita senza retry.
  • Ogni altro errore (4xx, 5xx, timeout oltre 10s, DNS irraggiungibile) attiva i retry: 6 tentativi in tutto — il primo immediato, poi dopo 1 minuto, 5 minuti, 30 minuti, 2 ore e 12 ore (circa 15 ore complessive) — dopodiché la consegna è marcata Fallita.
  • I retry ripetono lo stesso X-Audin-Delivery-Id: rendi l'elaborazione idempotente su quella chiave.
  • Nella card "Ultime consegne" vedi ogni tentativo con stato HTTP, durata e corpo della risposta del tuo endpoint (troncato).

Rotazione del signing secret

"Rigenera" genera un nuovo secret, attivo immediatamente: Audin firma da subito solo col nuovo. Il precedente resta consultabile per 24 ore a fini di supporto. Procedura senza downtime: predisponi la verifica a doppio secret (o preparati ad aggiornare l'env), clicca "Rigenera", salva il nuovo secret (mostrato una volta sola) e aggiornalo nei tuoi sistemi.

Test in sviluppo

Per sviluppare in locale esponi l'endpoint con un tunnel (ngrok, Cloudflare Tunnel) o ispeziona i payload con un servizio tipo webhook.site. Il pulsante "Invia evento di test" fa una POST sincrona (evento event: "test", audin_message_id: null) e mostra subito status, durata e risposta — senza retry.

Checklist di sicurezza

  • endpoint solo HTTPS, secret in un secret manager (mai nel codice);
  • verifica HMAC in constant-time su ogni richiesta + anti-replay 5 min;
  • risposta 2xx entro 10 secondi, elaborazione asincrona;
  • idempotenza su X-Audin-Delivery-Id;
  • gli header custom non sostituiscono la verifica della firma.

Prossimi passi

On this page