Webhook Payload Reference
KoeIQ sends HTTP POST requests to your configured endpoint when events occur. All payloads are signed with HMAC-SHA256.
Event Types
| Event | Triggered when |
|---|---|
| transcription.completed | Transcription processing finishes for a call |
| analytics.completed | AI analytics (quality score, emotion, intent) finish |
| alert.triggered | An alert rule condition is met |
transcription.completed
{
"event": "transcription.completed",
"timestamp": "2026-03-17T09:00:00Z",
"tenant_id": "t_xxxx",
"data": {
"voicelog_id": "vl_xxxx",
"call_id": "CALL-20260317-001",
"status": "done",
"duration_seconds": 342,
"language": "ja-JP",
"operator_id": "OP001",
"department": "Support",
"call_date": "2026-03-17"
}
}analytics.completed
{
"event": "analytics.completed",
"timestamp": "2026-03-17T09:01:30Z",
"tenant_id": "t_xxxx",
"data": {
"voicelog_id": "vl_xxxx",
"call_id": "CALL-20260317-001",
"quality_score": 82,
"sentiment_overall": 0.4,
"dominant_emotion": "neutral",
"intent_primary": "billing_inquiry",
"summary": "Customer called about invoice discrepancy..."
}
}alert.triggered
{
"event": "alert.triggered",
"timestamp": "2026-03-17T09:02:00Z",
"tenant_id": "t_xxxx",
"data": {
"alert_rule_id": "ar_xxxx",
"alert_rule_name": "Low Quality Score",
"voicelog_id": "vl_xxxx",
"call_id": "CALL-20260317-001",
"condition": "quality_score_below",
"threshold": 60,
"actual_value": 45,
"operator_id": "OP001"
}
}Verifying Signatures
Every request includes an X-KoeIQ-Signature header in the format sha256=<hex> — the HMAC-SHA256 of the raw request body signed with your webhook secret.
Warning: Always verify the signature against the raw request body bytes, not a parsed JSON object — serialisation differences will cause mismatches.
Python
import hmac, hashlib
def verify_signature(payload_body: bytes, signature_header: str, secret: str) -> bool:
expected = hmac.new(
secret.encode(), payload_body, hashlib.sha256
).hexdigest()
return hmac.compare_digest(f"sha256={expected}", signature_header)
# In your webhook handler:
sig = request.headers.get("X-KoeIQ-Signature", "")
if not verify_signature(request.body, sig, "your_webhook_secret"):
return 401Node.js
import crypto from "crypto";
function verifySignature(payload: string, signatureHeader: string, secret: string): boolean {
const expected = "sha256=" + crypto
.createHmac("sha256", secret)
.update(payload, "utf8")
.digest("hex");
return crypto.timingSafeEqual(
Buffer.from(expected),
Buffer.from(signatureHeader)
);
}
// In your webhook handler:
const sig = req.headers["x-koeiq-signature"] as string;
if (!verifySignature(rawBody, sig, process.env.WEBHOOK_SECRET!)) {
return res.status(401).json({ error: "Invalid signature" });
}