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 onceEvent 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
createdtimestamps, 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
200fast.
Event types
| Event | When |
|---|---|
transcript.completed | Batch transcription job finished. Includes transcript_id. |
transcript.failed | Job could not be completed. Includes error. |
call.started | Agent picked up or placed a call. |
call.completed | Call ended normally. |
call.failed | Technical failure during the call. |
experiment.finished | A Gym experiment crossed its stopping rule. |
deployment.ready | A self-hosted deployment transitioned to healthy. |
deployment.degraded | Health 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