> ## Documentation Index
> Fetch the complete documentation index at: https://docs.bluereacher.com/llms.txt
> Use this file to discover all available pages before exploring further.

# GoHighLevel Integration

> Connect BlueReacher to GoHighLevel so workflows send iMessage and replies land back in Conversations, including agency sub-account setup and fixes for the common errors.

Send iMessage from any GoHighLevel workflow, and let replies land in the contact's Conversations thread. Your BlueReacher lines are dedicated iMessage lines, managed for you, with no A2P registration required, so a brand new sub-account can send on day one.

This page covers connecting an account, wiring workflows to sends, routing replies back, running many sub-accounts as an agency, and fixing the errors that come up most.

## Before you start

You need three things:

1. A BlueReacher API key. Dashboard, Settings, API Keys, Create key. Keys start with `bluereacher_`.
2. At least one active line in your BlueReacher workspace. Copy its `line_id` from Lines.
3. A GoHighLevel sub-account where you are an Admin.

## Step 1: Connect your GoHighLevel account

In BlueReacher, open Integrations, choose GoHighLevel, and click Connect. You are sent to HighLevel's authorization screen.

If you log in at agency level, HighLevel shows a sub-account picker. Select every sub-account you want BlueReacher in. If you log in at sub-account level, only that one is connected.

Approve the scopes. BlueReacher requests read access to contacts and read/write access to conversations. Nothing else.

Back in BlueReacher you will see each connected sub-account listed with its HighLevel Location ID. Assign a line to each one. A line can serve several sub-accounts, but each sub-account must have exactly one default line so outbound sends never guess.

## Step 2: Trigger sends from a workflow

Open **Automation, Workflows** in the sub-account, then open or create a workflow. Set your trigger as usual (Form Submitted, Opportunity Status Changed, Appointment Status, whatever starts the sequence).

Click **+ Add Action**, choose **Send Data**, then **Custom Webhook**.

Configure it like this:

| Setting   | Value                                               |
| --------- | --------------------------------------------------- |
| Method    | `POST`                                              |
| URL       | `https://api.bluereacher.com/functions/v1/messages` |
| Auth type | Bearer Token                                        |
| Token     | `bluereacher_your_api_key`                          |
| Header    | `Content-Type: application/json`                    |
| Body      | Raw JSON, shown below                               |

```json theme={null}
{
  "line_id": "line_7f3a92c4",
  "to": "{{contact.phone}}",
  "content": "Hey {{contact.first_name}}, this is Marcus from Northside Roofing. Got your estimate request. Want me to hold Thursday 2pm?",
  "channel": "imessage",
  "external_id": "{{contact.id}}"
}
```

Put the HighLevel contact ID in `external_id`. That is how BlueReacher matches a later reply back to the right contact record.

Publish the workflow. Draft workflows accept test runs but never fire on live contacts.

### Endpoint reference: send a message

**Purpose:** queue one iMessage to one recipient on one of your lines.

**Method and path:** `POST https://api.bluereacher.com/functions/v1/messages`

**Auth:** `Authorization: Bearer bluereacher_your_api_key`. Keys are workspace scoped. A key from workspace A cannot send on a line in workspace B.

**Request fields**

| Field         | Type   | Required | Description                                                                            |
| ------------- | ------ | -------- | -------------------------------------------------------------------------------------- |
| `to`          | string | Yes      | Recipient in E.164 format, for example `+15125550188`.                                 |
| `content`     | string | Yes      | Message body. 1 to 2,000 characters.                                                   |
| `line_id`     | string | Yes      | The sending line. Use the line assigned to this sub-account.                           |
| `from`        | string | No       | Sending number in E.164. Alternative to `line_id`. Send one or the other, not both.    |
| `channel`     | string | No       | `imessage`, `rcs`, or `sms`. Defaults to `imessage`.                                   |
| `media_url`   | string | No       | Public HTTPS URL of an image, PDF, or video to attach. Max 15 MB.                      |
| `external_id` | string | No       | Your own reference. Pass `{{contact.id}}` to link the thread to the HighLevel contact. |
| `contact_id`  | string | No       | BlueReacher contact ID, if you already have one.                                       |

**Response fields**

| Field         | Type           | Description                                                          |
| ------------- | -------------- | -------------------------------------------------------------------- |
| `id`          | string         | BlueReacher message ID.                                              |
| `from`        | string         | Line number the message was sent from.                               |
| `to`          | string         | Recipient number.                                                    |
| `content`     | string         | Message body as sent.                                                |
| `channel`     | string         | Channel used for delivery.                                           |
| `status`      | string         | `queued`, `sent`, `delivered`, `read`, `failed`, or `undeliverable`. |
| `line_id`     | string         | Line that handled the send.                                          |
| `contact_id`  | string         | BlueReacher contact record.                                          |
| `external_id` | string         | Value you passed in, echoed back.                                    |
| `media_url`   | string or null | Attachment URL, if any.                                              |
| `created_at`  | string         | ISO 8601 UTC timestamp.                                              |

**curl**

