Skip to main content

Webhooks Overview

Webhooks let Truedy push events to your server in real time when important things happen — a call ends, a batch campaign completes, a voice clone finishes training. Instead of polling the API every few seconds, your server gets notified the moment the event occurs.

When to use webhooks

Use caseWebhook to use
Update your CRM when a call completescall.ended
Send a follow-up email after an outbound callcall.ended
Trigger a workflow when a campaign finishesbatch.completed
Monitor live call activity in your ops dashboardcall.started, call.joined
Alert on call failurescall.failed
Enable a voice once training is donevoice.training.completed
Without webhooks you’d need to poll the API constantly — webhooks are more reliable, cheaper, and real-time.

How it works

1

You create a webhook endpoint in your app

A publicly accessible HTTPS URL that accepts POST requests with a JSON body.
2

You register the endpoint in Truedy

Dashboard → Settings → Webhooks → Add Endpoint. Paste your URL and choose which event types to receive.
3

An event occurs

A call ends, a campaign completes, etc.
4

Truedy dispatches the event

Truedy bundles the event data, signs it with HMAC-SHA256, and sends an HTTP POST to your URL.
5

Your server processes the event

Verify the signature, extract the event type and data, run your business logic.
6

Return 204 quickly

Truedy expects a 2xx response. If it doesn’t receive one within the timeout window, it may re-deliver the event. Return 204 immediately — process asynchronously if needed.

Minimal working receiver

Here is a minimal webhook handler to get you started:
import express from 'express'
import crypto from 'crypto'

const app = express()
const SECRET = process.env.TRUEDY_WEBHOOK_SECRET!

// express.raw() preserves the exact bytes needed for HMAC verification
app.post('/webhooks/truedy', express.raw({ type: 'application/json' }), (req, res) => {
  const rawBody = req.body as Buffer
  const timestamp = req.headers['x-truedy-timestamp'] as string
  const signature = req.headers['x-truedy-signature'] as string

  // 1. Verify signature
  const msg = `${timestamp}.${rawBody.toString('utf8')}`
  const expected = crypto.createHmac('sha256', SECRET).update(msg).digest('hex')
  const now = Math.floor(Date.now() / 1000)
  const ts = parseInt(timestamp, 10)

  if (isNaN(ts) || now - ts > 300 || !crypto.timingSafeEqual(Buffer.from(expected, 'hex'), Buffer.from(signature, 'hex'))) {
    return res.status(401).json({ error: 'Invalid signature' })
  }

  // 2. Parse and handle
  const event = JSON.parse(rawBody.toString('utf8'))
  console.log('Received:', event.event)

  switch (event.event) {
    case 'call.ended':
      // event.data.call.callId, event.data.call.duration, event.data.call.summary
      break
    case 'batch.completed':
      // event.data.batch_id
      break
  }

  res.status(204).send()
})

app.listen(3000)

Event payload structure

Every event Truedy sends has this envelope:
{
  "event": "call.ended",
  "timestamp": "2026-03-18T14:32:00.000Z",
  "data": {
    "call": {
      "callId": "uv_call_abc123",
      "agent": { "agentId": "uv_agent_xyz" },
      "duration": 142,
      "costUsd": 0.21,
      "summary": "Caller asked about pricing, shown the Pro plan.",
      "endReason": "completed"
    }
  }
}
FieldDescription
eventEvent type string, e.g. call.ended
timestampISO 8601 timestamp when the event was dispatched
dataEvent-specific payload — structure varies by event type
See Available Webhooks for the full catalogue of event types and their payload shapes.

Reliability guarantees

Truedy delivers webhooks at-least-once. Your receiver must be idempotent — the same event may arrive more than once if a delivery fails or times out.
To handle duplicates safely:
  1. Extract a dedupe key from the payload (data.call.callId, data.batch_id, etc.)
  2. Before processing, check whether that key is already in your database
  3. If yes: return 204 immediately
  4. If no: process, then record the key atomically
Full runbook in Webhook Error Handling & Retries.

Event families

FamilyEvents
Callscall.started, call.joined, call.ended, call.failed, call.billed
Batch/Campaignsbatch.status.changed, batch.completed
Voice trainingvoice.training.completed, voice.training.failed

Next steps

Available Webhooks

Complete event catalogue with payload shapes

Securing Webhooks

Node.js, Python & Flask HMAC verification examples

Error Handling & Retries

Idempotent receiver implementation with DB example

Idempotency

Platform-wide idempotency patterns