
Webhooks for Call Events: Building a Reliable SIP-to-CRM Event Pipeline
A call event pipeline that drops 2% of events looks fine in testing. At 500 calls per day across a 10-agent team, that is 10 missing call records per day — 200 per month — and a pipeline report that cannot be trusted. Here is how to build the event pipeline so it handles failures, retries, and ordering correctly.
The Event Types That Matter for Outbound CRM Integration
A SIP trunk fires events at multiple points in a call's lifecycle. For CRM integration, three events are operationally significant:
Call initiated. Fired when the SIP INVITE leaves the trunk. Contains the call UUID, from number, to number, and initiation timestamp. Useful for creating a "dial attempt" record in the CRM immediately — before the call connects — so that if the connection fails, the attempt is still logged.
Call answered. Fired when the far end sends a SIP 200 OK (the call connected). Contains the call UUID and answer timestamp. The delta between initiation and answer timestamps is ring time. Useful for triggering agent screen pops in the CRM — the agent's contact record should already be on screen before the prospect says "hello."
Call completed. Fired when the call terminates (BYE message exchanged). Contains call UUID, duration, hangup cause, and (in some providers' payloads) a recording URL if recording was enabled. This is the event that writes the final call record to the CRM.
The minimum viable integration uses only the call-completed event. The complete integration uses all three to build a real-time view of each call from first dial to final disposition.
Anatomy of a Reliable Webhook Receiver
A webhook receiver that fails under load or drops events has one of four problems: timeout, no deduplication, no retry handling, or processing in the HTTP handler rather than queuing for async processing.
Timeout. A webhook endpoint must return HTTP 200 within the provider's timeout window — typically 5–10 seconds. If the endpoint is doing database writes, external API calls, or CRM API requests synchronously in the response path, it will occasionally timeout under normal conditions and consistently timeout under load. The fix: respond 200 immediately and queue the event for async processing.
No deduplication. When the webhook endpoint returns a non-2xx status or times out, the provider retries delivery. A call-completed event can arrive 2, 3, or more times for the same call UUID. Without deduplication, the CRM creates multiple call records for the same call. The fix: check the call UUID against a deduplication store on receipt; process only the first occurrence.
No retry handling on the sender side. If the webhook endpoint is down for a 5-minute window, all events that fired during that window are lost — unless the provider queues retries. UnlimCall's webhook delivery includes retry logic with exponential backoff. Events that receive a non-2xx response are retried at 30s, 2m, and 10m intervals. Events that fail all three retry attempts are flagged in the portal delivery log.
Processing in the HTTP handler. If the handler is synchronously writing to a CRM API (which may itself be slow or temporarily unavailable), the handler will miss the response window during any CRM API degradation. Decouple the HTTP response from the processing: accept the event, write it to an internal queue, respond 200, and let a background worker process the queue.
Schema: What the UnlimCall Call Event Payload Looks Like
``json { "event": "call.completed", "call_uuid": "a3f7c291-4b8e-4d2a-9c1f-08d7b3e4f6a2", "from": "+12025551234", "to": "+4930123456789", "direction": "outbound", "initiated_at": "2026-06-04T09:14:22.000Z", "answered_at": "2026-06-04T09:14:29.000Z", "ended_at": "2026-06-04T09:17:45.000Z", "duration_seconds": 196, "hangup_cause": "normal_clearing", "hangup_cause_code": 16, "seat_id": "seat_0481ab", "did_id": "did_de_1092" } ``
The call_uuid is the deduplication key. The seat_id maps to a specific agent credential. The did_id maps to the specific DID used as caller ID. Both IDs are stable references to portal objects.
Full schema documentation is in the custom SIP integration guide.
GoHighLevel-Specific Event Wiring
GoHighLevel's BYOC layer intercepts call events from the connected SIP trunk and translates them into GHL's internal event format before writing to the contact timeline. This means the raw SIP trunk webhook is not the mechanism you configure for GHL — GHL handles the translation internally.
For GHL-specific workflow triggers — "when a call completes with no answer, enroll the contact in re-engagement sequence" — use GHL's native workflow trigger "Outbound Call Status" which surfaces hangup outcomes as GHL workflow conditions. The trigger reads from GHL's internal call record, which was populated from the BYOC event translation.
For data that needs to leave GHL — posting call outcomes to a data warehouse, a custom CRM field not supported by GHL, or a third-party analytics tool — configure GHL's outbound webhook (under Settings > Webhooks) to fire on call completion. This is a second webhook layer, GHL-to-destination, separate from the SIP trunk-to-GHL path.
The GoHighLevel SIP trunk integration guide covers the BYOC event path from trunk to GHL timeline in more detail.
Event Ordering and Out-of-Order Delivery
Webhook events can arrive out of order. In rare cases — usually during network congestion or provider retry scenarios — a call-completed event arrives at the webhook endpoint before the call-initiated event. A CRM handler that expects events in chronological sequence will misprocess the completion record.
The robust approach: treat each event as independent and idempotent. The call-completed event carries all the information needed to create a full call record (UUID, from, to, timestamps, duration, cause). If a call-completed event arrives and no call-initiated record exists in the CRM, create the full record from the completed event payload. Do not block processing on event ordering.
Monitoring the Pipeline
A webhook event pipeline requires its own observability, separate from the CRM's built-in reporting.
Key metrics to track in your own logging layer:
- Events received per hour (volume baseline)
- Events processed successfully per hour (should match received, minus deduplication)
- Events deduped per hour (should be low — spikes indicate provider retry storms)
- Events failed processing per hour (CRM API errors, malformed payloads)
- P95 processing latency (time from webhook receipt to CRM write)
If you do not instrument these metrics, you will not know the pipeline is degraded until an agent or manager reports missing call logs — by which time several hours of data may already be gone.
The portal's webhook delivery log shows the provider-side view: which events fired, which HTTP responses were received, and which events are pending retry. Check this log when investigating discrepancies between dialed calls and CRM call records.
Takeaways
- Three events matter for CRM integration: call initiated, call answered, and call completed. Minimum viable: call completed only.
- Reliable receivers respond 200 immediately and queue for async processing — never process synchronously in the HTTP handler.
- Deduplication on call UUID is non-negotiable; retry delivery makes duplicate events inevitable.
- UnlimCall retries failed deliveries at 30s, 2m, and 10m; the portal delivery log shows per-event status.
- GoHighLevel's BYOC layer handles SIP-to-GHL event translation; configure GHL's outbound webhook for destinations outside GHL.
Instrument the Pipeline Before You Scale
Start with the pricing page for seat provisioning, then review the custom SIP integration guide for the full webhook schema.