← All posts

GoHighLevel Inbound Webhook: Setup, Mapping & Examples

Hamza Lamhidra
GoHighLevel Inbound Webhook receiving external app data and triggering a workflow.

Learn how to set up a GoHighLevel Inbound Webhook, map incoming data correctly, find contacts by external IDs, handle arrays, and debug failed workflows.

A GoHighLevel Inbound Webhook lets an external application send data directly into a HighLevel workflow through a unique webhook URL.

HighLevel currently supports GET, POST, and PUT requests for the Inbound Webhook Trigger. After HighLevel receives a test request, you select that request as the Mapping Reference, which makes the incoming values available to later workflow actions.

The basic flow looks like this:

External system
↓
HighLevel Inbound Webhook URL
↓
Captured request
↓
Mapping Reference
↓
Record resolution
↓
Workflow actions

The URL itself is the easy part.

Most real problems happen later:

This guide covers the setup, but focuses especially on those production decisions.

For the difference between Marketplace/App Webhooks, Inbound Webhooks, Workflow Webhooks, and Custom Webhooks, see our guide to the different GoHighLevel webhook types.

If you need to understand the other webhook surfaces first, see the complete GoHighLevel webhooks guide

How the GoHighLevel Inbound Webhook Trigger Works

The Inbound Webhook is a workflow trigger.

An external system initiates the process by sending an HTTP request to the unique URL generated by HighLevel.

External application
↓
GET / POST / PUT
↓
HighLevel Inbound Webhook URL
↓
Captured request
↓
Mapping Reference
↓
Workflow

When you send a test request, HighLevel captures it.

You then choose one of those captured requests as the Mapping Reference.

That reference tells Workflow Builder what incoming fields are available.

For example, if the incoming request contains:

{
"externalCustomerId": "cus_12345",
"subscriptionStatus": "active"
}

the workflow can later use those values:

externalCustomerId
↓
Find Contact

subscriptionStatus
↓
Update field / If-Else / external action

The important point is:

The Mapping Reference is not the customer's stored data. It is the captured request HighLevel uses to understand the structure of incoming data.

Real HighLevel Inbound Webhook example

GoHighLevel Inbound Webhook Trigger showing a captured request and Mapping Reference
A captured inbound request can be selected as the Mapping Reference so its values become available inside the workflow.

How to Set Up a GoHighLevel Inbound Webhook

1. Add the Inbound Webhook Trigger

Inside the relevant HighLevel location, open:

Automation → Workflows

Create a new workflow or open the workflow that should receive the external event.

Add the Inbound Webhook trigger.

HighLevel generates a unique URL for the trigger.

2. Copy the generated webhook URL

Add that URL to the external application, backend, n8n workflow, Make scenario, or other system that will send the request.

Treat the URL as sensitive.

Do not publish it in:

3. Send a representative test request

Do not test with meaningless placeholder data if production will send a completely different payload.

For example, if the real integration is receiving subscription updates, a useful test could look like:

{
"eventType": "subscription.updated",
"externalCustomerId": "cus_12345",
"email": "customer@example.com",
"subscriptionStatus": "active"
}

That gives HighLevel a Mapping Reference that actually resembles the production event.

4. Select the Mapping Reference

After the request reaches HighLevel, open the captured requests and select the correct one as the Mapping Reference.

Verify that its body and structure match what the external system will really send.

5. Use the incoming values in workflow actions

Once the Mapping Reference is saved, those incoming values can be selected in later workflow steps.

For example:

Inbound Webhook
↓
externalCustomerId
↓
Find Contact

or:

Inbound Webhook
↓
subscriptionStatus
↓
If / Else
↓
active / cancelled

6. Send another realistic request

Do not stop after the first test succeeds.

Send another realistic request and verify that the complete workflow behaves correctly.

A successful test request only proves that one request reached the trigger.

It does not prove that every future production payload will have the same structure.

