Trillet AI logo

Events and webhooks

Signed webhooks for everything that happens across your tenants, with retries, replay and an event log.

Coming soon. The Voice Engine API is in development. This page describes the planned design; details may change before release.

Register one or more webhook endpoints on your partner account. Each receives the events you subscribe to, from all of your tenants, signed so you can verify they came from Trillet.

Endpoints

MethodPathPurpose
POST/partner/v1/webhook-endpointsAdd an endpoint
GET / PATCH / DELETE/partner/v1/webhook-endpoints/{id}Read, change or remove it
POST/partner/v1/webhook-endpoints/{id}/rotate-secretRotate its signing secret
POST/partner/v1/webhook-endpoints/{id}/testSend a test event
GET/partner/v1/eventsEvent log for the last 30 days
POST/partner/v1/events/{id}/redeliverSend an event again
curl -X POST https://api.trillet.ai/partner/v1/webhook-endpoints \
  -H "Authorization: Bearer tpk_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://api.example.com/trillet/events",
    "eventTypes": ["call.ended", "call.analyzed", "booking.created"],
    "includeConversationData": true
  }'

Event shape

{
  "id": "evt_01JC9V8K2T",
  "type": "call.ended",
  "apiVersion": "2026-11-01",
  "occurredAt": "2026-11-02T15:04:11Z",
  "livemode": true,
  "tenant": { "id": "ten_6a9622d2", "externalId": "cust_8842", "metadata": { "plan": "pro" } },
  "data": {
    "callId": "call_8f2a",
    "direction": "inbound",
    "from": "+15125550177",
    "to": "+15125550142",
    "startedAt": "2026-11-02T15:01:40Z",
    "endedAt": "2026-11-02T15:04:11Z",
    "billedSeconds": 151,
    "outcome": "booked",
    "endedBy": "agent",
    "summary": "Caller booked a cleaning for Thursday 3pm.",
    "cost": { "retail": 0.38, "wholesale": 0.23, "currency": "USD" }
  }
}

Event types

TypeWhen
call.startedA call is connected to an agent (inbound, outbound or web)
call.endedA call ends. Outcome, duration, cost and summary.
call.analyzedPost-call analysis is done. Transcript and extracted data.
call.no_answer, call.failed, call.blockedA call did not connect, failed, or was blocked (for example, tenant suspended)
call.transferred, call.transfer_failedA transfer to a person happened or failed
booking.created, booking.cancelled, booking.rescheduledThe agent changed an appointment, from any calendar or practice system integration
tool.failedA custom tool or API call during a call returned an error
recording.availableThe call recording is ready (a signed, expiring URL)
sms.received, sms.sent, sms.failedText messages
workflow_run.completed, workflow_run.failedWorkflow runs
number.provisioned, number.releasedPhone numbers
tenant.created, tenant.suspended, tenant.resumed, tenant.deleted, tenant.provisioning_failedTenant lifecycle
rollout.completed, rollout.pausedTemplate rollouts
usage.threshold_reachedA tenant or your account reached 80% or 100% of a spend cap

New event types may be added at any time. Ignore types you do not handle.

Conversation data

Transcripts, summaries and extracted data are included by default. Turn them off for an endpoint with includeConversationData: false, or set redactPII: true to mask phone numbers, emails and similar values. Recordings are never sent in the payload, only as a signed URL that expires.

Verifying signatures

Events are signed using the Standard Webhooks format, so you can use any of its libraries. Each request carries three headers:

  • webhook-id: the event ID, also useful for de-duplication
  • webhook-timestamp: Unix seconds
  • webhook-signature: v1,<base64 HMAC-SHA256> of {webhook-id}.{webhook-timestamp}.{raw body}
import { Webhook } from 'standardwebhooks';

const wh = new Webhook(process.env.TRILLET_WEBHOOK_SECRET);

app.post('/trillet/events', express.raw({ type: 'application/json' }), (req, res) => {
  let event;
  try {
    event = wh.verify(req.body, req.headers);
  } catch {
    return res.status(400).send('invalid signature');
  }
  handle(event); // de-duplicate on event.id
  res.sendStatus(204);
});

Verify against the raw request body, before any JSON parsing. Reject timestamps more than 5 minutes old. During a secret rotation both the old and new signatures are sent for 24 hours.

Delivery and retries

  • Respond with any 2xx within 10 seconds. Do slow work after responding.
  • Failed deliveries are retried with backoff for about 24 hours.
  • Events can arrive more than once and out of order. De-duplicate on id and use occurredAt for ordering.
  • An endpoint that fails for 3 days is disabled and your account contact is emailed. Re-enable it and use the event log to redeliver what you missed.