Docs menu
Outbound webhooks
Get finished reports and call summaries pushed to your systems as they happen, so you don't have to poll.
Setting up #
In Dashboard → Integrations → Add a destination, choose Webhook and enter your HTTPS endpoint. Pick the events you want. After you connect, a signing secret (whsec_…) is shown once. Store it with your endpoint's config. Slack, Microsoft Teams and Discord are also available as ready-made destinations.
Events #
| Event | Sent when |
|---|---|
report.completed | An AI report finishes, whether started from the dashboard, a schedule or the API |
call.completed | A call recording has been transcribed and summarised |
Request format #
Each delivery is a POST with a JSON body and these headers:
| Header | Value |
|---|---|
X-Summarix-Event | report.completed or call.completed |
X-Summarix-Delivery | Delivery ID, the same on every retry. Use it to ignore duplicates |
X-Summarix-Timestamp | Unix seconds when the request was signed |
X-Summarix-Signature | sha256= + hex HMAC-SHA256 of {timestamp}.{raw body} with your signing secret |
{
"event": "report.completed",
"workspace": "Acme (Pty) Ltd",
"report": {
"id": "cm…",
"title": "Q3 sales performance",
"url": "https://app.summarix.co.za/dashboard/reports/cm…",
"summary": "Revenue grew 18% quarter-on-quarter…",
"kpis": [{ "label": "Revenue", "value": "R 1 234 567" }],
"insights": ["…"],
"recommendations": ["…"]
}
}{
"event": "call.completed",
"workspace": "Acme (Pty) Ltd",
"call": {
"id": "cm…",
"url": "https://app.summarix.co.za/dashboard/calls/cm…",
"title": "Billing query resolved",
"summary": "Customer queried a duplicate debit order…",
"sentiment": "positive",
"outcome": "resolved",
"agentName": "Thandi",
"durationSec": 312,
"startedAt": "2026-09-24T08:15:00.000Z",
"followUpRequired": true,
"actionItems": [{ "owner": "agent", "task": "Email corrected invoice", "due": "today" }]
}
}Verifying signatures #
Always verify the signature before trusting a webhook, and reject timestamps more than 5 minutes old to block replays. Use the raw request body, before any JSON parsing.
import crypto from "node:crypto";
app.post("/summarix", express.raw({ type: "application/json" }), (req, res) => {
const ts = req.header("X-Summarix-Timestamp");
const sig = req.header("X-Summarix-Signature") ?? "";
if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) return res.sendStatus(400);
const expected = "sha256=" + crypto.createHmac("sha256", process.env.SUMMARIX_WEBHOOK_SECRET)
.update(`${ts}.${req.body}`).digest("hex");
if (sig.length !== expected.length || !crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected))) return res.sendStatus(401);
const event = JSON.parse(req.body);
// … handle event.event
res.sendStatus(204);
});import hmac, hashlib, time, os
from flask import request, abort
@app.post("/summarix")
def summarix():
ts = request.headers.get("X-Summarix-Timestamp", "0")
sig = request.headers.get("X-Summarix-Signature", "")
if abs(time.time() - int(ts)) > 300: abort(400)
body = request.get_data()
expected = "sha256=" + hmac.new(os.environ["SUMMARIX_WEBHOOK_SECRET"].encode(), f"{ts}.".encode() + body, hashlib.sha256).hexdigest()
if not hmac.compare_digest(sig, expected): abort(401)
event = request.get_json()
return "", 204$ts = $_SERVER['HTTP_X_SUMMARIX_TIMESTAMP'] ?? '0';
$sig = $_SERVER['HTTP_X_SUMMARIX_SIGNATURE'] ?? '';
$body = file_get_contents('php://input');
$expected = 'sha256=' . hash_hmac('sha256', $ts . '.' . $body, getenv('SUMMARIX_WEBHOOK_SECRET'));
if (abs(time() - (int)$ts) > 300 || !hash_equals($expected, $sig)) { http_response_code(401); exit; }
$event = json_decode($body, true);Delivery & retries #
Respond with any 2xx within 15 seconds. Non-2xx responses and timeouts are retried with exponential backoff (after about 30 s, then 1 min), for up to 3 attempts in total. After that, the delivery is marked failed and the error is shown on the destination card. Deliveries can arrive more than once or out of order. X-Summarix-Delivery stays the same across retries, so use it to de-duplicate.