Skip to main content
All webhook payloads share a common envelope:
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.
Key fields:
  • bookingOutcomebooked | declined | transferred | unknown
  • agentHandledBychloe (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.

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.
  • booking.bookedByai | 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.
  • outcomebooked | 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.