```bash theme={null}
curl -X POST https://api.bluereacher.com/functions/v1/messages \
  -H "Authorization: Bearer bluereacher_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "line_id": "line_7f3a92c4",
    "to": "+15125550188",
    "content": "Hey Dana, got your estimate request. Want me to hold Thursday 2pm?",
    "channel": "imessage",
    "external_id": "8Kx2Lp0QvRnW4dGh"
  }'
```

**Response**

```json theme={null}
{
  "id": "msg_01J9F2K7QW3ZQ8V6M4T2R5N1AB",
  "from": "+14155550142",
  "to": "+15125550188",
  "content": "Hey Dana, got your estimate request. Want me to hold Thursday 2pm?",
  "channel": "imessage",
  "status": "queued",
  "line_id": "line_7f3a92c4",
  "contact_id": "ct_9c21be04",
  "external_id": "8Kx2Lp0QvRnW4dGh",
  "media_url": null,
  "created_at": "2026-07-24T15:04:22Z"
}
```

**Error notes**

| Code | Body `error`           | Meaning                                                                    |
| ---- | ---------------------- | -------------------------------------------------------------------------- |
| 400  | `invalid_phone_number` | `to` is not E.164. Add the `+` and country code.                           |
| 401  | `invalid_api_key`      | Key is wrong, revoked, or from another workspace.                          |
| 403  | `line_not_authorized`  | The key cannot send on that `line_id`.                                     |
| 422  | `content_required`     | `content` was empty after merge fields resolved.                           |
| 429  | `rate_limited`         | Over 60 requests per minute per key. Retry after the `Retry-After` header. |
| 503  | `line_unavailable`     | Line is paused. Check Lines in the dashboard.                              |

HighLevel marks a Custom Webhook step failed on any non-2xx response and retries with exponential backoff before skipping it.

## Step 3: Replies back into GoHighLevel

Once a sub-account is connected, BlueReacher writes inbound replies straight into that contact's Conversations thread. No extra setup. The reply shows in the timeline, on the mobile app, and in the contact record.

Matching order is `external_id` first, then phone number in E.164. Contacts whose number is stored without a country code will not match.

Because replies land in Conversations, you can start a workflow with the **Customer Replied** trigger and branch on the message text. That is the simplest way to pause a sequence, notify a rep, or move an opportunity.

If you want the raw payload instead, add a webhook in BlueReacher under Settings, Webhooks, subscribe to `message.received`, and point it at a workflow using HighLevel's **Inbound Webhook** trigger. Payload:

```json theme={null}
{
  "event": "message.received",
  "id": "msg_01J9F31CB6XPD2ZQ7YH0KM8TSV",
  "from": "+15125550188",
  "to": "+14155550142",
  "content": "Thursday works. What time?",
  "channel": "imessage",
  "status": "delivered",
  "line_id": "line_7f3a92c4",
  "contact_id": "ct_9c21be04",
  "external_id": "8Kx2Lp0QvRnW4dGh",
  "media_url": null,
  "created_at": "2026-07-24T15:11:07Z"
}
```

Inbound Webhook is an LC Premium Trigger, so it draws on the sub-account's premium execution balance.

## Step 4: Agencies and sub-accounts

Connect once at agency level and authorize the sub-accounts you manage. BlueReacher keeps them isolated: separate API keys, separate lines, separate reply routing, separate reporting.

Two rules worth following:

* One line per client. Sharing a line across clients mixes reply threads and makes reporting useless.
* One API key per sub-account. When you offboard a client, revoke that key and every workflow in their account stops sending immediately.

To add a sub-account later, open Integrations, GoHighLevel, Manage connection, Add sub-accounts. You reauthorize in HighLevel and pick the new ones. Existing connections are untouched.

Custom Webhook is an LC Premium Action. Enable it at **Agency Level, Settings, Company, Workflow - Premium Features**. Each sub-account gets 100 free executions, then \$0.01 per execution bills to the Agency Wallet unless you turn on rebilling for that sub-account.

## Troubleshooting

**Workflow runs but nothing sends.** Open the workflow's History tab and find the contact. A skipped Custom Webhook step means the premium execution balance ran out or the call returned a non-2xx code. The step detail shows the response body.

**401 invalid\_api\_key.** The most common cause is pasting an agency-wide key into a sub-account workflow after rotating keys. Generate a fresh key and update the Auth field.

**400 invalid\_phone\_number.** HighLevel stores imported numbers in whatever format the CSV used. Add an Update Contact Field step before the webhook to normalize the phone to E.164, or use a formatted custom field in the body.

**Replies are not showing in Conversations.** Check that the sub-account still appears as Connected in BlueReacher. Tokens are revoked when a HighLevel user removes the app or when the authorizing user loses Admin. Reconnect and replies resume, including ones received while disconnected.

**The same contact gets two messages.** Allow Re-Entry is on in Workflow Settings. Turn it off, or add a wait plus an If/Else check on a tag you set after the first send.

**Status comes back `undeliverable`.** The recipient is not reachable on iMessage. Branch on the webhook `status` and route those contacts to email or a call task.

**Merge field sends literally.** If a message arrives reading `{{contact.first_name}}`, the field was empty and HighLevel passed the token through. Add a fallback value in the merge field picker.
