Webhooks
Signed events pushed to your server the moment Kuno finishes processing a note — no polling.
Webhooks push a JSON event to your server the moment Kuno finishes a processing step. Every delivery is signed with an endpoint-specific secret so your server can verify it really came from Kuno.
Register an endpoint
- Add the webhook. In the app: Settings → Developer settings → Webhooks → "Add webhook". Enter your HTTPS payload URL and pick the events (up to 10 endpoints per account).
- Store the signing secret. The secret (
kuno_whsec_…) is shown exactly once — store it next to your endpoint. - Send a test event. Trigger a test from the endpoint's detail sheet — Kuno shows your server's live HTTP status.
From the same detail sheet you can also pause/resume the endpoint, rotate the secret, inspect recent deliveries, or delete the webhook.
Events
| Type | Fires when | Payload highlights |
|---|---|---|
transcription.completed | A transcript is ready. | data.transcript.segments — speaker, text, timings. |
summary.completed | A summary is ready. | data.summary — title, body, key points, decisions, action items. |
recording.processed | All processing for a note finished successfully. | data.recording only — a light "done" signal. |
ping | You send a test from the app. | data.webhookId. |
Every event also carries data.recording with the note's id,
title, status, duration, and language.
Envelope
{
"id": "evt_5f0c4c8e-…",
"type": "summary.completed",
"createdAt": "2026-07-04T08:12:31.000Z",
"data": {
"recording": { "id": "…", "title": "Standup notes", "status": "processed",
"durationSeconds": 1260, "languageCode": "de" },
"summary": { "id": "…", "title": "Standup — July 4", "body": "…",
"keyPoints": ["…"], "decisions": ["…"],
"actionItems": ["…"] }
}
} Delivery headers
| Header | Value |
|---|---|
X-Kuno-Event | The event type. |
X-Kuno-Event-Id | The envelope id — deduplicate on this. |
X-Kuno-Timestamp | Unix seconds when the delivery was signed. |
X-Kuno-Signature | v1=<hex HMAC-SHA256> |
User-Agent | Kuno-Webhooks/1.0 |
Verify the signature
The signed string is {timestamp}.{rawBody} — the raw request
bytes, not re-serialized JSON. Compare in constant time, and reject
timestamps older than ~5 minutes to blunt replay:
import { createHmac, timingSafeEqual } from "node:crypto";
function verify(secret, timestamp, rawBody, signatureHeader) {
if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false;
const expected =
"v1=" +
createHmac("sha256", secret)
.update(`${timestamp}.${rawBody}`)
.digest("hex");
const a = Buffer.from(expected);
const b = Buffer.from(signatureHeader);
return a.length === b.length && timingSafeEqual(a, b);
} Delivery & retries
- HTTP
POST, JSON body; respond with any2xxwithin 10 seconds. -
Failed deliveries are retried twice (about 2 s and then 10 s later) —
three attempts total. Delivery is at-least-once:
deduplicate on
X-Kuno-Event-Id. - After 20 consecutive failures the endpoint is paused automatically; the app shows why, and you can resume it after fixing your server.
- Test events count too: a manually triggered test event runs through the same failure counter as real deliveries. A failed test moves the endpoint closer to auto-pause, and a successful test resets the counter to zero — which can mask a flaky setup if you only test occasionally.
Security: endpoints must be public HTTPS URLs — private and internal hosts are rejected. Rotating the secret re-signs all future deliveries immediately, so update your server first. Webhook payloads contain your note content — treat your endpoint like any system that stores personal data.