Skip to main content

Webhooks

Frihet webhooks gjør det mulig å motta HTTP-varsler i sanntid når hendelser skjer i din konto. I stedet for å polle API-et, konfigurer en URL og Frihet vil sende en POST med hendelsesdataene i det øyeblikket det skjer.

Konfigurasjon

Du kan administrere webhooks fra Frihet-panelet (Konfigurasjon > Webhooks) eller programmatisk via REST API-et.

Fra panelet

  1. Gå til Konfigurasjon > Webhooks i din Frihet-konto
  2. Trykk Opprett webhook
  3. Skriv inn et beskrivende navn, destinasjons-URL og velg hendelsene du vil motta
  4. Valgfritt, definer en hemmelighet for HMAC-verifisering (anbefalt)
  5. Lagre webhooken

Webhook REST API

Administrer webhooks programmatisk med de komplette CRUD-endepunktene.

List opp 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
}

Hent webhook

GET /v1/webhooks/:id
curl https://api.frihet.io/v1/webhooks/wh_abc123 \
-H "X-API-Key: fri_tu-clave-aqui"

Opprett webhook

POST /v1/webhooks
FeltTypeObligatoriskBeskrivelse
urlstringJaDestinasjons-URL (HTTPS obligatorisk i produksjon)
eventsstring[]JaListe over hendelsestyper å motta
secretstringNeiHemmelighet for HMAC-SHA256 signatur (anbefalt)
activebooleanNeiWebhook-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"
}

Oppdater webhook

PATCH /v1/webhooks/:id

Du trenger bare å sende feltene du vil endre.

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): Oppdatert webhook-objekt.

Slett 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å bruke HTTPS (HTTP er kun tillatt for localhost og 127.0.0.1 under utvikling)
  • Private IP-er (10.x, 172.16-31.x, 192.168.x) er ikke tillatt for å unngå SSRF-angrep
  • Må svare med en 2xx-kode på mindre enn 30 sekunder

Grenser:

  • Maksimalt 20 webhooks per konto
  • Maksimal payload på 100 KB per leveranse

Hendelsestyper

Frihet sender ut 14 hendelsestyper, gruppert etter ressurs.

Fakturaer (4 hendelser)

HendelseBeskrivelse
invoice.createdEn ny faktura er opprettet
invoice.updatedEn eksisterende faktura er endret
invoice.paidEn faktura er markert som betalt
invoice.overdueEn faktura har passert forfallsdatoen

Utgifter (2 hendelser)

HendelseBeskrivelse
expense.createdEn ny utgift er registrert
expense.updatedEn eksisterende utgift er endret

Tilbud (4 hendelser)

HendelseBeskrivelse
quote.createdEt nytt tilbud er opprettet
quote.updatedEt eksisterende tilbud er endret
quote.acceptedEn kunde har akseptert et tilbud (statusendring til accepted)
quote.rejectedEn kunde har avvist et tilbud (statusendring til rejected)

Kunder (2 hendelser)

HendelseBeskrivelse
client.createdEn ny kunde er registrert
client.updatedEn kundes data er endret

Produkter (2 hendelser)

HendelseBeskrivelse
product.createdEt nytt produkt eller en ny tjeneste er opprettet
product.updatedEt eksisterende produkt eller en tjeneste er endret

Payload-struktur

Hver webhook-leveranse er en POST-forespørsel med JSON-kropp. Strukturen er den samme for alle hendelsestyper:

{
"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"
}
}

Feltet data inneholder den fullstendige tilstanden til ressursen på tidspunktet for hendelsen.


Leveranse-headere

Hver webhook-forespørsel inkluderer følgende headere:

HeaderBeskrivelseEksempel
Content-TypeInnholdstypeapplication/json
X-Frihet-EventHendelsestypeinvoice.paid
X-Frihet-Delivery-IdUnik leveranse-identifikatord4e5f6a7b8c9
X-Frihet-TimestampISO 8601 tidsstempel2026-02-12T14:30:00.000Z
X-Frihet-SignatureHMAC-SHA256 signatur av payloadensha256=a1b2c3d4e5f6...