Mapping Reference: The Part Most Tutorials Under-Explain

Mapping Reference is one of the most important parts of a GoHighLevel Inbound Webhook.

Think of it as a sample schema:

Incoming request
↓
Captured by HighLevel
↓
Selected as Mapping Reference
↓
Workflow knows which values exist

Suppose your external service originally sends:

{
"customer": {
"email": "customer@example.com"
}
}

Your workflow uses:

customer.email

Later, the external service changes its payload to:

{
"customer": {
"contact": {
"email": "customer@example.com"
}
}
}

The email still exists.

But its path changed from:

customer.email

to:

customer.contact.email

Now you can end up with this situation:

Webhook received
↓
Workflow starts
↓
Mapped email is blank

The first instinct should not be to rebuild the workflow.

Check:

Actual production payload
↓
Current Mapping Reference
↓
Mapped field path

If the sender's payload structure changes, update the Mapping Reference and any mappings that depend on the old structure.

An Inbound Webhook workflow had been creating contacts correctly, then a mapped field started coming through blank. Nothing errored. The webhook returned 200 on every delivery, so the delivery side looked completely healthy.

The symptom was only in the data: the workflow ran, the contact was created, but one value landed empty. I diagnosed it by capturing a fresh production request into the Mapping Reference and comparing its structure against the field path the workflow step still referenced. The sending system had changed its payload shape, moving the value to a deeper path, so the mapping was pointing at a key that no longer existed, which resolves to blank rather than throwing an error.

The fix was to re-capture a current request as the Mapping Reference and re-point the mapping at the new path. To keep it from recurring silently, I made the workflow treat a missing critical value as a real failure it could branch on, instead of letting a blank pass straight through into a bad record.

Design the Payload Before You Map It

If you control the system sending the webhook, treat the payload as an integration contract.

A useful payload should have:

For example:

{
"eventType": "invoice.paid",
"externalCustomerId": "cus_123",
"invoiceId": "inv_456",
"status": "paid"
}

That immediately answers:

What happened?
→ invoice.paid

Which customer?
→ cus_123

Which invoice?
→ inv_456

What is its state?
→ paid

Avoid changing field meaning or data type silently.

For example, this:

{
"total": 99
}

is structurally different from:

{
"total": {
"amount": 99,
"currency": "USD"
}
}

Even if both contain the same amount, the workflow sees a different payload structure.

That can affect the Mapping Reference and downstream mappings.

Does a GoHighLevel Inbound Webhook Require a Contact?

No.

The Inbound Webhook Trigger itself can run without a contact when the actions after it do not require contact context.

For example:

Inbound Webhook
↓
If / Else
↓
Google Sheets
↓
Slack notification

There may be no reason to create a CRM contact for that process.

The correct question is:

Does this workflow actually need a contact?

If the answer is yes, the next question becomes:

How should the incoming event be matched to the correct contact?

Pattern 1: Contactless workflow

Inbound Webhook
↓
Process incoming values
↓
Slack / Sheets / Custom Webhook / internal action

No contact resolution is necessary.

Pattern 2: Find an existing contact

Inbound Webhook
↓
Find Contact
↓
Contact Found
↓
Continue workflow

Pattern 3: Find first, create only when necessary

Inbound Webhook
↓
Find Contact
↓
┌────────────────┐
Found Not Found
↓ ↓
Update Create Contact

This is more deliberate than automatically creating or updating a contact for every inbound event.

Match the External Event to the Correct HighLevel Record

Receiving the event is only half of the integration.

The next question is:

Which HighLevel record does this external event belong to?

Consider an external billing system sending:

{
"externalCustomerId": "cus_89231",
"subscriptionStatus": "active"
}

Suppose the corresponding HighLevel contact stores:

External Customer ID = cus_89231

The workflow can resolve the event like this:

Inbound Webhook
↓
externalCustomerId = cus_89231
↓
Find Contact
↓
External Customer ID matches
↓
Contact Found
↓
Update subscription status

