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
- Gå til Konfigurasjon > Webhooks i din Frihet-konto
- Trykk Opprett webhook
- Skriv inn et beskrivende navn, destinasjons-URL og velg hendelsene du vil motta
- Valgfritt, definer en hemmelighet for HMAC-verifisering (anbefalt)
- 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
| Felt | Type | Obligatorisk | Beskrivelse |
|---|---|---|---|
url | string | Ja | Destinasjons-URL (HTTPS obligatorisk i produksjon) |
events | string[] | Ja | Liste over hendelsestyper å motta |
secret | string | Nei | Hemmelighet for HMAC-SHA256 signatur (anbefalt) |
active | boolean | Nei | Webhook-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
localhostog127.0.0.1under 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)
| Hendelse | Beskrivelse |
|---|---|
invoice.created | En ny faktura er opprettet |
invoice.updated | En eksisterende faktura er endret |
invoice.paid | En faktura er markert som betalt |
invoice.overdue | En faktura har passert forfallsdatoen |
Utgifter (2 hendelser)
| Hendelse | Beskrivelse |
|---|---|
expense.created | En ny utgift er registrert |
expense.updated | En eksisterende utgift er endret |
Tilbud (4 hendelser)
| Hendelse | Beskrivelse |
|---|---|
quote.created | Et nytt tilbud er opprettet |
quote.updated | Et eksisterende tilbud er endret |
quote.accepted | En kunde har akseptert et tilbud (statusendring til accepted) |
quote.rejected | En kunde har avvist et tilbud (statusendring til rejected) |
Kunder (2 hendelser)
| Hendelse | Beskrivelse |
|---|---|
client.created | En ny kunde er registrert |
client.updated | En kundes data er endret |
Produkter (2 hendelser)
| Hendelse | Beskrivelse |
|---|---|
product.created | Et nytt produkt eller en ny tjeneste er opprettet |
product.updated | Et 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:
| Header | Beskrivelse | Eksempel |
|---|---|---|
Content-Type | Innholdstype | application/json |
X-Frihet-Event | Hendelsestype | invoice.paid |
X-Frihet-Delivery-Id | Unik leveranse-identifikator | d4e5f6a7b8c9 |
X-Frihet-Timestamp | ISO 8601 tidsstempel | 2026-02-12T14:30:00.000Z |
X-Frihet-Signature | HMAC-SHA256 signatur av payloaden | sha256=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:
- Ta den rå forespørselskroppen (raw body)
- Beregn HMAC-SHA256 ved å bruke hemmeligheten som nøkkel
- Sammenlign resultatet med verdien av headeren
X-Frihet-Signature(uten prefiksetsha256=)
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øk | Forsinkelse | Akkumulert tid |
|---|---|---|
| 1 (initialt) | øyeblikkelig | 0s |
| 2 (første gjenforsøk) | 2 sekunder | 2s |
| 3 (andre gjenforsøk) | 4 sekunder | 6s |
- 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:
- Gå til Innstillinger > Utviklere > Webhooks
- Trykk på webhooken du vil inspisere
- Å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,retryingellerfailed
Vanlige problemer
| Symptom | Sannsynlig årsak | Løsning |
|---|---|---|
| Alle leveranser feiler med timeout | Endepunktet bruker mer enn 30s på å svare | Aksepter hendelsen med en umiddelbar 200 og behandle asynkront |
| SSL/TLS-feil | Sertifikat utløpt eller ufullstendig kjede | Forny sertifikatet og verifiser kjeden med openssl s_client |
| Systematisk 403-kode | Brannmur blokkerer innkommende POST-forespørsler | Tillat trafikk fra Google Cloud IP-er (us-central1) |
| 502/503-kode | Server nede eller under vedlikehold | Sjekk 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
- Verifiser at webhook-URL-en er korrekt og tilgjengelig fra internett
- Sjekk at webhooken er i aktiv status i panelet
- Gå gjennom leveranseloggene for å se etter feil
- Hvis du bruker en brannmur, sørg for at innkommende POST-forespørsler fra Google Cloud (
us-central1) er tillatt
Signaturene stemmer ikke overens
- Verifiser at du bruker den rå forespørselskroppen (før du parser JSON)
- Bekreft at hemmeligheten i koden din samsvarer med den som er konfigurert i Frihet-panelet
- Ikke endre eller omformater kroppen før du verifiserer signaturen
- Sjekk at rammeverket ditt ikke automatisk parser kroppen før du får tilgang til den rå
Gjenforsøk kommer ikke frem
- Gjenforsøk behandles hvert 5. minutt. Hvis endepunktet ditt var nede kortvarig, kan forsøkene allerede være brukt opp
- Gå gjennom leveransehistorikken for å bekrefte status for hvert forsøk
- Hvis de 3 forsøkene feilet, vil leveransen ikke prøves på nytt automatisk