Headeren X-Frihet-Signature er kun inkludert hvis du har konfigurert en hemmelighet i webhooken.


Signaturverifisering

Hvis du konfigurerer en hemmelighet når du oppretter webhooken, signerer Frihet hver payload med HMAC-SHA256. Verifiseringsprosessen består av:

  1. Ta den rå forespørselskroppen (raw body)
  2. Beregn HMAC-SHA256 ved å bruke hemmeligheten som nøkkel
  3. Sammenlign resultatet med verdien av headeren X-Frihet-Signature (uten prefikset sha256=)

Med SDK (anbefalt)

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('Ugyldig signatur');

const event = JSON.parse(req.body.toString());
console.log(req.headers['x-frihet-event'], event.data);
res.sendStatus(200);
});

Manuelt eksempel 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')
);
}

// Bruk 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('Ugyldig signatur');
}

const event = JSON.parse(req.body.toString());
const eventType = req.headers['x-frihet-event'];

console.log(`Hendelse mottatt: ${eventType}`, event.data);

// Behandle hendelsen i henhold til dens type
switch (eventType) {
case 'invoice.paid':
// Oppdater ditt regnskapssystem
break;
case 'expense.created':
// Varsle økonomiteamet
break;
}

res.status(200).send('OK');
});

Eksempel 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'Hendelse mottatt: {event_type}')

if event_type == 'invoice.paid':
# Oppdater ditt regnskapssystem
pass
elif event_type == 'quote.accepted':
# Konverter tilbud til faktura
pass

return 'OK', 200

Viktig: Bruk alltid en konstant tidssammenligning (timingSafeEqual i Node.js, compare_digest i Python) for å unngå timing-angrep.


Gjenforsøkspolicy

Hvis en webhook-leveranse feiler (ikke-2xx responsstatuskode eller timeout), vil Frihet automatisk prøve på nytt med eksponensiell backoff:

ForsøkForsinkelseAkkumulert tid
1 (initialt)øyeblikkelig0s
2 (første gjenforsøk)2 sekunder2s
3 (andre gjenforsøk)4 sekunder6s
  • Maksimalt 3 forsøk per leveranse (1 initialt + 2 gjenforsøk)
  • Maksimal forsinkelse på 30 sekunder mellom gjenforsøk
  • Timeout på 30 sekunder per forespørsel
  • Hvis alle 3 forsøk feiler, blir leveransen markert som failed

Gjenforsøk behandles via en jobb som kjører hvert 5. minutt, noe som garanterer pålitelighet selv om hovedprosessen startes på nytt.

Du kan sjekke leveransehistorikken for hver webhook fra Frihet-panelet, inkludert responsstatuskode, respons-kroppen (første 1000 tegn) og feilene fra hvert forsøk.


Testing

Fra Frihet-panelet kan du sende en testhendelse til enhver konfigurert webhook. Test-payloaden har følgende struktur:

{
"event": "webhook.test",
"timestamp": "2026-02-12T14:30:00.000Z",
"data": {
"message": "This is a test webhook from Frihet ERP"
}
}

Dette gjør det mulig å verifisere at ditt endepunkt er tilgjengelig, at signaturen valideres korrekt, og at systemet ditt behandler hendelsene uten feil.


Beste praksis

Svar raskt

Ditt endepunkt bør svare med en 200-kode så raskt som mulig. Hvis du trenger å utføre tung prosessering (sende e-poster, oppdatere eksterne databaser, etc.), aksepter hendelsen og behandle den asynkront i en arbeidskø.

Håndter idempotens

Det er mulig at samme hendelse leveres mer enn én gang (for eksempel hvis serveren din svarte med en timeout men behandlet hendelsen). Bruk feltet X-Frihet-Delivery-Id som en idempotensnøkkel for å unngå duplikater.

