Docs/Webhook Payload Reference

Webhook Payload Reference

KoeIQ sends HTTP POST requests to your configured endpoint when events occur. All payloads are signed with HMAC-SHA256.

Event Types

EventTriggered when
transcription.completedTranscription processing finishes for a call
analytics.completedAI analytics (quality score, emotion, intent) finish
alert.triggeredAn 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 401

Node.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" });
}

Next Steps

All DocsContact Support →