IntegrationsWebhooks
Verifying Signatures
Every Kaiten Cloud webhook delivery is signed. Verify the signature with the Svix SDK before trusting a payload — headers, snippets for Node.js, Python, and Go, and where the secret lives.
Every webhook delivery from Kaiten Cloud carries a signature computed with the endpoint's signing secret. Verifying it proves two things before your handler acts: the delivery came from Kaiten, and the body was not altered in transit. Treat an unverified delivery as untrusted input.
The headers
Each delivery carries three headers, set by the delivery provider:
| Header | Content |
|---|---|
svix-id | The delivery's unique message id — stable across retry attempts, and your deduplication key |
svix-timestamp | Unix timestamp of the attempt; verification rejects timestamps too far from the present, which defeats replayed deliveries |
svix-signature | The HMAC-SHA256 signature over the id, the timestamp, and the raw body |
These are HTTP headers of the request. The optional headers object a delivery can carry inside its body is something else: the event's own metadata — instance_id, traceparent — described with the payload examples. It is part of the body, so the signature covers it.
Verifying with the Svix SDK
The Svix SDKs implement the check in one call: pass the raw request body, the headers, and the endpoint's secret.
import express from 'express';
import { Webhook } from 'svix';
const app = express();
const wh = new Webhook(process.env.KAITEN_WEBHOOK_SECRET);
// Register the route with a raw-body parser: the signature covers the bytes as sent.
app.post('/kaiten-webhook', express.raw({ type: 'application/json' }), (req, res) => {
let event;
try {
event = wh.verify(req.body, req.headers); // throws if the signature does not verify
} catch (err) {
return res.status(400).send('invalid signature');
}
// event.name, event.type, event.data — the published contract; event.headers when present
res.status(200).send('ok');
});import json
import os
from fastapi import FastAPI, Request, Response
from svix.webhooks import Webhook, WebhookVerificationError
app = FastAPI()
wh = Webhook(os.environ["KAITEN_WEBHOOK_SECRET"])
@app.post("/kaiten-webhook")
async def handle_webhook(request: Request):
body = await request.body() # raw bytes, before any JSON parsing
try:
wh.verify(body, request.headers) # raises if the signature does not verify
except WebhookVerificationError:
return Response(status_code=400)
event = json.loads(body) # event["name"], event["type"], event["data"]; event.get("headers") when present
return {"status": "ok"}package main
import (
"io"
"log"
"net/http"
"os"
svix "github.com/svix/svix-webhooks/go"
)
func main() {
// An unset or malformed secret is refused here, at startup, not on the first delivery.
wh, err := svix.NewWebhook(os.Getenv("KAITEN_WEBHOOK_SECRET"))
if err != nil {
log.Fatal(err)
}
http.HandleFunc("/kaiten-webhook", func(w http.ResponseWriter, r *http.Request) {
body, err := io.ReadAll(r.Body) // raw bytes, before any JSON decoding
if err != nil {
http.Error(w, "cannot read body", http.StatusBadRequest)
return
}
if err := wh.Verify(body, r.Header); err != nil {
http.Error(w, "invalid signature", http.StatusBadRequest)
return
}
// json.Unmarshal(body, &event) — name, type, data, and the optional headers
w.WriteHeader(http.StatusOK)
})
log.Fatal(http.ListenAndServe(":8080", nil))
}Verify against the raw request body, exactly as received. A body that has been parsed and re-serialized — by a JSON middleware, for instance — will not match the signature even when its content is identical.
A receiver that already depends on a Kaiten SDK has the same check without the Svix dependency: verifyKaitenWebhook in @kaitencloud/server and kaitencloud.webhooks.verify_webhook in the Python SDK verify a delivery against the same three headers and return its payload typed — the SDKs page says what else each one covers.
Where the secret lives
Each endpoint has its own signing secret. It is shown in the console's endpoint list under Integrations → Webhooks — masked, with a reveal control — and returned over the API by GET /webhooks/{id}, as signingSecret; the create response and the endpoint list do not carry it. It belongs in the configuration of the service that receives the deliveries, never in client-side code. Creating and managing endpoints: managing endpoints.
Where to go next
Verification is the first line of a handler; delivery and retries covers the rest — answering quickly, deduplicating on svix-id, reading the history. The API conventions page places signatures among the other guarantees of the API.
Delivery and Retries
How Kaiten Cloud delivers a webhook event, what happens when your endpoint fails, and where to read the delivery history.
Overview
Keep your CRM in step with Kaiten — a connector mirrors Kaiten customers and instances into the CRM, with Kaiten as the source of truth. Attio is available today.