> ## Documentation Index
> Fetch the complete documentation index at: https://docs.avoca.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Event Payloads

> Payload schemas for each webhook event type

All webhook payloads share a common envelope:

```json theme={null}
{
  "event": "lead.created",
  "eventId": "5e0f2a9c-6d3a-4a6e-9d1e-3f8a2b7c4d5e",
  "deliveredAt": "2026-05-01T16:04:22.117Z",
  "teamId": 808
}
```

`deliveredAt` is regenerated on each retry attempt; `eventId` is stable across retries — use it for idempotency.

## voice\_call.completed

Fires after a call ends and has been classified. `transcript` and `recordingUrl` are included only when enabled on the webhook configuration.

```json theme={null}
{
  "event": "voice_call.completed",
  "eventId": "…",
  "deliveredAt": "…",
  "teamId": 808,
  "call": {
    "id": "1f0e8a2b-…",
    "timestamp": "2026-05-01T16:00:12.000Z",
    "callerId": "+19175551234",
    "phoneNumber": "+19175551234",
    "callerFirstName": "John",
    "callerLastName": "Doe",
    "bookingOutcome": "booked",
    "declineReason": null,
    "agentHandledBy": "chloe",
    "aiOrHuman": "ai",
    "callType": "Inbound",
    "callReason": "Service request",
    "callOutcome": "Appointment booked",
    "callEndedReason": "customer-ended-call",
    "durationSeconds": 245,
    "isBooked": true,
    "isBookable": true,
    "isTransferred": false,
    "forwardedPhoneNumber": null,
    "serviceTitanJobId": 456789,
    "customerId": "1048291",
    "isEmergency": false,
    "transcript": "…",
    "recordingUrl": "https://…"
  }
}
```

Key fields:

* `bookingOutcome` — `booked` | `declined` | `transferred` | `unknown`
* `agentHandledBy` — `chloe` (AI) | `human` | `unknown`
* `isEmergency` — emergency classification from the AI or booking-window flag
* `serviceTitanJobId` / `customerId` — CRM linkage when a booking was made

## lead.created

Fires when a new lead is recorded. Includes the raw inbound webhook body (when the lead arrived via webhook intake) under `rawWebhook`.

```json theme={null}
{
  "event": "lead.created",
  "eventId": "…",
  "deliveredAt": "…",
  "teamId": 808,
  "lead": {
    "id": 12345,
    "source": "Angi",
    "customerName": "John Doe",
    "phoneNumber": "+11234567890",
    "email": "john@example.com",
    "address": "123 Main St, Austin, TX 78701",
    "externalId": "abc-123",
    "createdAt": "2026-01-15T10:30:00Z"
  },
  "rawWebhook": { "...": "original third-party payload" }
}
```

## lead.booking\_created

Fires when a booking is created for a lead — once per distinct CRM job. Multiple appointments booked in one conversation emit multiple events. For native-channel bookings (Google LSA, Yelp, web chat), `campaign` is `null`.

```json theme={null}
{
  "event": "lead.booking_created",
  "eventId": "…",
  "deliveredAt": "…",
  "teamId": 808,
  "lead": { "id": 12345, "source": "Angi", "customerName": "John Doe", "phoneNumber": "+11234567890", "email": null, "address": null, "externalId": "abc-123", "createdAt": "2026-01-15T10:30:00Z" },
  "booking": {
    "crmJobId": "98765",
    "bookedAt": "2026-01-15T10:35:00Z",
    "bookedBy": "ai",
    "serviceType": "HVAC Repair",
    "businessUnitId": 42,
    "serviceAreaId": 7,
    "crmCustomerId": "1048291"
  },
  "campaign": {
    "id": 314,
    "name": "Speed to Lead — Angi",
    "workflowType": "SPEED_TO_LEAD"
  }
}
```

* `booking.bookedBy` — `ai` | `human` | `self_service` | `unknown`
* `booking.crmJobId` can be `null` when the event fires before the CRM job ID is recorded

## lead.completed

Fires once per lead when it reaches the true end of its lifecycle — either its campaign finished, or it terminated at intake without ever entering a campaign.

```json theme={null}
{
  "event": "lead.completed",
  "eventId": "…",
  "deliveredAt": "…",
  "teamId": 808,
  "lead": { "id": 12345, "source": "Angi", "customerName": "John Doe", "phoneNumber": "+11234567890", "email": null, "address": null, "externalId": "abc-123", "createdAt": "2026-01-15T10:30:00Z" },
  "completion": {
    "outcome": "booked",
    "completedAt": "2026-01-15T11:40:00Z",
    "crmJobId": "98765",
    "bookingsCount": 1,
    "errorMessage": null,
    "notEnrolledReason": null
  }
}
```

* `outcome` — `booked` | `unbooked` | `opted_out` | `failed`
* `bookingsCount` — currently reports 0 or 1; derive exact counts from `lead.booking_created` events
* `errorMessage` — set only when `outcome` is `failed`
* `notEnrolledReason` — set only when the lead never entered a campaign

## customer.communication\_preference\_changed

Fires when a customer opts out (STOP) or back in (START) of SMS. Currently SMS-only; `email` and `voice` channels are reserved for future use.

```json theme={null}
{
  "event": "customer.communication_preference_changed",
  "eventId": "…",
  "deliveredAt": "…",
  "teamId": 808,
  "change": {
    "channel": "sms",
    "status": "opted_out",
    "phoneNumber": "+19175551234",
    "email": null,
    "customerId": "1048291",
    "source": "inbound_sms",
    "occurredAt": "2026-05-02T09:11:05.882Z",
    "category": "STOP"
  }
}
```
