Agent Studio

Call Status Webhooks

Receive real-time notifications for call lifecycle events

Call Status Webhooks

Call Status Webhooks notify your backend when call lifecycle events occur. Use them for tracking, analytics, billing, and audit logging.

Note: This is different from tool API calls. Call Status Webhooks notify you after events happen (call completed, failed, etc.). For making API calls during tool execution, see Tool Actions.

Overview

┌─────────────────┐                    ┌─────────────────┐
│  Agent Studio   │                    │  Your Backend   │
│                 │                    │                 │
│   Call ends     │                    │                 │
│       │         │  POST /webhooks    │                 │
│       ▼         │ ─────────────────► │  Handle event   │
│  Dispatch       │  {type: "call.     │       │         │
│  webhook        │   completed"...}   │       ▼         │
│                 │                    │  Update DB,     │
│                 │                    │  analytics, etc │
└─────────────────┘                    └─────────────────┘

Event Types

EventWhen FiredUse Case
call.startedCall initiated, waiting for connectionTrack call volume
call.connectedUser connected, agent activeMonitor connection success
call.completedCall ended normallyBilling, analytics, audit
call.failedCall failed to connectAlerting, retry logic
call.disconnectedCall dropped unexpectedlyQuality monitoring
call.timeoutCall timed outQuality monitoring
call.agent.changedAgent handoff occurredWorkflow analytics

Webhook Payload

Every webhook request includes:

{
  "id": "evt_abc123",
  "object": "event",
  "api_version": "2026-01-19",
  "created": 1705680000,
  "type": "call.completed",
  "tenant_id": "tenant-uuid",
  "livemode": true,
  "data": {
    "call_id": "call-uuid",
    "room_name": "room-abc",
    "workflow_slug": "daily-checkup",
    "user_id": "user-123",
    "status": "completed",
    "duration_seconds": 180,
    "agent_history": ["greeter", "support"],
    "transcript": [
      {"role": "assistant", "content": "Hello!", "agent": "greeter"},
      {"role": "user", "content": "Hi there"}
    ],
    "context": {
      "user.name": "John",
      "workflow.logged_meals": ["Breakfast"]
    },
    "metrics": {
      "duration_seconds": 180,
      "agent_count": 2,
      "message_count": 15
    }
  }
}

Configuring Webhooks

Via Dashboard

  1. Navigate to Settings → Webhooks
  2. Enter your endpoint URL
  3. Select which events to receive
  4. Choose what data to include (transcript, context, metrics)
  5. Save - copy the webhook secret immediately

Via API

curl -X POST https://api.studio.tap.health/api/v1/webhooks \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://your-backend.com/webhooks/agent-studio",
    "filter": {
      "events": ["call.completed", "call.failed"],
      "include_transcript": true,
      "include_context": false,
      "include_metrics": true
    },
    "headers": {
      "X-Custom-Header": "value"
    },
    "timeout": 30,
    "max_retries": 5
  }'

Response includes the signing secret:

{
  "id": "whk_abc123",
  "url": "https://your-backend.com/webhooks/agent-studio",
  "secret": "whsec_abc123def456...",
  "enabled": true,
  "filter": {...}
}

Important: Save the secret immediately - it's only shown once!

Configuration Options

FieldDescriptionDefault
urlYour webhook endpoint URLRequired
filter.eventsArray of event types to receiveAll events
filter.include_transcriptInclude full conversation transcriptfalse
filter.include_contextInclude call context (user data, workflow state)false
filter.include_metricsInclude call metrics (duration, message count)true
headersCustom headers to include in requests{}
timeoutRequest timeout in seconds30
max_retriesNumber of retry attempts5

Security: Signature Verification

Every webhook request includes an HMAC-SHA256 signature in the X-Webhook-Signature header:

X-Webhook-Signature: t=1705680000,v1=abc123def456...

Always verify signatures to prevent spoofing attacks.

Verification Example (Python)

import hmac
import hashlib
import time

WEBHOOK_SECRET = "whsec_your_secret_here"

def verify_signature(payload: bytes, signature_header: str) -> bool:
    """Verify webhook signature."""
    try:
        parts = dict(p.split("=") for p in signature_header.split(","))
        timestamp = int(parts["t"])
        signature = parts["v1"]
    except (KeyError, ValueError):
        return False
    
    # Reject old timestamps (prevent replay attacks)
    if abs(time.time() - timestamp) > 300:  # 5 minute tolerance
        return False
    
    # Compute expected signature
    signed_payload = f"{timestamp}.{payload.decode()}"
    expected = hmac.new(
        WEBHOOK_SECRET.encode(),
        signed_payload.encode(),
        hashlib.sha256,
    ).hexdigest()
    
    return hmac.compare_digest(signature, expected)

Verification Example (Node.js)

const crypto = require('crypto');

const WEBHOOK_SECRET = 'whsec_your_secret_here';

