Fix GoHighLevel Webhook Not Firing: Step-by-Step Guide

by Fahim

You set up a custom webhook in a GoHighLevel workflow, triggered a test contact, and… absolutely nothing. No logs on your server, no errors in your console, just pure silence. I’ve lost count of how many hours I’ve wasted staring at blank terminal screens, only to realize a single hidden toggle or a missing JSON parser was blocking the entire pipeline.

Let’s trace this silent failure step-by-step and get your payloads flowing. No fluff, just the exact settings and debugging steps to fix it.

Terminal screen showing successful GoHighLevel webhook payloads with 200 OK status codes
Terminal screen showing successful GoHighLevel webhook payloads with 200 OK status codes

Why GoHighLevel Webhooks Fail Silently

When a webhook fails, it’s easy to assume the GoHighLevel platform is down. But in my experience, the platform is usually working fine—the workflow just never reached the webhook step, or your server quietly rejected the incoming request. We need to isolate exactly where the chain broke.

Don’t guess. We need to verify if the workflow actually ran, if the webhook action executed, if the outbound request left HighLevel, and finally, if your target server accepted the payload. Skipping any of these steps means you’re just throwing darts in the dark.

Check the Workflow Execution Logs First

The absolute first place to look is the workflow execution history. If there’s no record of the execution, your webhook never stood a chance.

Open your workflow in GoHighLevel and click the History tab at the top of the builder. Here’s what you’re looking for:

  • No record of the contact: If the contact isn’t listed, your workflow trigger didn’t fire. Double-check your trigger filters and make sure the workflow is actually set to Published, not Draft (we’ve all done it).
  • Contact is stuck in a wait step: Look at the execution path. If you have a “Wait” action before your webhook, that contact is probably still sitting there waiting for the timer to run out.
  • Failed status on the Webhook step: If the webhook step shows a red or orange warning, click it to inspect the error. HighLevel will usually show the exact HTTP status code returned by your server (like 404, 500, or 403).

Turn on Workflow Re-entry (The Classic Testing Trap)

Here’s a classic mistake: testing with the same contact over and over without enabling re-entry. By default, GoHighLevel workflows only let a contact enter once.

If you trigger the workflow, tweak your webhook URL, and try to trigger it again with the same test contact, nothing will happen. The contact is already marked as “completed” in that workflow, so HighLevel silently ignores them.

To fix this, go to the workflow builder, click Settings in the top left, and toggle on Allow Re-entry. Save and publish. Now you can trigger the webhook repeatedly while you debug your code.

Isolate the Bug Using Webhook.site

If the workflow history shows a green checkmark for the webhook, but your server still shows zero activity, you need to isolate the problem. Is your server silently dropping the request, or is HighLevel sending it into the void?

I always use Webhook.site to rule out server-side issues. It gives you a unique, temporary URL that captures and displays incoming HTTP requests instantly.

Copy the unique URL from Webhook.site, swap it into your GoHighLevel webhook action, and trigger the workflow again. If the payload shows up on Webhook.site, your workflow configuration is fine. The issue is 100% on your destination server—whether it’s blocking the request, failing to parse the body, or throwing an unhandled exception.

Fix SSL and Firewall Blocks

GoHighLevel requires a secure connection to deliver webhook payloads. If your destination URL starts with http:// instead of https://, the webhook will often fail silently or get blocked by modern security protocols.

Also, if you’re hosting your endpoint on a private server or behind a strict firewall, you might be blocking HighLevel’s incoming IP addresses. Since HighLevel doesn’t publish a static list of IP addresses for webhooks, you need to make sure your server accepts POST requests from external networks and doesn’t require custom browser headers to load.

And remember: if you’re developing your receiver locally, you can’t use localhost in your HighLevel workflow. You have to expose your local server to the public internet. I usually test webhooks locally using Cloudflare Tunnels to securely route live HighLevel payloads straight to my local machine.

Parse the Incoming GoHighLevel Payload Correctly

Once you’ve verified that the webhook is actually reaching your server, you need to make sure your code parses the payload correctly. GoHighLevel sends webhook data as a standard JSON POST request. If your backend isn’t configured to parse JSON bodies, your variables will just return as undefined.

Here’s a simple Node.js and Express server setup that correctly parses and logs the incoming HighLevel payload. I always use a basic setup like this to verify the exact structure of the contact data before writing any complex business logic.

const express = require('express');
const app = express(); // This middleware is required to parse JSON payloads
app.use(express.json());
app.post('/ghl-webhook', (req, res) => {
  const payload = req.body;
  console.log('Received webhook from GoHighLevel!');
  console.log('Contact Name:', `${payload.first_name} ${payload.last_name}`);
  console.log('Contact Email:', payload.email);
  // Always return a 200 OK status to let GHL know you received it
  res.status(200).json({ status: 'success' });
});
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
  console.log(`Webhook receiver running on port ${PORT}`);
});

If your server doesn’t return a 200 OK response within a few seconds, HighLevel might mark the delivery as failed. If you’re expecting high volumes of traffic, make sure you prevent duplicate webhook processing in Express so you don’t run your backend logic twice for the same event.

Fixing Empty Custom Fields and Contact Merges

Sometimes the webhook fires, but the data inside the payload is completely empty. This usually happens when you use custom fields that haven’t been populated for your test contact, or when you’ve used incorrect merge tags.

To ensure your custom fields are sent correctly, open your test contact record and fill out every single custom field with dummy data. When you trigger the workflow, inspect the payload on Webhook.site or your local console to see the exact keys HighLevel uses. They often look like contact.custom_fields.your_field_key rather than a clean, simple variable name.

If you need to distribute these incoming payloads to multiple external services, you can build a custom router to route webhooks to multiple endpoints from a single receiver script.

Frequently Asked Questions

Why does my GoHighLevel webhook return a 400 Bad Request error?

A 400 Bad Request means your server received the request but couldn’t parse it. This is usually caused by strict schema validation on your backend. If you’re using Zod or a similar library, make sure your schema matches the exact payload structure HighLevel sends. Check out my guide on how to validate webhook payloads with Zod to handle these checks gracefully.

Does GoHighLevel retry failed webhooks?

No. GoHighLevel workflows won’t automatically retry failed webhook actions if your server returns a 500 error or times out. Once the action fails, the contact just moves to the next step. If you need reliable delivery, you’ll have to build retry logic on your receiver side or use a queue system to buffer incoming spikes.

Can I send custom headers with GoHighLevel webhooks?

Yes, the standard webhook action inside the workflow builder lets you add custom headers. This is incredibly useful for adding authorization tokens or API keys to secure your endpoint. Always verify these headers on your server before processing any data.

Why is my workflow history showing success but my server received nothing?

This is almost always an SSL or DNS resolution issue. If your server is using an expired SSL certificate, or if your DNS is routing through a proxy that blocks automated user-agents, HighLevel will drop the connection. Use Webhook.site to verify if the issue is global or isolated to your specific domain.

Next Steps for Your Webhook Setup

Now that your GoHighLevel webhook is firing and delivering payloads successfully, make sure your local development environment is set up to handle real-time testing safely. Read my guide on how to test webhooks locally using Cloudflare Tunnels to set up a secure, public URL for your local machine without messing with your router settings.

Official resources

all_in_one_marketing_tool