Verifiser alltid signaturen

Stol aldri på en webhook uten å verifisere headeren X-Frihet-Signature. Enhver aktør med tilgang til URL-en din kan sende falske payloads.

Bruk HTTPS

I produksjon må ditt endepunkt være beskyttet med HTTPS. Frihet avviser HTTP-URL-er (unntatt i lokal utvikling).

Overvåk feil

Gå jevnlig gjennom leveranseloggen i Frihet-panelet. Hvis du ser gjentatte mislykkede leveranser, sjekk at endepunktet ditt er tilgjengelig og svarer på mindre enn 30 sekunder.

Filtrer hendelser

Abonner kun på hendelsene du trenger. Hver webhook kan lytte til en eller flere hendelsestyper. Jo færre unødvendige hendelser du behandler, desto lavere blir belastningen på serveren din.


Feilsøking av leveranser

Frihet registrerer resultatet av hver webhook-leveranse. Du kan sjekke det fra panelet:

  1. Gå til Innstillinger > Utviklere > Webhooks
  2. Trykk på webhooken du vil inspisere
  3. Åpne fanen Leveranser

Hver oppføring viser:

  • HTTP-svarkode returnert av ditt endepunkt
  • Responstid i millisekunder
  • Respons-kropp (første 1000 tegn)
  • Dato og tid for hvert forsøk (inkludert gjenforsøk)
  • Endelig status: delivered, retrying eller failed

Vanlige problemer

SymptomSannsynlig årsakLøsning
Alle leveranser feiler med timeoutEndepunktet bruker mer enn 30s på å svareAksepter hendelsen med en umiddelbar 200 og behandle asynkront
SSL/TLS-feilSertifikat utløpt eller ufullstendig kjedeForny sertifikatet og verifiser kjeden med openssl s_client
Systematisk 403-kodeBrannmur blokkerer innkommende POST-forespørslerTillat trafikk fra Google Cloud IP-er (us-central1)
502/503-kodeServer nede eller under vedlikeholdSjekk serverloggene dine og verifiser at prosessen er aktiv

Testknapp

På detaljskjermen for hver webhook sender knappen Send test en syntetisk webhook.test-hendelse til URL-en din. Bruk denne knappen til å verifisere:

  • At URL-en er tilgjengelig fra internett
  • At HMAC-signaturen valideres korrekt
  • At serveren din svarer med en 2xx-kode

Testresultatet vises umiddelbart i leveranseloggen.


Feilsøking

Jeg mottar ikke webhooks

  1. Verifiser at webhook-URL-en er korrekt og tilgjengelig fra internett
  2. Sjekk at webhooken er i aktiv status i panelet
  3. Gå gjennom leveranseloggene for å se etter feil
  4. Hvis du bruker en brannmur, sørg for at innkommende POST-forespørsler fra Google Cloud (us-central1) er tillatt

Signaturene stemmer ikke overens

  1. Verifiser at du bruker den rå forespørselskroppen (før du parser JSON)
  2. Bekreft at hemmeligheten i koden din samsvarer med den som er konfigurert i Frihet-panelet
  3. Ikke endre eller omformater kroppen før du verifiserer signaturen
  4. Sjekk at rammeverket ditt ikke automatisk parser kroppen før du får tilgang til den rå

Gjenforsøk kommer ikke frem

  1. Gjenforsøk behandles hvert 5. minutt. Hvis endepunktet ditt var nede kortvarig, kan forsøkene allerede være brukt opp
  2. Gå gjennom leveransehistorikken for å bekrefte status for hvert forsøk
  3. Hvis de 3 forsøkene feilet, vil leveransen ikke prøves på nytt automatisk

For stor payload

Hvis ressursen knyttet til hendelsen er veldig stor (mange linjer på en faktura, omfattende felt), kan payloaden overskride grensen på 100 KB og leveransen vil bli avvist. Forenkle ressursdataene eller kontakt support hvis du trenger en høyere grense.