Agent skill

CloudTalk Webhooks Skill

Receive and verify CloudTalk webhooks (cloud call center). Use when setting up CloudTalk webhook handlers, debugging Svix signature verification (svix-id, svix-timestamp, svix-signature, whsec_ secret), deduplicating on event_id, or handling events like call.started, call.answered, call.ended, call.recording_ready, contact.created, message.received, transcript.ready and user.status_changed.

Install this skill

npx skills add hookdeck/webhook-skills --skill cloudtalk-webhooks


CloudTalk is a cloud call center. Its account-level webhooks (Settings → Webhooks in the Dashboard) push call, contact, user, message, Conversation Intelligence, AI Voice Agent and Dialer events to your HTTPS endpoint as JSON. They are signed with the Svix scheme.

When to Use This Skill

  • How do I receive CloudTalk webhooks?
  • How do I verify a CloudTalk webhook signature (svix-signature)?
  • How do I handle call.started, call.answered and call.ended events?
  • How do I get a call recording after call.ended (call.recording_ready)?
  • Why is my CloudTalk webhook signature verification failing?
  • Why am I processing the same CloudTalk event twice?

Verification (core)

HeaderValue
svix-idDelivery id. Same on every retry of the same event
svix-timestampUnix seconds when the request was sent
svix-signatureSpace-delimited list of v1,<base64> signatures

Signed content is {svix-id}.{svix-timestamp}.{raw body}, HMAC-SHA256, base64. The key is the base64-decoded part of the secret after whsec_. The svix library (npm and PyPI) does this, enforces a 5-minute timestamp tolerance, and compares in constant time. Hookdeck retries and replays resend the original svix-timestamp, so the library rejects them once they are more than 5 minutes old. If you receive through Hookdeck and need late retries to pass, use the manual check in references/verification.md and drop or widen its timestamp window.

import { Webhook } from 'svix'; // svix 2.x is ESM-only

// rawBody: the exact bytes received (express.raw({ type: '*/*' }) / request.text()), NOT re-serialised JSON
function verifyCloudTalkWebhook(rawBody, headers) {
  const secret = process.env.CLOUDTALK_WEBHOOK_SECRET; // whsec_...
  if (!secret) throw new Error('CLOUDTALK_WEBHOOK_SECRET not set'); // fail closed → 500
  new Webhook(secret).verify(rawBody, {
    'svix-id': headers['svix-id'],
    'svix-timestamp': headers['svix-timestamp'],
    'svix-signature': headers['svix-signature'],
  }); // throws WebhookVerificationError → 400
  return JSON.parse(rawBody.toString('utf8')); // svix 2.x verify() returns nothing
}

Python: from svix.webhooks import Webhook, then Webhook(secret).verify(raw_body, headers) followed by json.loads(raw_body).

svix 2.x gotcha: verify() no longer returns the parsed payload (JS since 2.2, Python since 2.0). CloudTalk's docs show event = wh.verify(...), which gives undefined/None on current svix. Verify, then parse the raw body yourself.

Manual verification (no SDK) is in references/verification.md.

For complete handlers with tests, see examples/express/, examples/nextjs/, examples/fastapi/.

What Is Not Signed This Way

Only the account-level webhooks from Settings → Webhooks carry these headers. The Call Flow Designer Webhook step, an AI Voice Agent's Send via Webhook option and Workflow Automation actions send their own payloads over their own delivery path. They are not signed with this scheme, and the retry, replay and retention behaviour here does not apply to them. Authenticate those the way their own setup screen describes, and don't point them at a handler that requires svix-* headers.

Event Envelope and Deduplication

Every delivery has the same envelope; only data differs. Route on type, and ignore types you don't recognize because new ones are added over time.

{ "event_id": "3f9a2b4c-8d1e-8a2b-9c3d-4e5f60718293", "type": "contact.created", "version": "v1",
  "occurred_at": "2026-08-17T09:34:21.512Z", "company_id": "123456",
  "data": { "id": "44210", "name": "Jane Doe", "phones": ["+12025550143"] } }

Delivery is at-least-once, so de-duplicate on event_id (same on every retry; claim it before doing the work, and keep it for 30 days because Resend and Recover replay that far back). Order is not guaranteed: sort by occurred_at.

Common Event Types

EventFired when
call.startedA call begins, before anyone picks up
call.ringingA call rings on a specific user
call.answeredA call is picked up
call.held / call.resumedA call is put on or taken off hold
call.endedA call finishes, with durations, contacts and the handling user
call.recording_readyA recording or voicemail is stored (async, after call.ended)
call.tags_modifiedTags are added to or removed from a call
contact.created / contact.updated / contact.deletedA contact changes (deleted carries the id only)
user.created / user.updated / user.deleted / user.status_changedTeam changes and user availability
message.sent / message.receivedAn SMS, MMS or WhatsApp message is sent or arrives
transcript.ready / cidata.readyTranscript, or AI analysis (summary, sentiment, topics, score), is ready
voice_agent.extracted_data_readyAn AI Voice Agent hands over extracted data
dialer.disposition_created / dialer.survey_filledDialer campaign disposition or survey response

See references/overview.md for payload details.

Delivery Semantics

  • Return a 2xx within 15 seconds. 3xx redirects are not followed and count as failures. Verify, enqueue, return 200, process asynchronously.
  • Retries: up to 50 over about 11.5 hours (5s, 30s, 2m, 5m, 10m, then every 15m). An endpoint failing continuously for about 5 days is disabled, then re-enabled automatically if it recovers within 12 hours.
  • Payloads are retained for 30 days for Resend and Recover in the delivery log.

Environment Variables

CLOUDTALK_WEBHOOK_SECRET=whsec_xxxxx  # Settings > Webhooks > the endpoint > signing secret (one per endpoint)

Local Development

# Express / Next.js (use 8000 for FastAPI). No install or account required.
npx hookdeck-cli listen 3000 cloudtalk --path /webhooks/cloudtalk

Add the printed URL as an endpoint under Settings → Webhooks, then use Send test event.

Reference Materials


Repository

hookdeck/webhook-skills

v0.1.0 · MIT · Updated Nov 3, 2026

View on GitHub →