function verifySignature(payload, signatureHeader) {
  const parts = Object.fromEntries(
    signatureHeader.split(',').map(p => p.split('='))
  );
  
  const timestamp = parseInt(parts.t);
  const signature = parts.v1;
  
  // Check timestamp (5 minute tolerance)
  if (Math.abs(Date.now() / 1000 - timestamp) > 300) {
    return false;
  }
  
  // Compute expected signature
  const signedPayload = `${timestamp}.${payload}`;
  const expected = crypto
    .createHmac('sha256', WEBHOOK_SECRET)
    .update(signedPayload)
    .digest('hex');
  
  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expected)
  );
}

Delivery & Retries

Retry Behavior

Failed deliveries are retried with exponential backoff:

AttemptDelay
1Immediate
21 second
32 seconds
44 seconds
58 seconds

Success criteria: HTTP 2xx response within timeout.

What Triggers Retries

  • Connection errors
  • Timeout (default 30s)
  • HTTP 5xx responses
  • HTTP 429 (rate limited)

Not retried: HTTP 4xx responses (except 429) indicate client errors.

Handling Webhooks

FastAPI Example

from fastapi import FastAPI, Request, HTTPException
import json

app = FastAPI()

@app.post("/webhooks/agent-studio")
async def handle_webhook(request: Request):
    body = await request.body()
    signature = request.headers.get("X-Webhook-Signature", "")
    
    if not verify_signature(body, signature):
        raise HTTPException(status_code=401, detail="Invalid signature")
    
    event = json.loads(body)
    event_type = event["type"]
    data = event["data"]
    
    if event_type == "call.completed":
        # Log for analytics
        print(f"Call {data['call_id']} completed")
        print(f"  User: {data['user_id']}")
        print(f"  Duration: {data['duration_seconds']}s")
        print(f"  Workflow: {data['workflow_slug']}")
        
        # Store for billing/analytics
        # await record_call(data)
        
    elif event_type == "call.failed":
        # Alert on failures
        print(f"Call {data['call_id']} failed: {data.get('status_reason')}")
        # await alert_ops_team(data)
    
    return {"status": "received"}

Express.js Example

const express = require('express');
const app = express();

app.post('/webhooks/agent-studio', express.raw({type: '*/*'}), (req, res) => {
  const signature = req.headers['x-webhook-signature'];
  
  if (!verifySignature(req.body.toString(), signature)) {
    return res.status(401).send('Invalid signature');
  }
  
  const event = JSON.parse(req.body);
  
  switch (event.type) {
    case 'call.completed':
      console.log(`Call ${event.data.call_id} completed`);
      // Handle completion
      break;
    case 'call.failed':
      console.log(`Call ${event.data.call_id} failed`);
      // Handle failure
      break;
  }
  
  res.json({ status: 'received' });
});

Managing Webhooks

API Endpoints

MethodEndpointDescription
GET/api/v1/webhooksGet current configuration
POST/api/v1/webhooksCreate/replace (returns new secret)
PATCH/api/v1/webhooksUpdate (keeps existing secret)
DELETE/api/v1/webhooksDelete configuration
POST/api/v1/webhooks/testSend test event
POST/api/v1/webhooks/enableEnable webhook
POST/api/v1/webhooks/disableDisable webhook
POST/api/v1/webhooks/rotate-secretGenerate new signing secret

Testing Your Endpoint

Send a test event to verify your endpoint works:

curl -X POST https://api.studio.tap.health/api/v1/webhooks/test \
  -H "Authorization: Bearer $API_KEY"

This sends a call.test event to your configured URL.

Rotating Secrets

If your secret is compromised:

curl -X POST https://api.studio.tap.health/api/v1/webhooks/rotate-secret \
  -H "Authorization: Bearer $API_KEY"

Important: Update your verification code immediately after rotation.

Best Practices

  1. Always verify signatures - Never process unverified webhooks
  2. Handle idempotency - Webhooks may be delivered multiple times; use event.id to dedupe
  3. Respond quickly - Return 2xx within timeout; process asynchronously if needed
  4. Store the secret securely - Use environment variables or secret managers
  5. Monitor delivery - Check dashboard for failed deliveries
  6. Use HTTPS - Production endpoints must use HTTPS

Troubleshooting

Webhooks Not Arriving

  1. Check webhook is enabled in dashboard
  2. Verify endpoint URL is correct and publicly accessible
  3. Check firewall allows inbound HTTPS from Agent Studio
  4. Review delivery logs in dashboard

Signature Verification Failing

  1. Ensure you're using the correct secret
  2. Check timestamp tolerance (default 5 minutes)
  3. Verify payload isn't being modified by middleware
  4. Use raw body bytes, not parsed JSON

Retries Happening

  1. Ensure endpoint returns 2xx status
  2. Check endpoint responds within timeout
  3. Review application logs for errors

On this page