This can be more reliable than matching only by a person's name.

Email or phone can also be appropriate identifiers depending on the integration.

The principle is:

Use the most stable identifier available for the relationship between the two systems.

What if no contact is found?

HighLevel's Find Contact flow can branch into:

Contact Found

and:

Contact Not Found

The Not Found branch does not always need to create a new contact.

Depending on the business process, it could:

That decision belongs to the business logic.

Contacts are not the only records

Some inbound integrations are not contact-centric.

An external system might need to resolve:

For example:

ERP event
↓
Inbound Webhook
↓
Find Company by External ID
↓
Update workflow

The broader rule remains the same:

Resolve the incoming event to the correct record before making record-specific changes.

An external system sent subscription events into a HighLevel workflow, and each event carried the customer's ID from that external system. Rather than matching on the person's name or even email, I stored that external ID as a custom field on the HighLevel contact when the record was first created, then used Find Contact against that custom field to resolve each incoming event.

That identifier was the right choice because it was the most stable link between the two systems. A name can be entered inconsistently, and an email can change or be shared across records, but the external system's customer ID is assigned once and never changes for the life of that customer. So every subsequent event, whether a status change, a renewal, or a cancellation, resolved to exactly the right contact, even if the person later updated their email. Matching on email would have been fragile. Matching on the external ID made the relationship deterministic.

Working With Nested Objects and Arrays

Inbound payloads are not always flat.

A simple scalar payload might be:

{
"status": "paid"
}

A nested payload could look like:

{
"customer": {
"email": "customer@example.com",
"plan": "pro"
}
}

And an array could look like:

{
"items": [
{
"sku": "A1",
"quantity": 1
},
{
"sku": "B2",
"quantity": 2
}
]
}

Nested objects require the correct field path in your Mapping Reference.

Arrays need additional handling.

Some older GoHighLevel guidance says arrays from Inbound Webhooks cannot be used in workflow actions.

That no longer describes the entire current workflow toolset.

HighLevel's current Array Formatter can receive arrays from Inbound Webhook data and perform operations such as:

Current workflow tooling can also use supported list values in text fields.

That does not mean every complex JSON payload should be processed directly inside HighLevel.

Deeply nested data or nested arrays can still be easier to normalize before sending them into the workflow.

For example:

Complex provider payload
↓
n8n / backend
↓
Flatten / normalize
↓
Simple GHL payload
↓
Inbound Webhook

Keep the Generated Inbound Webhook URL Private

The generated Inbound Webhook URL is an entry point into your workflow.

Treat it like a secret endpoint.

Do not expose it publicly.

If the URL becomes compromised, replace the trigger so HighLevel generates a new endpoint and stop using the old one.

The security model should also depend on the source of the event.

Trusted internal system

A direct connection may be enough:

Internal system
↓
GHL Inbound Webhook

provided the URL is handled securely.

External provider with signed webhooks

For higher-risk events, a stronger architecture can be:

External provider
↓
Your endpoint
↓
Verify provider signature
↓
Validate / normalize payload
↓
GHL Inbound Webhook

For example, if a payment provider signs its webhooks, verifying that signature before forwarding the event to HighLevel proves more than simply knowing the HighLevel webhook URL.

A secret URL and cryptographic sender verification solve different problems.

Does the GoHighLevel Inbound Webhook Cost Money?

The Inbound Webhook is currently presented as a Premium Workflow Trigger.

HighLevel's current pricing documentation lists Premium Workflow executions under its usage-based Workflow pricing, with Workflow Pro tiers available for larger execution volumes.

Because HighLevel pricing can change, check the current pricing page before estimating production costs.

Cost matters more when event volume becomes significant.

For example, architecture decisions can involve several costs at once:

Workflow execution cost
API usage
Middleware cost
Hosting
Maintenance
Security
Transformation
Operational control

