Webhooks
I webhook di Frihet consentono di ricevere notifiche HTTP in tempo reale quando si verificano eventi nel tuo account. Invece di effettuare il polling all'API, configura un URL e Frihet invierà un POST con i dati dell'evento nel momento in cui si produce.
Configurazione
Puoi gestire i webhook dal pannello di Frihet (Configurazione > Webhooks) o in modo programmatico tramite l'API REST.
Dal pannello
- Vai a Configurazione > Webhooks nel tuo account Frihet
- Premi Crea webhook
- Introduci un nome descrittivo, l'URL di destinazione e seleziona gli eventi che vuoi ricevere
- Opzionalmente, definisci un segreto per la verifica HMAC (raccomandato)
- Salva il webhook
API REST dei webhook
Gestisci i webhook in modo programmatico con gli endpoint CRUD completi.
Elencare i webhook
GET /v1/webhooks
curl https://api.frihet.io/v1/webhooks \
-H "X-API-Key: fri_tu-clave-aqui"
Risposta (200):
{
"data": [
{
"id": "wh_abc123",
"url": "https://mi-app.com/hook",
"events": ["invoice.paid", "invoice.created"],
"active": true,
"createdAt": "2026-02-01T10:00:00.000Z"
}
],
"total": 1,
"limit": 50,
"offset": 0
}
Ottenere un webhook
GET /v1/webhooks/:id
curl https://api.frihet.io/v1/webhooks/wh_abc123 \
-H "X-API-Key: fri_tu-clave-aqui"
Creare un webhook
POST /v1/webhooks
| Campo | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|
url | string | Sì | URL di destinazione (HTTPS obbligatorio in produzione) |
events | string[] | Sì | Lista dei tipi di evento da ricevere |
secret | string | No | Segreto per la firma HMAC-SHA256 (raccomandato) |
active | boolean | No | Stato del webhook (default: true) |
curl -X POST https://api.frihet.io/v1/webhooks \
-H "X-API-Key: fri_tu-clave-aqui" \
-H "Content-Type: application/json" \
-d '{
"url": "https://mi-app.com/hook",
"events": ["invoice.paid", "expense.created"],
"secret": "mi-secreto-hmac"
}'
Risposta (201):
{
"id": "wh_def456",
"url": "https://mi-app.com/hook",
"events": ["invoice.paid", "expense.created"],
"active": true,
"createdAt": "2026-03-18T10:00:00.000Z"
}
Aggiornare un webhook
PATCH /v1/webhooks/:id
Devi inviare solo i campi che desideri modificare.
curl -X PATCH https://api.frihet.io/v1/webhooks/wh_def456 \
-H "X-API-Key: fri_tu-clave-aqui" \
-H "Content-Type: application/json" \
-d '{
"events": ["invoice.paid", "invoice.created", "expense.created"],
"active": false
}'
Risposta (200): Oggetto webhook aggiornato.
Eliminare un webhook
DELETE /v1/webhooks/:id
curl -X DELETE https://api.frihet.io/v1/webhooks/wh_def456 \
-H "X-API-Key: fri_tu-clave-aqui"
Risposta: 204 No Content
Requisiti dell'URL:
- Deve usare HTTPS (è permesso HTTP solo per
localhoste127.0.0.1durante lo sviluppo) - Non sono ammessi IP privati (10.x, 172.16-31.x, 192.168.x) per evitare attacchi SSRF
- Deve rispondere con un codice 2xx in meno di 30 secondi
Limiti:
- Massimo 20 webhook per account
- Payload massimo di 100 KB per consegna
Tipi di evento
Frihet emette 14 tipi di evento, raggruppati per risorsa.
Fatture (4 eventi)
| Evento | Descrizione |
|---|---|
invoice.created | È stata creata una nuova fattura |
invoice.updated | È stata modificata una fattura esistente |
invoice.paid | Una fattura è stata contrassegnata come pagata |
invoice.overdue | Una fattura ha superato la sua data di scadenza |
Spese (2 eventi)
| Evento | Descrizione |
|---|---|
expense.created | È stata registrata una nuova spesa |
expense.updated | È stata modificata una spesa esistente |
Preventivi (4 eventi)
| Evento | Descrizione |
|---|---|
quote.created | È stato creato un nuovo preventivo |
quote.updated | È stato modificato un preventivo esistente |
quote.accepted | Un cliente ha accettato un preventivo (cambio di stato a accepted) |
quote.rejected | Un cliente ha rifiutato un preventivo (cambio di stato a rejected) |
Clienti (2 eventi)
| Evento | Descrizione |
|---|---|
client.created | È stato registrato un nuovo cliente |
client.updated | Sono stati modificati i dati di un cliente |
Prodotti (2 eventi)
| Evento | Descrizione |
|---|---|
product.created | È stato creato un nuovo prodotto o servizio |
product.updated | È stato modificato un prodotto o servizio esistente |
Struttura del payload
Ogni consegna di webhook è una richiesta POST con corpo JSON. La struttura è la stessa per tutti i tipi di evento:
{
"event": "invoice.paid",
"timestamp": "2026-02-12T14:30:00.000Z",
"data": {
"id": "inv_abc123",
"clientName": "Acme S.L.",
"items": [
{ "description": "Consultoria", "quantity": 10, "unitPrice": 75 }
],
"total": 750,
"status": "paid",
"paidAt": "2026-02-12T14:29:58.000Z",
"createdAt": "2026-01-15T10:30:00.000Z",
"updatedAt": "2026-02-12T14:30:00.000Z"
}
}
Il campo data contiene lo stato completo della risorsa al momento dell'evento.
Intestazioni di consegna
Ogni richiesta di webhook include le seguenti intestazioni:
| Intestazione | Descrizione | Esempio |
|---|---|---|
Content-Type | Tipo di contenuto | application/json |
X-Frihet-Event | Tipo di evento | invoice.paid |
X-Frihet-Delivery-Id | Identificatore unico della consegna | d4e5f6a7b8c9 |
X-Frihet-Timestamp | Timestamp ISO 8601 | 2026-02-12T14:30:00.000Z |
X-Frihet-Signature | Firma HMAC-SHA256 del payload | sha256=a1b2c3d4e5f6... |
L'intestazione X-Frihet-Signature è inclusa solo se hai configurato un segreto nel webhook.
Verifica della firma
Se configuri un segreto al momento della creazione del webhook, Frihet firma ogni payload con HMAC-SHA256. Il processo di verifica consiste in:
- Prendere il corpo della richiesta in formato raw (raw body)
- Calcolare l'HMAC-SHA256 usando il segreto come chiave
- Confrontare il risultato con il valore dell'intestazione
X-Frihet-Signature(senza il prefissosha256=)
Con l'SDK (raccomandato)
import { Webhooks } from '@frihet/sdk';
app.post('/webhook/frihet', express.raw({ type: 'application/json' }), (req, res) => {
const isValid = Webhooks.verifySignature(
req.body,
req.headers['x-frihet-signature'],
process.env.FRIHET_WEBHOOK_SECRET,
);
if (!isValid) return res.status(401).send('Firma invalida');
const event = JSON.parse(req.body.toString());
console.log(req.headers['x-frihet-event'], event.data);
res.sendStatus(200);
});
Esempio manuale in Node.js
const crypto = require('crypto');
function verifyWebhookSignature(rawBody, signature, secret) {
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(rawBody)
.digest('hex');
const receivedSignature = signature.replace('sha256=', '');
return crypto.timingSafeEqual(
Buffer.from(expectedSignature, 'hex'),
Buffer.from(receivedSignature, 'hex')
);
}
// Uso in un server Express
app.post('/webhook/frihet', express.raw({ type: 'application/json' }), (req, res) => {
const signature = req.headers['x-frihet-signature'];
const secret = process.env.FRIHET_WEBHOOK_SECRET;
if (!signature || !verifyWebhookSignature(req.body.toString(), signature, secret)) {
return res.status(401).send('Firma invalida');
}
const event = JSON.parse(req.body.toString());
const eventType = req.headers['x-frihet-event'];
console.log(`Evento recibido: ${eventType}`, event.data);
// Elaborare l'evento in base al suo tipo
switch (eventType) {
case 'invoice.paid':
// Aggiornare il tuo sistema di contabilità
break;
case 'expense.created':
// Notificare al team finanziario
break;
}
res.status(200).send('OK');
});
Esempio in Python
import hmac
import hashlib
from flask import Flask, request, abort
app = Flask(__name__)
WEBHOOK_SECRET = 'tu-secreto-aqui'
def verify_signature(payload: bytes, signature: str, secret: str) -> bool:
expected = hmac.new(
secret.encode('utf-8'),
payload,
hashlib.sha256
).hexdigest()
received = signature.replace('sha256=', '')
return hmac.compare_digest(expected, received)
@app.route('/webhook/frihet', methods=['POST'])
def handle_webhook():
signature = request.headers.get('X-Frihet-Signature', '')
if not verify_signature(request.data, signature, WEBHOOK_SECRET):
abort(401)
event = request.get_json()
event_type = request.headers.get('X-Frihet-Event')
print(f'Evento recibido: {event_type}')
if event_type == 'invoice.paid':
# Aggiornare il tuo sistema di contabilità
pass
elif event_type == 'quote.accepted':
# Convertire il preventivo in fattura
pass
return 'OK', 200
Importante: Usa sempre un confronto a tempo costante (timingSafeEqual in Node.js, compare_digest in Python) per evitare attacchi di timing.
Politica di ritentativi
Se la consegna di un webhook fallisce (codice di risposta non-2xx o timeout), Frihet riprova automaticamente con backoff esponenziale:
| Tentativo | Ritardo | Tempo accumulato |
|---|---|---|
| 1 (iniziale) | immediato | 0s |
| 2 (primo tentativo) | 2 secondi | 2s |
| 3 (secondo tentativo) | 4 secondi | 6s |
- Massimo 3 tentativi per consegna (1 iniziale + 2 ritentativi)
- Ritardo massimo di 30 secondi tra i ritentativi
- Timeout di 30 secondi per richiesta
- Se i 3 tentativi falliscono, la consegna viene contrassegnata come
failed
I ritentativi vengono elaborati tramite un job che viene eseguito ogni 5 minuti, garantendo l'affidabilità anche se il processo principale si riavvia.
Puoi consultare la cronologia delle consegne di ogni webhook dal pannello di Frihet, inclusi il codice di risposta, il corpo della risposta (primi 1000 caratteri) e gli errori di ogni tentativo.
Test
Dal pannello di Frihet puoi inviare un evento di test a qualsiasi webhook configurato. Il payload di test ha la seguente struttura:
{
"event": "webhook.test",
"timestamp": "2026-02-12T14:30:00.000Z",
"data": {
"message": "This is a test webhook from Frihet ERP"
}
}
Ciò permette di verificare che il tuo endpoint sia accessibile, che la firma sia convalidata correttamente e che il tuo sistema elabori gli eventi senza errori.
Buone pratiche
Rispondi rapidamente
Il tuo endpoint deve rispondere con un codice 200 il prima possibile. Se devi effettuare un'elaborazione pesante (inviare email, aggiornare database esterni, ecc.), accetta l'evento ed elaboralo in modo asincrono in una coda di lavoro.
Gestisci l'idempotenza
È possibile che lo stesso evento venga consegnato più di una volta (ad esempio, se il tuo server ha risposto con un timeout ma ha elaborato l'evento). Usa il campo X-Frihet-Delivery-Id come chiave di idempotenza per evitare duplicati.
Verifica sempre la firma
Non fidarti mai di un webhook senza verificare l'intestazione X-Frihet-Signature. Qualsiasi attore con accesso al tuo URL potrebbe inviare payload falsi.
Usa HTTPS
In produzione, il tuo endpoint deve essere protetto con HTTPS. Frihet rifiuta gli URL HTTP (eccetto nello sviluppo locale).
Monitora i fallimenti
Controlla periodicamente i log di consegna nel pannello di Frihet. Se vedi consegne fallite in modo ricorrente, verifica che il tuo endpoint sia disponibile e risponda in meno di 30 secondi.
Filtra gli eventi
Iscriviti solo agli eventi di cui hai bisogno. Ogni webhook può ascoltare uno o più tipi di evento. Meno eventi inutili elabori, minore sarà il carico sul tuo server.
Debugging delle consegne
Frihet registra il risultato di ogni consegna di webhook. Puoi consultarlo dal pannello:
- Vai a Impostazioni > Sviluppatori > Webhooks
- Premi sul webhook che vuoi ispezionare
- Apri la scheda Consegne
Ogni voce mostra:
- Codice di risposta HTTP restituito dal tuo endpoint
- Tempo di risposta in millisecondi
- Corpo della risposta (primi 1000 caratteri)
- Data e ora di ogni tentativo (inclusi i ritentativi)
- Stato finale:
delivered,retryingofailed
Problemi comuni
| Sintomo | Causa probabile | Soluzione |
|---|---|---|
| Tutte le consegne falliscono con timeout | L'endpoint impiega più di 30s a rispondere | Accetta l'evento con un 200 immediato ed elabora in modo asincrono |
| Errore SSL/TLS | Certificato scaduto o catena incompleta | Rinnova il certificato e verifica la catena con openssl s_client |
| Codice 403 sistematico | Firewall che blocca le richieste POST in entrata | Permetti il traffico dagli IP di Google Cloud (us-central1) |
| Codice 502/503 | Server inattivo o in manutenzione | Controlla i log del tuo server e verifica che il processo sia attivo |
Pulsante di test
Nella schermata dei dettagli di ogni webhook, il pulsante Invia test invia un evento sintetico webhook.test al tuo URL. Usa questo pulsante per verificare:
- Che l'URL sia accessibile da internet
- Che la firma HMAC sia convalidata correttamente
- Che il tuo server risponda con un codice
2xx
Il risultato del test appare immediatamente nel log delle consegne.
Risoluzione dei problemi
Non ricevo i webhook
- Verifica che l'URL del webhook sia corretto e accessibile da internet
- Controlla che il webhook sia nello stato attivo nel pannello
- Controlla i log di consegna per vedere se ci sono errori
- Se usi un firewall, assicurati che le richieste POST in entrata da Google Cloud (
us-central1) siano permesse
Le firme non corrispondono
- Verifica che stai usando il raw body della richiesta (prima di fare il parsing del JSON)
- Conferma che il segreto nel tuo codice corrisponda a quello configurato nel pannello di Frihet
- Non modificare né riformattare il body prima di verificare la firma
- Controlla che il tuo framework non stia automaticamente facendo il parsing del body prima che tu possa accedere al raw
I ritentativi non arrivano
- I ritentativi vengono elaborati ogni 5 minuti. Se il tuo endpoint è stato inattivo brevemente, è possibile che i tentativi siano già esauriti
- Controlla la cronologia delle consegne per confermare lo stato di ogni tentativo
- Se tutti e 3 i tentativi sono falliti, la consegna non verrà ritentata automaticamente
Payload troppo grande
Se la risorsa associata all'evento è molto grande (molte righe in una fattura, campi estesi), il payload può superare il limite di 100 KB e la consegna verrà rifiutata. Semplifica i dati della risorsa o contatta il supporto se hai bisogno di un limite superiore.