Data Engineering7 min read1 April 2026

Webhook-First Architecture: How to Build CRM Automations That Never Break

Most CRM automations are built wrong — they rely on polling, scheduled checks, and manual triggers. Here's why webhook-first architecture is more reliable and how to implement it.

H

Haroon Mohamed

AI Automation & Lead Generation

Why automations break

You've built a GoHighLevel workflow. A lead fills in a form, a sequence fires, a follow-up SMS goes out 3 hours later. It works.

Then it doesn't. A lead submits the form and nothing happens. You check the workflow — it shows as active. You run a test lead — it fires fine. But real leads aren't triggering it.

This is the experience of 90% of CRM automations built without a clear understanding of how data flows between systems.

The root cause: most automations are built around polling — the system checks for new data every few minutes, on a schedule. If the schedule misfires, the check fails, or the external system is slow, leads fall through.

The fix: webhooks — the external system pushes data to your CRM the instant something happens. No waiting. No checking. No gaps.


Polling vs. webhooks: a concrete example

Scenario: A lead submits a Facebook Lead Ad form.

Polling approach:

  1. GoHighLevel's Facebook integration checks Facebook for new leads every 5 minutes
  2. At 2:07pm, a lead submits
  3. GHL's next check runs at 2:10pm — picks up the lead
  4. Total delay: up to 5 minutes
  5. If the check at 2:10pm fails (Facebook API timeout, GHL hiccup): the lead doesn't get picked up until 2:15pm — or ever, if the check runs without error-logging

Webhook approach:

  1. A lead submits at 2:07pm
  2. Facebook pushes the data to your webhook URL at 2:07:03pm
  3. GoHighLevel receives the data and creates the contact immediately
  4. Your AI calling agent fires at 2:07:30pm
  5. If the webhook delivery fails: Facebook retries automatically for 24 hours

The difference isn't just speed — though a 5-minute vs. 30-second contact window matters enormously for lead conversion. It's reliability. Webhooks have automatic retry logic built in. Polling fails silently.


The webhook-first architecture

Here's how I structure every CRM automation build from scratch:

Layer 1: Event sources

Every action that creates a new state in your system should emit a webhook:

  • Form submission (Typeform, Elementor, native GHL form)
  • Ad lead (Facebook, Google, TikTok)
  • Call completed (VAPI, Retell, Bland)
  • Appointment booked (Calendly, GHL calendar)
  • Payment received (Stripe, PayPal)
  • Contract signed (DocuSign, PandaDoc)

If your tools don't natively emit webhooks for these events, check if they can. Almost all SaaS tools built in the last 5 years have webhook support somewhere in their settings.

Layer 2: Webhook receiver

This is where data lands when an event fires. Options:

Option A: GoHighLevel native. GHL has a native webhook trigger in workflows. The GHL webhook URL receives a POST request, and the workflow fires. Simple, reliable, requires no extra tool.

Option B: Zapier/Make. Webhooks by Zapier or Make.com receive the POST, transform the data if needed, and push to GHL (or multiple destinations). Better for complex data transformation.

Option C: Custom endpoint. A Cloudflare Worker, Vercel Edge Function, or AWS Lambda receives the webhook, applies business logic, and dispatches to multiple systems. Most flexible, requires code.

For most CRM setups, Option A or B is sufficient. Option C is for when you need to fan out to 3+ systems simultaneously or apply complex conditional logic.

Layer 3: Data normalisation

Every system sends data in its own format. Facebook lead ads have a different payload structure than Typeform, which is different from VAPI's call webhook.

Before the data hits your CRM, normalise it:

  • Standardise field names (fb uses full_name, VAPI might use caller_name)
  • Format phone numbers to E.164 (+15551234567, not 555-123-4567)
  • Clean up email addresses (lowercase, trim whitespace)
  • Resolve missing fields (if city is missing, infer from phone number area code if needed)

In Zapier, this is done with a "Formatter" step. In Make, it's a "Set variables" module. In code, it's just a mapping function.

Layer 4: CRM write

