# Webhook API Reference

> Complete reference for webhook events, payloads, and integration.

Source: https://edesy.in/docs/voice-agent/api/webhooks

Technical reference for all webhook events sent by the voice agent platform.

> **Two different mechanisms — make sure you're reading the right one.**
>
> | | This page | [Call-ended callback](https://edesy.in/docs/voice-agent/api/calls#call-ended-callback) |
> |---|---|---|
> | Configured | Per workspace / agent subscription | `callbackUrl` on a single `POST /api/v1/calls` |
> | Signed | Yes, HMAC-SHA256 | No |
> | Retried | Yes | No — single delivery |
> | Events | `call.started`, `call.ended`, `transcript.updated`, `function.called`, … | `call.completed`, `call.failed`, `call.voicemail` only |
>
> If you passed `callbackUrl` when placing a call, the payload you receive is the one
> documented on the [Calls page](https://edesy.in/docs/voice-agent/api/calls#call-ended-callback), not
> the events below.

## Webhook Delivery

### Request Format

```http
POST {your_webhook_url}
Content-Type: application/json
X-Webhook-Signature: sha256=abc123...
X-Webhook-Event: call.ended
X-Webhook-Timestamp: 1704067200
```

### Signature Verification

```go
func verifyWebhook(payload []byte, signature, secret string) bool {
    mac := hmac.New(sha256.New, []byte(secret))
    mac.Write(payload)
    expected := "sha256=" + hex.EncodeToString(mac.Sum(nil))
    return hmac.Equal([]byte(expected), []byte(signature))
}
```

```python
import hmac
import hashlib

def verify_webhook(payload: bytes, signature: str, secret: str) -> bool:
    expected = "sha256=" + hmac.new(
        secret.encode(), payload, hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(expected, signature)
```

## Event Types

### call.started

Triggered when a call begins.

```json
{
  "event": "call.started",
  "timestamp": "2024-01-01T12:00:00Z",
  "data": {
    "call_id": "call_abc123",
    "agent_id": "agent_xyz",
    "direction": "inbound",
    "from": "+1234567890",
    "to": "+0987654321",
    "provider": "twilio",
    "stream_sid": "MZxxx",
    "call_sid": "CAxxx",
    "workspace_id": "ws_123"
  }
}
```

### call.ended

Triggered when a call completes.

```json
{
  "event": "call.ended",
  "timestamp": "2024-01-01T12:05:00Z",
  "data": {
    "call_id": "call_abc123",
    "agent_id": "agent_xyz",
    "duration_seconds": 300,
    "end_reason": "user_hangup",
    "disposition": "success",
    "transcript": {
      "summary": "Customer inquired about order status...",
      "turns": [
        {
          "role": "assistant",
          "content": "Hello! How can I help you today?",
          "timestamp": "2024-01-01T12:00:01Z"
        },
        {
          "role": "user",
          "content": "I want to check my order status",
          "timestamp": "2024-01-01T12:00:05Z"
        }
      ]
    },
    "metadata": {
      "functions_called": ["get_order_status"],
      "transfer_occurred": false,
      "recording_url": "https://..."
    }
  }
}
```

**End Reasons:**
| Reason | Description |
|--------|-------------|
| `user_hangup` | User ended the call |
| `agent_hangup` | Agent completed and ended |
| `transfer` | Call was transferred |
| `error` | Call ended due to error |
| `timeout` | No activity timeout |
| `max_duration` | Maximum call duration reached |

### call.failed

Triggered when an outbound call fails to connect (busy, no-answer, rejected).

```json
{
  "event": "call.failed",
  "timestamp": "2024-01-01T12:00:30Z",
  "data": {
    "call_id": "call_abc123",
    "agent_id": "agent_xyz",
    "to": "+1234567890",
    "from": "+0987654321",
    "provider": "twilio",
    "failure_reason": "busy",
    "failure_code": "3010",
    "failure_description": "Busy Line",
    "hangup_source": "Carrier",
    "workspace_id": "ws_123"
  }
}
```

**Failure Reasons:**
| Reason | Description |
|--------|-------------|
| `busy` | Recipient's line is busy |
| `no-answer` | Call was not answered |
| `rejected` | Call was declined by recipient |
| `canceled` | Call was canceled before connecting |
| `failed` | General failure (network issue) |
| `unallocated` | Phone number does not exist |

### call.transferred

Triggered when a call is transferred.

```json
{
  "event": "call.transferred",
  "timestamp": "2024-01-01T12:03:00Z",
  "data": {
    "call_id": "call_abc123",
    "transfer_to": "+1555123456",
    "transfer_type": "blind",
    "reason": "customer_request",
    "context": "Customer requested to speak with a manager"
  }
}
```

### transcript.updated

Real-time transcript updates during the call.

```json
{
  "event": "transcript.updated",
  "timestamp": "2024-01-01T12:01:30Z",
  "data": {
    "call_id": "call_abc123",
    "turn": {
      "role": "user",
      "content": "I want to check my order ORD12345",
      "timestamp": "2024-01-01T12:01:30Z",
      "is_final": true,
      "confidence": 0.95
    },
    "sequence_number": 5
  }
}
```

### function.called

Triggered when a function/tool is executed.

```json
{
  "event": "function.called",
  "timestamp": "2024-01-01T12:01:35Z",
  "data": {
    "call_id": "call_abc123",
    "function": {
      "name": "get_order_status",
      "parameters": {
        "order_id": "ORD12345"
      },
      "result": {
        "status": "shipped",
        "delivery_date": "2024-01-03"
      },
      "duration_ms": 150
    }
  }
}
```

### error.occurred

Triggered when an error occurs during the call.

```json
{
  "event": "error.occurred",
  "timestamp": "2024-01-01T12:02:00Z",
  "data": {
    "call_id": "call_abc123",
    "error": {
      "code": "stt_timeout",
      "message": "Speech-to-text service timed out",
      "severity": "warning",
      "recovered": true
    }
  }
}
```

**Error Codes:**
| Code | Severity | Description |
|------|----------|-------------|
| `stt_timeout` | warning | STT service timeout |
| `stt_error` | error | STT service failure |
| `tts_error` | error | TTS service failure |
| `llm_timeout` | warning | LLM response timeout |
| `llm_error` | error | LLM service failure |
| `function_error` | warning | Tool execution failed |
| `connection_lost` | error | WebSocket disconnected |

### dtmf.received

Triggered when DTMF input is received.

```json
{
  "event": "dtmf.received",
  "timestamp": "2024-01-01T12:01:45Z",
  "data": {
    "call_id": "call_abc123",
    "digits": "12345#",
    "aggregated": true
  }
}
```

## Retry Policy

Failed webhook deliveries are retried with exponential backoff:

| Attempt | Delay |
|---------|-------|
| 1 | Immediate |
| 2 | 1 second |
| 3 | 5 seconds |
| 4 | 30 seconds |
| 5 | 2 minutes |

Webhooks are considered failed if:
- Response status is not 2xx
- Connection timeout (10 seconds)
- Response timeout (30 seconds)

## Best Practices

### 1. Respond Quickly

```go
func handleWebhook(w http.ResponseWriter, r *http.Request) {
    // Acknowledge immediately
    w.WriteHeader(http.StatusOK)

    // Process asynchronously
    go processWebhookAsync(r.Body)
}
```

### 2. Idempotency

```go
func processWebhook(payload WebhookPayload) error {
    // Check if already processed
    if db.WebhookProcessed(payload.CallID, payload.Event) {
        return nil // Already handled
    }

    // Process and mark as done
    err := handleEvent(payload)
    if err == nil {
        db.MarkWebhookProcessed(payload.CallID, payload.Event)
    }
    return err
}
```

### 3. Verify Signatures

Always verify webhook signatures in production to prevent spoofing.

## Testing

### Local Development

Use ngrok or similar to expose local endpoints:

```bash
ngrok http 3000
# Then configure webhook URL: https://abc123.ngrok.io/webhooks
```

### Test Payload

```bash
curl -X POST https://your-server.com/webhooks \
  -H "Content-Type: application/json" \
  -H "X-Webhook-Event: call.ended" \
  -d '{
    "event": "call.ended",
    "timestamp": "2024-01-01T12:00:00Z",
    "data": {
      "call_id": "test_123",
      "duration_seconds": 60,
      "end_reason": "user_hangup"
    }
  }'
```

## Related Resources

- [Finding Your Workspace ID](https://edesy.in/docs/voice-agent/workspace/finding-workspace-id) - Locate your workspace ID for integrations
- [Webhooks Guide](https://edesy.in/docs/voice-agent/features/webhooks) - Configuration and use cases
- [REST API](https://edesy.in/docs/voice-agent/api) - API endpoints
- [SDKs](https://edesy.in/docs/voice-agent/api/sdks) - Client libraries