Do not choose an integration architecture based only on the per-execution price.

Common GoHighLevel Inbound Webhook Problems

When an Inbound Webhook fails, debug it in the same order the data travels.

External sender
↓
Did it send?
↓
Did HighLevel capture the request?
↓
Correct Mapping Reference?
↓
Does the live payload match it?
↓
Correct field/path?
↓
Does the workflow need a record?
↓
Was the correct record resolved?
↓
Did the downstream action execute?

No request appears in HighLevel

Check:

Test request works but production does not

Compare the test and live payloads.

Do not compare only the values.

Compare:

Keys
Nesting
Data types
Arrays
Body vs query params vs headers

The test could be valid while production sends a different shape.

Incoming field is blank

Check:

  1. Does the field exist in the actual captured request?
  2. Is it in the body, header, or query parameters?
  3. Does the current Mapping Reference contain it?
  4. Is the mapped field path correct?

The wrong contact is updated

That is usually a record-resolution problem, not an HTTP delivery problem.

Inspect the matching rule used by Find Contact or the equivalent record lookup.

A contact action fails

Confirm that the workflow has actually resolved a contact.

A contactless Inbound Webhook can execute perfectly until it reaches an action that requires contact context.

Array data does not behave as expected

Check whether the current workflow action supports the array/list shape you are receiving.

For complex nested arrays, normalize the payload before sending it to HighLevel if that produces a simpler and more reliable workflow.

HighLevel receives the webhook, but a later action fails

Separate these two questions:

Did Inbound Webhook receive the request?

and:

Did the rest of the workflow succeed?

The trigger can work correctly while:

Debug the first failing step rather than repeatedly changing the webhook sender.

An Inbound Webhook workflow was resolving events to the wrong contact. The webhook was received, the workflow ran, and a contact was updated every time, so nothing looked broken at the delivery or execution level. The problem was that the update sometimes landed on the wrong person.

I isolated it by separating the two questions the article stresses: did the trigger receive the request, and did the workflow resolve the correct record? Delivery and execution were both fine, which pointed the investigation at record resolution rather than the webhook itself. The Find Contact step was matching on an identifier that was not unique enough, so when two records shared that value, the workflow updated whichever it found first.

The fix was to match on a stable, unique identifier instead, so each event resolved to exactly one record. Once the matching rule used the identifier that was truly one-to-one between the two systems, the wrong-contact updates stopped.

When Should You Put Middleware in Front of the Inbound Webhook?

Not every integration needs middleware.

A direct connection is often the simplest solution:

Trusted external system
↓
GHL Inbound Webhook

when:

Middleware becomes useful when it has a specific job.

For example:

External provider
↓
Middleware / backend
↓
Verify
Validate
Normalize
Enrich
↓
GHL Inbound Webhook

Useful reasons include:

Another pattern is:

System A ─┐
System B ─┼→ Normalization layer → GHL Inbound Webhook
System C ─┘

Instead of making the HighLevel workflow understand three different payload schemas.

n8n can be useful when the job is mainly transformation and SaaS integration.

A custom backend makes more sense when you need deeper validation, authentication, state, or custom business logic.

If n8n is the external system sending or receiving data, see the full n8n + GoHighLevel integration guide

GoHighLevel Inbound Webhook Production Checklist

Before relying on an Inbound Webhook in production, verify:

If those boundaries are clear, the workflow is much easier to maintain than one built around a single successful test request.

Need Help With a GoHighLevel Inbound Webhook Integration?

A basic Inbound Webhook is quick to configure.

The harder integrations involve deciding:

If your integration has reached that point, Hamza can help review the webhook flow and choose the simplest architecture that safely handles the real process.

For custom inbound event architecture, work with a GoHighLevel integration developer

Work with Hamza on Upwork

Book a free workflow audit

30 minutes. We look at your two or three most critical workflows and I tell you which ones are actually at risk. No pitch deck.