Docs/GuidesWebhooks

Webhooks

Wordcab pushes events — transcript done, call completed, experiment finished — to URLs you control. Signed, retried, and replayable.

Webhooks push events — job done, call completed, redaction flagged — to a URL you control. Every payload is HMAC-signed, retried on 5xx, and delivered at least once.

Register an endpoint

python
wh = client.webhooks.create(
    url="https://your-server.example.com/hooks/wordcab",
    events=[
        "transcript.completed",
        "call.started",
        "call.completed",
        "call.failed",
        "experiment.finished",
    ],
    description="Primary ops hook",
)

print(wh.id, wh.secret)   # the secret is shown exactly once

Event payload

json
{
  "id": "evt_01HZ...",
  "type": "call.completed",
  "created": 1712345678,
  "data": {
    "call_id": "call_abc123",
    "agent_id": "agent_xyz789",
    "duration": 145,
    "transcript_id": "transcript_def456",
    "ended_reason": "caller_hangup"
  }
}

Signature verification

Every POST includes Wordcab-Signature and Wordcab-Timestamp headers. The signature is HMAC-SHA256(timestamp + "." + body, secret), hex-encoded.

import hmac, hashlib, time

SECRET = b"whsec_..."

def verify(request):
    ts = request.headers["Wordcab-Timestamp"]
    sig = request.headers["Wordcab-Signature"]

    # Reject stale deliveries (replay window: 5 minutes)
    if abs(int(ts) - time.time()) > 300:
        raise ValueError("stale")

    expected = hmac.new(
        SECRET,
        msg=(ts + "." + request.body).encode(),
        digestmod=hashlib.sha256,
    ).hexdigest()

    if not hmac.compare_digest(expected, sig):
        raise ValueError("bad signature")
import crypto from "node:crypto";

export function verify(req) {
  const ts = req.headers["wordcab-timestamp"];
  const sig = req.headers["wordcab-signature"];

  if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) {
    throw new Error("stale");
  }

  const expected = crypto
    .createHmac("sha256", process.env.WORDCAB_WH_SECRET)
    .update(`${ts}.${req.rawBody}`)
    .digest("hex");

  if (!crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(sig))) {
    throw new Error("bad signature");
  }
}

Delivery semantics

  • At-least-once. Dedupe on evt_ id; store processed ids for at least 24 hours.
  • Order is not guaranteed. Rely on created timestamps, not arrival order.
  • Retries. 2xx = delivered. Anything else retries with exponential backoff over 24 hours (10 attempts).
  • Timeouts. 10-second response timeout. Move heavy work off the request thread and 200 fast.

Event types

EventWhen
transcript.completedBatch transcription job finished. Includes transcript_id.
transcript.failedJob could not be completed. Includes error.
call.startedAgent picked up or placed a call.
call.completedCall ended normally.
call.failedTechnical failure during the call.
experiment.finishedA Gym experiment crossed its stopping rule.
deployment.readyA self-hosted deployment transitioned to healthy.
deployment.degradedHealth probes failing; a model pool is unhealthy.

Kafka delivery

On self-hosted deployments, the same events can be published to a Kafka topic instead of (or in addition to) HTTP webhooks. Configure the topic at install time; the schema is identical.

yaml
events:
  kafka:
    enabled: true
    brokers:
      - kafka-1.internal:9092
      - kafka-2.internal:9092
    topic: wordcab.events
    acks: all