Webhooks
Frihets webhooks gör det möjligt att ta emot HTTP-meddelanden i realtid när händelser inträffar på ditt konto. Istället för att polla API:et, konfigurerar du en URL och Frihet skickar ett POST med händelsedata när händelsen inträffar.
Konfiguration
Du kan hantera webhooks från Frihets kontrollpanel (Konfiguration > Webhooks) eller programmatiskt via REST API:et.
Från kontrollpanelen
- Gå till Konfiguration > Webhooks på ditt Frihet-konto
- Klicka på Skapa webhook
- Ange ett beskrivande namn, destinations-URL och välj de händelser du vill ta emot
- Alternativt, definiera en hemlighet för HMAC-verifiering (rekommenderas)
- Spara webhooken
Webhooks REST API
Hantera webhooks programmatiskt med de fullständiga CRUD-endpoints.
Lista webhooks
GET /v1/webhooks
curl https://api.frihet.io/v1/webhooks \
-H "X-API-Key: fri_tu-clave-aqui"
Svar (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
}
Hämta webhook
GET /v1/webhooks/:id
curl https://api.frihet.io/v1/webhooks/wh_abc123 \
-H "X-API-Key: fri_tu-clave-aqui"
Skapa webhook
POST /v1/webhooks
| Fält | Typ | Obligatoriskt | Beskrivning |
|---|---|---|---|
url | string | Ja | Destinations-URL (HTTPS obligatoriskt i produktion) |
events | string[] | Ja | Lista över händelsetyper att ta emot |
secret | string | Nej | Hemlighet för HMAC-SHA256-signering (rekommenderas) |
active | boolean | Nej | Webhookens status (standard: 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"
}'
Svar (201):
{
"id": "wh_def456",
"url": "https://mi-app.com/hook",
"events": ["invoice.paid", "expense.created"],
"active": true,
"createdAt": "2026-03-18T10:00:00.000Z"
}
Uppdatera webhook
PATCH /v1/webhooks/:id
Du behöver bara skicka de fält du vill ändra.
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
}'
Svar (200): Uppdaterat webhook-objekt.
Radera webhook
DELETE /v1/webhooks/:id
curl -X DELETE https://api.frihet.io/v1/webhooks/wh_def456 \
-H "X-API-Key: fri_tu-clave-aqui"
Svar: 204 No Content
URL-krav:
- Måste använda HTTPS (HTTP tillåts endast för
localhostoch127.0.0.1under utveckling) - Privata IP:er (10.x, 172.16-31.x, 192.168.x) är inte tillåtna för att undvika SSRF-attacker
- Måste svara med en 2xx-kod på mindre än 30 sekunder
Gränser:
- Maximalt 20 webhooks per konto
- Maximal payload på 100 KB per leverans
Händelsetyper
Frihet skickar ut 14 händelsetyper, grupperade per resurs.
Fakturor (4 händelser)
| Händelse | Beskrivning |
|---|---|
invoice.created | En ny faktura har skapats |
invoice.updated | En befintlig faktura har ändrats |
invoice.paid | En faktura har markerats som betald |
invoice.overdue | En faktura har överskridit sitt förfallodatum |
Utgifter (2 händelser)
| Händelse | Beskrivning |
|---|---|
expense.created | En ny utgift har registrerats |
expense.updated | En befintlig utgift har ändrats |
Offerter (4 händelser)
| Händelse | Beskrivning |
|---|---|
quote.created | En ny offert har skapats |
quote.updated | En befintlig offert har ändrats |
quote.accepted | En kund har accepterat en offert (statusändring till accepted) |
quote.rejected | En kund har avvisat en offert (statusändring till rejected) |
Kunder (2 händelser)
| Händelse | Beskrivning |
|---|---|
client.created | En ny kund har registrerats |
client.updated | Kunduppgifter har ändrats |
Produkter (2 händelser)
| Händelse | Beskrivning |
|---|---|
product.created | En ny produkt eller tjänst har skapats |
product.updated | En befintlig produkt eller tjänst har ändrats |
Payload-struktur
Varje webhook-leverans är en POST-förfrågan med JSON-kropp. Strukturen är densamma för alla händelsetyper:
{
"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"
}
}
Fältet data innehåller resursens fullständiga tillstånd vid händelsetidpunkten.
Leveransrubriker
Varje webhook-förfrågan inkluderar följande rubriker:
| Rubrik | Beskrivning | Exempel |
|---|---|---|
Content-Type | Innehållstyp | application/json |
X-Frihet-Event | Händelsetyp | invoice.paid |
X-Frihet-Delivery-Id | Unik leveransidentifierare | d4e5f6a7b8c9 |
X-Frihet-Timestamp | ISO 8601 tidsstämpel | 2026-02-12T14:30:00.000Z |
X-Frihet-Signature | HMAC-SHA256-signatur för payloaden | sha256=a1b2c3d4e5f6... |
Rubriken X-Frihet-Signature inkluderas endast om du har konfigurerat en hemlighet i webhooken.
Signaturverifiering
Om du konfigurerar en hemlighet när du skapar webhooken, signerar Frihet varje payload med HMAC-SHA256. Verifieringsprocessen består av att:
- Ta den råa förfråganskroppen (raw body)
- Beräkna HMAC-SHA256 med hemligheten som nyckel
- Jämföra resultatet med värdet i rubriken
X-Frihet-Signature(utan prefixetsha256=)
Med SDK (rekommenderas)
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('Ogiltig signatur');
const event = JSON.parse(req.body.toString());
console.log(req.headers['x-frihet-event'], event.data);
res.sendStatus(200);
});
Manuellt exempel i 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')
);
}
// Användning i en Express-server
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('Ogiltig signatur');
}
const event = JSON.parse(req.body.toString());
const eventType = req.headers['x-frihet-event'];
console.log(`Händelse mottagen: ${eventType}`, event.data);
// Bearbeta händelsen beroende på dess typ
switch (eventType) {
case 'invoice.paid':
// Uppdatera ditt bokföringssystem
break;
case 'expense.created':
// Meddela finansavdelningen
break;
}
res.status(200).send('OK');
});
Exempel i 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'Händelse mottagen: {event_type}')
if event_type == 'invoice.paid':
# Uppdatera ditt bokföringssystem
pass
elif event_type == 'quote.accepted':
# Konvertera offert till faktura
pass
return 'OK', 200
Viktigt: Använd alltid en tidskonstant jämförelse (timingSafeEqual i Node.js, compare_digest i Python) för att undvika timing-attacker.
Återförsökspolicy
Om en webhook-leverans misslyckas (icke-2xx svarskod eller timeout), försöker Frihet automatiskt igen med exponentiell backoff:
| Försök | Fördröjning | Ackumulerad tid |
|---|---|---|
| 1 (initialt) | omedelbart | 0s |
| 2 (första återförsöket) | 2 sekunder | 2s |
| 3 (andra återförsöket) | 4 sekunder | 6s |
- Maximalt 3 försök per leverans (1 initial + 2 återförsök)
- Maximal fördröjning på 30 sekunder mellan återförsök
- Timeout på 30 sekunder per förfrågan
- Om alla 3 försök misslyckas, markeras leveransen som
failed
Återförsöken bearbetas av ett jobb som körs var 5:e minut, vilket garanterar tillförlitlighet även om huvudprocessen startas om.
Du kan se leveranshistoriken för varje webhook från Frihets kontrollpanel, inklusive svarskoden, svarskroppen (första 1000 tecken) och felen för varje försök.
Tester
Från Frihets kontrollpanel kan du skicka en test-händelse till vilken konfigurerad webhook som helst. Test-payloaden har följande struktur:
{
"event": "webhook.test",
"timestamp": "2026-02-12T14:30:00.000Z",
"data": {
"message": "This is a test webhook from Frihet ERP"
}
}
Detta gör det möjligt att verifiera att din endpoint är tillgänglig, att signaturen valideras korrekt och att ditt system bearbetar händelser utan fel.
Bästa praxis
Svara snabbt
Din endpoint bör svara med en 200-kod så snabbt som möjligt. Om du behöver utföra tung bearbetning (skicka e-post, uppdatera externa databaser, etc.), acceptera händelsen och bearbeta den asynkront i en arbetskö.
Hantera idempotens
Det är möjligt att en och samma händelse levereras mer än en gång (till exempel om din server svarade med en timeout men bearbetade händelsen). Använd fältet X-Frihet-Delivery-Id som en idempotensnyckel för att undvika dubbletter.
Verifiera signaturen alltid
Lita aldrig på en webhook utan att verifiera rubriken X-Frihet-Signature. Alla aktörer med tillgång till din URL skulle kunna skicka falska payloads.
Använd HTTPS
I produktion måste din endpoint skyddas med HTTPS. Frihet avvisar HTTP-URL:er (förutom under lokal utveckling).
Övervaka fel
Granska regelbundet leveransloggarna i Frihets kontrollpanel. Om du ser återkommande misslyckade leveranser, verifiera att din endpoint är tillgänglig och svarar på mindre än 30 sekunder.
Filtrera händelser
Prenumerera endast på de händelser du behöver. Varje webhook kan lyssna på en eller flera händelsetyper. Ju färre onödiga händelser du bearbetar, desto lägre blir belastningen på din server.
Felsökning av leveranser
Frihet loggar resultatet av varje webhook-leverans. Du kan se det från kontrollpanelen:
- Gå till Inställningar > Utvecklare > Webhooks
- Klicka på den webhook du vill inspektera
- Öppna fliken Leveranser
Varje post visar:
- HTTP-svarskod returnerad av din endpoint
- Svarstid i millisekunder
- Svarskropp (första 1000 tecken)
- Datum och tid för varje försök (inklusive återförsök)
- Slutlig status:
delivered,retryingellerfailed
Vanliga problem
| Symptom | Sannolik orsak | Lösning |
|---|---|---|
| Alla leveranser misslyckas med timeout | Endpointen tar mer än 30 sekunder att svara | Acceptera händelsen med en omedelbar 200 och bearbeta asynkront |
| SSL/TLS-fel | Utgånget certifikat eller ofullständig kedja | Förnya certifikatet och verifiera kedjan med openssl s_client |
| Systematisk 403-kod | Brandvägg blockerar inkommande POST-förfrågningar | Tillåt trafik från Google Cloud IP:er (us-central1) |
| 502/503-kod | Server nere eller under underhåll | Granska serverloggarna och verifiera att processen är aktiv |
Testknapp
På detaljskärmen för varje webhook skickar knappen Skicka test en syntetisk webhook.test-händelse till din URL. Använd denna knapp för att verifiera:
- Att URL:en är tillgänglig från internet
- Att HMAC-signaturen valideras korrekt
- Att din server svarar med en
2xx-kod
Testresultatet visas omedelbart i leveransloggen.
Felsökning
Jag får inga webhooks
- Verifiera att webhookens URL är korrekt och tillgänglig från internet
- Kontrollera att webhooken är i aktiv status i kontrollpanelen
- Granska leveransloggarna för att se om det finns fel
- Om du använder en brandvägg, se till att inkommande POST-förfrågningar från Google Cloud (
us-central1) är tillåtna
Signaturerna matchar inte
- Verifiera att du använder den råa förfråganskroppen (innan JSON parsas)
- Bekräfta att hemligheten i din kod matchar den som konfigurerats i Frihets kontrollpanel
- Ändra eller omformatera inte kroppen innan du verifierar signaturen
- Kontrollera att ditt ramverk inte automatiskt parsar kroppen innan du kan komma åt den råa
Återförsöken kommer inte fram
- Återförsöken bearbetas var 5:e minut. Om din endpoint var nere kortvarigt kan försöken redan ha tagit slut
- Granska leveranshistoriken för att bekräfta statusen för varje försök
- Om alla 3 försök misslyckades kommer leveransen inte att försöka igen automatiskt