Hoppa till huvudinnehåll

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

  1. Gå till Konfiguration > Webhooks på ditt Frihet-konto
  2. Klicka på Skapa webhook
  3. Ange ett beskrivande namn, destinations-URL och välj de händelser du vill ta emot
  4. Alternativt, definiera en hemlighet för HMAC-verifiering (rekommenderas)
  5. 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ältTypObligatorisktBeskrivning
urlstringJaDestinations-URL (HTTPS obligatoriskt i produktion)
eventsstring[]JaLista över händelsetyper att ta emot
secretstringNejHemlighet för HMAC-SHA256-signering (rekommenderas)
activebooleanNejWebhookens 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 localhost och 127.0.0.1 under 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ändelseBeskrivning
invoice.createdEn ny faktura har skapats
invoice.updatedEn befintlig faktura har ändrats
invoice.paidEn faktura har markerats som betald
invoice.overdueEn faktura har överskridit sitt förfallodatum

Utgifter (2 händelser)

HändelseBeskrivning
expense.createdEn ny utgift har registrerats
expense.updatedEn befintlig utgift har ändrats

Offerter (4 händelser)

HändelseBeskrivning
quote.createdEn ny offert har skapats
quote.updatedEn befintlig offert har ändrats
quote.acceptedEn kund har accepterat en offert (statusändring till accepted)
quote.rejectedEn kund har avvisat en offert (statusändring till rejected)

Kunder (2 händelser)

HändelseBeskrivning
client.createdEn ny kund har registrerats
client.updatedKunduppgifter har ändrats

Produkter (2 händelser)

HändelseBeskrivning
product.createdEn ny produkt eller tjänst har skapats
product.updatedEn 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:

RubrikBeskrivningExempel
Content-TypeInnehållstypapplication/json
X-Frihet-EventHändelsetypinvoice.paid
X-Frihet-Delivery-IdUnik leveransidentifierared4e5f6a7b8c9
X-Frihet-TimestampISO 8601 tidsstämpel2026-02-12T14:30:00.000Z
X-Frihet-SignatureHMAC-SHA256-signatur för payloadensha256=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:

  1. Ta den råa förfråganskroppen (raw body)
  2. Beräkna HMAC-SHA256 med hemligheten som nyckel
  3. Jämföra resultatet med värdet i rubriken X-Frihet-Signature (utan prefixet sha256=)

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ökFördröjningAckumulerad tid
1 (initialt)omedelbart0s
2 (första återförsöket)2 sekunder2s
3 (andra återförsöket)4 sekunder6s
  • 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:

  1. Gå till Inställningar > Utvecklare > Webhooks
  2. Klicka på den webhook du vill inspektera
  3. Ö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, retrying eller failed

Vanliga problem

SymptomSannolik orsakLösning
Alla leveranser misslyckas med timeoutEndpointen tar mer än 30 sekunder att svaraAcceptera händelsen med en omedelbar 200 och bearbeta asynkront
SSL/TLS-felUtgånget certifikat eller ofullständig kedjaFörnya certifikatet och verifiera kedjan med openssl s_client
Systematisk 403-kodBrandvägg blockerar inkommande POST-förfrågningarTillåt trafik från Google Cloud IP:er (us-central1)
502/503-kodServer nere eller under underhållGranska 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

  1. Verifiera att webhookens URL är korrekt och tillgänglig från internet
  2. Kontrollera att webhooken är i aktiv status i kontrollpanelen
  3. Granska leveransloggarna för att se om det finns fel
  4. 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

  1. Verifiera att du använder den råa förfråganskroppen (innan JSON parsas)
  2. Bekräfta att hemligheten i din kod matchar den som konfigurerats i Frihets kontrollpanel
  3. Ändra eller omformatera inte kroppen innan du verifierar signaturen
  4. Kontrollera att ditt ramverk inte automatiskt parsar kroppen innan du kan komma åt den råa

Återförsöken kommer inte fram

  1. Återförsöken bearbetas var 5:e minut. Om din endpoint var nere kortvarigt kan försöken redan ha tagit slut
  2. Granska leveranshistoriken för att bekräfta statusen för varje försök
  3. Om alla 3 försök misslyckades kommer leveransen inte att försöka igen automatiskt

Payload för stor

Om resursen som är kopplad till händelsen är mycket stor (många rader på en faktura, omfattande fält), kan payloaden överstiga gränsen på 100 KB och leveransen kommer att avvisas. Förenkla resursens data eller kontakta support om du behöver en högre gräns.