Cumulus TalosDocs
Open Talos
On this page

Triggers and webhooks

Webhooks tell your systems what a run did; triggers start runs without an API call. Both are part of the agent's definition: you add them to a draft and they take effect when that version is published.

Webhooks

A webhook sends chosen run events to your HTTPS endpoint, signed with a secret you create. Add it with the notification.put draft edit and publish. The events you can subscribe to: run.admitted, run.ended, recording.updated, analysis.updated.

Add a webhook

Example
# 1. Create the signing secret (admin key). Keep the value: your receiver needs it.
PUT /api/v1/secrets/crm-webhook      { "kind": "webhook_signing", "value": "<a long random string>" }
#    → { "id": "crm-webhook", "current_version": "<version>", … }

# 2. Add the webhook to the agent's draft, then publish.
PATCH /api/v1/agents/{agent_id}/draft
{ "draft_id": "…", "expected_revision": 7, "edits": [
  { "type": "notification.put", "notification": {
      "id": "crm", "url": "https://hooks.example.com/talos", "format": "semantic.v1",
      "secret_ref": { "id": "crm-webhook", "version": "<version>" },
      "events": ["run.ended", "analysis.updated"] } } ] }

Delivery

The body is the event exactly as the run's event stream carries it (format semantic.v1).

What your endpoint receives

Example
POST https://hooks.example.com/talos
content-type: application/json
x-talos-event-id: 3f6c…            # the event's id; the same on every retry
idempotency-key: 3f6c…             # the same value: deduplicate on it
x-talos-signature: t=1790380800,v1=5d41402abc4b2a76b9719d911017c592…

{ "event_id": "3f6c…", "type": "run.ended", "sequence": 14, "run": { "run_id": "…", … }, "data": { "outcome": { … }, … }, … }
  • Answer 2xx to accept. A 408, 429, 5xx or no answer within 10 seconds is retried, up to 11 attempts; any other 4xx is final.
  • A retry can arrive after a success you sent but Talos did not receive: deduplicate on idempotency-key.
  • Each attempt is signed again, with its own timestamp. Deliveries are governed effects: see them with GET /api/v1/runs/{run_id}/effects.
  • Test runs ("test": true) are never delivered.

Verify the signature

x-talos-signature is t=<unix seconds>,v1=<hex HMAC-SHA256> of <t>.<raw body> with your signing secret. Compute it over the raw bytes you received (before parsing the JSON), compare in constant time, and reject old timestamps; five minutes is a reasonable window.

import { createHmac, timingSafeEqual } from "node:crypto";

/** True when the body was signed with your webhook signing secret within the last five minutes. */
export function verifyTalosWebhook(rawBody: string, signatureHeader: string, secret: string): boolean {
  const parts = Object.fromEntries(signatureHeader.split(",").map(part => part.split("=", 2) as [string, string]));
  // t is unix seconds; v1 is exactly 64 lowercase hex characters (Buffer.from would skip trailing junk).
  if (!/^\d{1,12}$/.test(parts.t ?? "") || !/^[0-9a-f]{64}$/.test(parts.v1 ?? "")) return false;
  if (Math.abs(Date.now() / 1000 - Number(parts.t)) > 300) return false;
  const expected = createHmac("sha256", secret).update(`${parts.t}.${rawBody}`).digest();
  return timingSafeEqual(Buffer.from(parts.v1, "hex"), expected);
}

Note

Publications pin a secret version. After you rotate a secret with PUT /api/v1/secrets/{secret_id}, update the draft's secret_ref and publish again.

Triggers

A trigger starts a background cloud run of the agent it belongs to:

  • schedule: a five-field cron expression in a time zone, with a fixed input.
  • event: when a run matching source (an agent, a channel, dispositions) emits run.ended or analysis.updated. input_mapping builds the new run's input from JSON pointers into that event.

Draft edits

Example
# Every weekday at 9:00 in New York, start a background run with this input.
{ "type": "trigger.put", "trigger": {
    "kind": "schedule", "id": "morning-sweep", "cron": "0 9 * * 1-5", "timezone": "America/New_York",
    "input": { "segment": "overdue" } } }

# When a phone run of another agent ends as voicemail, start a run of this agent with fields from that event.
{ "type": "trigger.put", "trigger": {
    "kind": "event", "id": "after-voicemail", "event_type": "run.ended",
    "source": { "agent_id": "<caller agent id>", "channel": "phone", "dispositions": ["voicemail_left"] },
    "input_mapping": { "previous_run_id": "/run/run_id" } } }
  • Only the head's triggers are active. Publishing with "live": false does not register them; promoting that version does, and replaces the previous head's triggers.
  • Publishing a trigger checks that you may start those runs; if not, 403 policy_denied.
  • Test runs never fire event triggers. Remove a trigger with trigger.remove and publish.

Secrets

Secrets hold the values agents must not carry in their definition: webhook signing keys, HTTP action credentials and SIP passwords (kinds webhook_signing, http_auth, sip_auth). Values are write-only: reads return names and versions, never values. Each PUT adds a version; drafts reference a secret as { id, version }. Writing and deleting secrets takes an admin key. See Phone for trunk credentials.

OperationMCP toolKey rolePurpose
GET /api/v1/secretssecrets_listviewerList secret metadata (never values)
PUT /api/v1/secrets/{secret_id}secrets_putadminCreate a secret or add a new version
DELETE /api/v1/secrets/{secret_id}secrets_deleteadminDelete a secret and all its versions
Content version 2699866eMarkdown source
Triggers and webhooks · Cumulus Talos docs