telenoaHelp
Connections

Webhooks

Get notified in your own system when an inquiry, call, booking or signature comes in — and check that the message is from us.

This page is for whoever sets up your own system — for example a CRM, a webshop or an automation tool like Zapier, Make or n8n.

A webhook is an address on your side. When something happens in Telenoa, we send a POST with JSON to that address. If it doesn't answer with 2xx, we retry for just over a day.

Webhooks require Pro, Business or Enterprise, and only administrators can set them up.

Set up a webhook

Go to Forbindelser → API og webhooks, and click Tilføj webhook under Webhooks.

Enter the Adresse. It must start with https:// and be public — addresses on an internal network are rejected.

Choose the events you want, and click Tilføj webhook.

Copy the signing secret (it starts with whsec_) and store it in your system. It is shown only once. If you lose it, remove the webhook and create it again.

Under Leveringer og test you can send a test event and see the latest deliveries — Leveret (delivered), Venter (pending) or Fejlet (failed) — with the response from your address. Send igen delivers it once more.

Events

EventWhen
henvendelse.oprettetA new inquiry from a form, widget, API, email or phone
henvendelse.opdateretStatus, case status, priority, category or assignee changed
henvendelse.besvaretA reply was sent to the customer, by a team member or by Noa
opkald.afsluttetNoa finished a phone call or a website conversation
kontakt.oprettetA new customer was added to the customer list
booking.oprettetNoa booked a time slot or a table
underskrift.underskrevetA customer signed an agreement

Every event has the same shape:

{
  "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
    }
  }
}
  • version is 1. We may add fields, but we never rename or remove a field within the same version.
  • id stays the same if the same event arrives twice. Use it to ignore duplicates.
  • The test event contains example data and "id": "evt_eksempel".

Check that it's us

Every delivery has three headers (following Standard Webhooks):

HeaderContent
webhook-idThe delivery's id
webhook-timestampUnix time in seconds
webhook-signaturev1,<signature> — there can be several, separated by spaces

The signature is an HMAC-SHA256, base64-encoded, of the text <webhook-id>.<webhook-timestamp>.<body>. The key is the secret without whsec_, base64-decoded.

Use the raw body exactly as received. If your framework parses the JSON and serializes it again, the signature won't match. Also reject deliveries whose timestamp is more than 5 minutes old.

Node.js (Express)

import crypto from "node:crypto";
import express from "express";

const secret = process.env.TELENOA_WEBHOOK_SECRET; // whsec_…
const key = Buffer.from(secret.replace(/^whsec_/, ""), "base64");

function isFromTelenoa(id, timestamp, signatures, body) {
  if (!id || !timestamp || !signatures) return false;
  if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false;
  const expected = crypto.createHmac("sha256", key).update(`${id}.${timestamp}.${body}`).digest();
  return signatures.split(" ").some((s) => {
    const [version, sig] = s.split(",");
    if (version !== "v1" || !sig) return false;
    const received = Buffer.from(sig, "base64");
    return received.length === expected.length && crypto.timingSafeEqual(received, expected);
  });
}

const app = express();
app.post("/telenoa", express.raw({ type: "application/json" }), (req, res) => {
  const body = req.body.toString("utf8");
  if (!isFromTelenoa(req.get("webhook-id"), req.get("webhook-timestamp"), req.get("webhook-signature"), body)) {
    return res.sendStatus(401);
  }
  const event = JSON.parse(body);
  console.log(event.type, event.data);
  res.sendStatus(204);
});

app.listen(3000);

Python (Flask)

import base64, hashlib, hmac, json, os, time
from flask import Flask, abort, request

secret = os.environ["TELENOA_WEBHOOK_SECRET"]  # whsec_…
key = base64.b64decode(secret.removeprefix("whsec_"))

def is_from_telenoa(id, timestamp, signatures, body: bytes) -> bool:
    if not id or not timestamp or not signatures or not timestamp.isdigit():
        return False
    if abs(time.time() - int(timestamp)) > 300:
        return False
    text = f"{id}.{timestamp}.".encode() + body
    expected = base64.b64encode(hmac.new(key, text, hashlib.sha256).digest()).decode()
    return any(
        s.startswith("v1,") and hmac.compare_digest(s[3:], expected)
        for s in signatures.split(" ")
    )

app = Flask(__name__)

@app.post("/telenoa")
def telenoa():
    body = request.get_data()  # the raw body
    h = request.headers
    if not is_from_telenoa(h.get("webhook-id"), h.get("webhook-timestamp"), h.get("webhook-signature"), body):
        abort(401)
    event = json.loads(body)
    print(event["type"], event["data"])
    return "", 204

The standardwebhooks package (npm and PyPI) can also verify the signature for you: give it the secret and the three headers.

Deliveries and retries

  • Answer with 2xx within 30 seconds. Anything else counts as a failure.
  • If a delivery fails, we retry after 5 seconds, 5 minutes, 30 minutes, 2 hours, 5 hours, 10 hours and 10 hours.
  • If an address keeps failing for 5 days, it is disabled. Remove it and create it again once it works.
  • Delivery contents are kept for 30 days. After that they can't be resent.
  • If you downgrade to Essential, nothing is sent until you upgrade again.

The old webhook

The webhook under Opsætning → Kategorier → Regler ("Send nye henvendelser til jeres eget system") keeps working as before, with its own format and the x-telenoa-signature header. See API and webhooks. Both can be on at the same time. For new setups we recommend the webhooks on this page: more events, a fixed format and retries for just over a day.

See also

On this page