Passa al contenuto principale

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

  1. Vai a Configurazione > Webhooks nel tuo account Frihet
  2. Premi Crea webhook
  3. Introduci un nome descrittivo, l'URL di destinazione e seleziona gli eventi che vuoi ricevere
  4. Opzionalmente, definisci un segreto per la verifica HMAC (raccomandato)
  5. 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
CampoTipoObbligatorioDescrizione
urlstringURL di destinazione (HTTPS obbligatorio in produzione)
eventsstring[]Lista dei tipi di evento da ricevere
secretstringNoSegreto per la firma HMAC-SHA256 (raccomandato)
activebooleanNoStato 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 localhost e 127.0.0.1 durante 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)

EventoDescrizione
invoice.createdÈ stata creata una nuova fattura
invoice.updatedÈ stata modificata una fattura esistente
invoice.paidUna fattura è stata contrassegnata come pagata
invoice.overdueUna fattura ha superato la sua data di scadenza

Spese (2 eventi)

EventoDescrizione
expense.createdÈ stata registrata una nuova spesa
expense.updatedÈ stata modificata una spesa esistente

Preventivi (4 eventi)

EventoDescrizione
quote.createdÈ stato creato un nuovo preventivo
quote.updatedÈ stato modificato un preventivo esistente
quote.acceptedUn cliente ha accettato un preventivo (cambio di stato a accepted)
quote.rejectedUn cliente ha rifiutato un preventivo (cambio di stato a rejected)

Clienti (2 eventi)

EventoDescrizione
client.createdÈ stato registrato un nuovo cliente
client.updatedSono stati modificati i dati di un cliente

Prodotti (2 eventi)

EventoDescrizione
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:

IntestazioneDescrizioneEsempio
Content-TypeTipo di contenutoapplication/json
X-Frihet-EventTipo di eventoinvoice.paid
X-Frihet-Delivery-IdIdentificatore unico della consegnad4e5f6a7b8c9
X-Frihet-TimestampTimestamp ISO 86012026-02-12T14:30:00.000Z
X-Frihet-SignatureFirma HMAC-SHA256 del payloadsha256=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:

  1. Prendere il corpo della richiesta in formato raw (raw body)
  2. Calcolare l'HMAC-SHA256 usando il segreto come chiave
  3. Confrontare il risultato con il valore dell'intestazione X-Frihet-Signature (senza il prefisso sha256=)

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:

TentativoRitardoTempo accumulato
1 (iniziale)immediato0s
2 (primo tentativo)2 secondi2s
3 (secondo tentativo)4 secondi6s
  • 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:

  1. Vai a Impostazioni > Sviluppatori > Webhooks
  2. Premi sul webhook che vuoi ispezionare
  3. 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, retrying o failed

Problemi comuni

SintomoCausa probabileSoluzione
Tutte le consegne falliscono con timeoutL'endpoint impiega più di 30s a rispondereAccetta l'evento con un 200 immediato ed elabora in modo asincrono
Errore SSL/TLSCertificato scaduto o catena incompletaRinnova il certificato e verifica la catena con openssl s_client
Codice 403 sistematicoFirewall che blocca le richieste POST in entrataPermetti il traffico dagli IP di Google Cloud (us-central1)
Codice 502/503Server inattivo o in manutenzioneControlla 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

  1. Verifica che l'URL del webhook sia corretto e accessibile da internet
  2. Controlla che il webhook sia nello stato attivo nel pannello
  3. Controlla i log di consegna per vedere se ci sono errori
  4. Se usi un firewall, assicurati che le richieste POST in entrata da Google Cloud (us-central1) siano permesse

Le firme non corrispondono

  1. Verifica che stai usando il raw body della richiesta (prima di fare il parsing del JSON)
  2. Conferma che il segreto nel tuo codice corrisponda a quello configurato nel pannello di Frihet
  3. Non modificare né riformattare il body prima di verificare la firma
  4. Controlla che il tuo framework non stia automaticamente facendo il parsing del body prima che tu possa accedere al raw

I ritentativi non arrivano

  1. I ritentativi vengono elaborati ogni 5 minuti. Se il tuo endpoint è stato inattivo brevemente, è possibile che i tentativi siano già esauriti
  2. Controlla la cronologia delle consegne per confermare lo stato di ogni tentativo
  3. 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.