Once normalised, write to the CRM. Critically:

  • Use "Find or create" logic — don't create duplicate contacts
  • Set the lead source field at time of creation (never lose this data)
  • Tag immediately with campaign context (ad set, audience, landing page)

In GoHighLevel: use the "Upsert Contact" API action, which creates if not found or updates if found, matching on email or phone.

Layer 5: Automation trigger

After the CRM write succeeds, trigger the automation sequence. This is now a clean trigger: "contact created with tag X" or "contact entered pipeline stage Y."

Because the data arrived via webhook and was processed synchronously, this trigger fires within seconds of the original event.


Error handling and observability

This is where most webhook setups fail — not in the happy path, but when something goes wrong.

Mandatory error handling:

  1. Webhook validation. Verify the incoming webhook signature before processing. Every major webhook provider (Facebook, Stripe, Calendly) signs their payloads with a secret. Validate the signature or reject the request. This prevents spoofed payloads.

  2. Idempotency. Facebook webhooks can deliver the same event multiple times. Store the event ID and skip reprocessing if you've seen it before. GoHighLevel's "Find Contact" logic handles this at the CRM level, but if you have custom logic, implement it explicitly.

  3. Dead letter queue. When a webhook payload fails processing, don't discard it. Store it somewhere (a Supabase table, an Airtable record, a Slack notification) so it can be reviewed and reprocessed. One dropped lead from a $1,000 ad campaign is a very expensive data loss.

  4. Monitoring. Use a service like Webhook.site or Hookdeck in development, and implement active monitoring in production. I track: webhooks received per hour, webhooks processed successfully, webhooks failed, average processing time.

A simple Supabase table with columns (id, source, received_at, processed, error_message) plus a daily query alerting you to failed events is sufficient for most operations.


A practical example: VAPI → GoHighLevel

Here's the specific implementation for VAPI call completion events:

VAPI webhook payload (simplified):

{
  "type": "end-of-call-report",
  "call": {
    "id": "call_abc123",
    "customer": {
      "number": "+15551234567"
    },
    "analysis": {
      "summary": "Lead qualified. Interested in solar. Bill is $180/month.",
      "structuredData": {
        "qualified": true,
        "bill_range": "$150-200",
        "homeowner": true
      }
    },
    "endedReason": "customer-ended-call"
  }
}

Processing steps (Zapier or Make):

  1. Receive webhook at trigger URL
  2. Extract: customer.number, call.analysis.structuredData.qualified, call.analysis.summary
  3. Format phone to E.164
  4. GoHighLevel: Find contact by phone number
  5. Update contact: set custom field vapi_qualified to true/false, set vapi_summary to the summary text
  6. If qualified = true: move to pipeline stage "Qualified — Appointment Pending," enroll in booking sequence
  7. If qualified = false: move to pipeline stage "Disqualified," apply tag disq: not-qualified-ai, stop all active sequences
  8. Log event to Supabase: {call_id, contact_id, qualified, processed_at}

Total processing time: under 3 seconds from call ending to CRM update.


When to use webhooks vs. native integrations

Native integrations (GHL's built-in Facebook connection, Calendly integration, etc.) are fine for simple, low-volume setups. Use them.

Switch to webhooks when:

  • You need sub-60-second processing (native integrations often run on 5–15 minute polling cycles)
  • You need to fan out to more than one system from a single event
  • You need custom data transformation that the native integration doesn't support
  • You need a reliable audit trail of every event
  • You're at a volume where polling failures have meaningful business impact (>100 leads/day)

For most operations above 50 leads/day, webhook-first architecture pays for its complexity within 2 weeks.

If you want help mapping out the webhook architecture for your specific stack, start with a conversation — I'll tell you where the gaps are.

Need This Built?

Ready to implement this for your business?

Everything in this article reflects real systems I've built and operated. Let's talk about yours.

H

Haroon Mohamed

Full-stack automation, AI, and lead generation specialist. 2+ years running 13+ concurrent client campaigns using GoHighLevel, multiple AI voice providers, Zapier, APIs, and custom data pipelines. Founder of HMX Zone.

ShareShare on X →