Webhooks
Få besked i jeres eget system, når der kommer en henvendelse, et opkald, en booking eller en underskrift, og tjek, at beskeden er fra os.
Denne side er til den, der sætter jeres eget system op, fx et CRM, en webshop eller et automatiseringsværktøj som Zapier, Make eller n8n.
En webhook er en adresse hos jer. Når noget sker i Kaneo, sender vi en POST med JSON til adressen. Svarer den ikke med 2xx, prøver vi igen i godt et døgn.
Webhooks kræver Pro, Business eller Enterprise, og kun administratorer kan sætte dem op.
Sæt en webhook op
Gå til Integrationer → API og webhooks, og klik på Tilføj webhook under Webhooks.
Skriv Adresse. Den skal starte med https:// og være offentlig, adresser på et internt netværk afvises.
Vælg de hændelser, I vil have, og klik på Tilføj webhook.
Kopiér Signeringshemmeligheden (den starter med whsec_), og gem den i jeres system. Den vises kun én gang. Har I mistet den, så fjern webhooken, og opret den igen.
Under Leveringer og test kan I sende en testhændelse og se de seneste leveringer: Leveret, Venter eller Fejlet, med svaret fra jeres adresse. Send igen sender en levering en gang til.
Hændelser
| Hændelse | Hvornår |
|---|---|
henvendelse.oprettet | En ny henvendelse fra formular, widget, API, mail eller telefon |
henvendelse.opdateret | Status, sagsstatus, prioritet, kategori eller ansvarlig er ændret |
henvendelse.besvaret | Et svar er sendt til kunden, af en medarbejder eller af Neo |
opkald.afsluttet | Neo har afsluttet et opkald eller en samtale fra hjemmesiden |
kontakt.oprettet | En ny kunde er føjet til kundelisten |
booking.oprettet | Neo har booket en tid eller et bord |
underskrift.underskrevet | En kunde har skrevet under på en aftale |
Alle hændelser har samme form:
{
"type": "henvendelse.oprettet",
"version": 1,
"id": "evt_…",
"tidspunkt": "2026-10-03T09:30:00.000Z",
"virksomhed_id": "…",
"data": {
"henvendelse": {
"id": "…",
"kilde_id": "…",
"kanal": "formular",
"status": "active",
"sagsstatus": "received",
"prioritet": "medium",
"kategori_id": null,
"ansvarlig_id": null,
"team_id": null,
"kontakt": { "navn": "Anna Jensen", "email": "anna@eksempel.dk", "telefon": "+45 12 34 56 78" },
"emne": "Tilbud på køkken",
"besked": "Hej, kan I give et tilbud på et nyt køkken?",
"oprettet": "2026-10-03T09:30:00.000Z",
"opdateret": null
}
}
}versioner 1. Vi tilføjer måske felter, men omdøber eller fjerner aldrig et felt i samme version.ider den samme, hvis den samme hændelse kommer to gange. Brug den til at se bort fra dubletter.- Testhændelsen har eksempeldata og
"id": "evt_eksempel".
Tjek, at det er os
Hver levering har tre headere (efter Standard Webhooks):
| Header | Indhold |
|---|---|
webhook-id | Leveringens id |
webhook-timestamp | Unix-tid i sekunder |
webhook-signature | v1,<signatur>, der kan stå flere, adskilt af mellemrum |
Signaturen er en HMAC-SHA256 i base64 af teksten <webhook-id>.<webhook-timestamp>.<body>. Nøglen er hemmeligheden uden whsec_, base64-afkodet.
Brug den rå body, præcis som den kom. Læser jeres framework JSON'en og skriver den igen, passer signaturen ikke. Afvis også leveringer, hvor tidsstemplet er mere end 5 minutter gammelt.
Node.js (Express)
import crypto from "node:crypto";
import express from "express";
const hemmelighed = process.env.TELENOA_WEBHOOK_SECRET; // whsec_…
const noegle = Buffer.from(hemmelighed.replace(/^whsec_/, ""), "base64");
function erFraTelenoa(id, tidsstempel, signaturer, body) {
if (!id || !tidsstempel || !signaturer) return false;
if (Math.abs(Date.now() / 1000 - Number(tidsstempel)) > 300) return false;
const forventet = crypto.createHmac("sha256", noegle).update(`${id}.${tidsstempel}.${body}`).digest();
return signaturer.split(" ").some((s) => {
const [version, sig] = s.split(",");
if (version !== "v1" || !sig) return false;
const modtaget = Buffer.from(sig, "base64");
return modtaget.length === forventet.length && crypto.timingSafeEqual(modtaget, forventet);
});
}
const app = express();
app.post("/telenoa", express.raw({ type: "application/json" }), (req, res) => {
const body = req.body.toString("utf8");
if (!erFraTelenoa(req.get("webhook-id"), req.get("webhook-timestamp"), req.get("webhook-signature"), body)) {
return res.sendStatus(401);
}
const haendelse = JSON.parse(body);
console.log(haendelse.type, haendelse.data);
res.sendStatus(204);
});
app.listen(3000);Python (Flask)
import base64, hashlib, hmac, json, os, time
from flask import Flask, abort, request
hemmelighed = os.environ["TELENOA_WEBHOOK_SECRET"] # whsec_…
noegle = base64.b64decode(hemmelighed.removeprefix("whsec_"))
def er_fra_telenoa(id, tidsstempel, signaturer, body: bytes) -> bool:
if not id or not tidsstempel or not signaturer or not tidsstempel.isdigit():
return False
if abs(time.time() - int(tidsstempel)) > 300:
return False
tekst = f"{id}.{tidsstempel}.".encode() + body
forventet = base64.b64encode(hmac.new(noegle, tekst, hashlib.sha256).digest()).decode()
return any(
s.startswith("v1,") and hmac.compare_digest(s[3:], forventet)
for s in signaturer.split(" ")
)
app = Flask(__name__)
@app.post("/telenoa")
def telenoa():
body = request.get_data() # den rå body
h = request.headers
if not er_fra_telenoa(h.get("webhook-id"), h.get("webhook-timestamp"), h.get("webhook-signature"), body):
abort(401)
haendelse = json.loads(body)
print(haendelse["type"], haendelse["data"])
return "", 204Pakken standardwebhooks (npm og PyPI) kan også tjekke signaturen for jer: giv den hemmeligheden og de tre headere.
Leveringer og genforsøg
- Svar med 2xx inden for 30 sekunder. Alt andet tæller som en fejl.
- Fejler en levering, prøver vi igen efter 5 sekunder, 5 minutter, 30 minutter, 2 timer, 5 timer, 10 timer og 10 timer.
- Fejler en adresse i 5 døgn i træk, slås den fra. Fjern den, og opret den igen, når den virker.
- Leveringernes indhold gemmes i 30 dage. Derefter kan de ikke sendes igen.
- Skifter I ned til Essential, sendes intet, før I opgraderer igen.
Den gamle webhook
Webhooken under Opsætning → Kategorier → Regler ("Send nye henvendelser til jeres eget system") virker som før, med sit eget format og headeren x-telenoa-signature. Se API og webhooks. Begge kan være slået til på samme tid. Til nye opsætninger anbefaler vi webhooks her, fordi de har flere hændelser, et fast format og genforsøg i godt et døgn.