> ## 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.

# Connecting other CRMs

> Wire any CRM to BlueReacher with the send-message API for outbound, webhooks for inbound replies, and external_id for record matching, with worked patterns for HubSpot, Salesforce, and no-code tools.

## What you are wiring

Any CRM connects to BlueReacher through two HTTP calls and one identifier.

**Outbound.** Your CRM automation calls `send-message` when a record hits a condition. New lead created, stage changed, no reply in three days.

**Inbound.** BlueReacher posts a webhook to your endpoint every time a contact replies. Your endpoint writes that reply onto the matching CRM record.

**Identity.** `external_id` ties the two directions together. Set it to your CRM record ID on the way out, read it on the way in, and every reply lands on the right record without phone number matching.

If your CRM can send an HTTP POST and receive one, it can run on BlueReacher. That covers HubSpot, Salesforce, Pipedrive, Close, GoHighLevel, and anything built in house.

## Before you start

1. Copy your API key from the dashboard under Settings, API Keys. Keys are prefixed `bluereacher_`.
2. Copy the `line_id` of the line you want to send from, under Settings, Lines. Lines are dedicated iMessage lines, managed for you, with no A2P registration required.
3. Stand up an HTTPS endpoint that accepts POST and returns `200`. Register its URL under Settings, Webhooks, and copy the signing secret shown there.

## Outbound: your CRM calls send-message

**Purpose.** Send one message from one of your lines to one contact, stamped with your CRM record ID.

**Method and path.** `POST https://api.bluereacher.com/functions/v1/send-message`

**Auth.** Pass `Authorization: Bearer bluereacher_your_api_key` and `Content-Type: application/json`. Call this from your server or your automation platform. Never from browser code, since the key would be exposed.

### Request fields

| Field         | Type   | Required | Description                                                                   |
| ------------- | ------ | -------- | ----------------------------------------------------------------------------- |
| `from`        | string | one of   | E.164 number of the sending line. Use `from` or `line_id`                     |
| `line_id`     | string | one of   | ID of the sending line. Use `line_id` or `from`                               |
| `to`          | string | yes      | Recipient in E.164 format, such as `+14155550123`                             |
| `content`     | string | yes      | Message body, plain text                                                      |
| `channel`     | string | no       | `imessage`, `rcs`, or `sms`. Defaults to `imessage`                           |
| `media_url`   | string | no       | Public HTTPS URL of an image or file to attach                                |
| `external_id` | string | no       | Your CRM record ID. Stored on the contact and echoed on every inbound webhook |

### Response fields

| Field         | Type   | Description                                |
| ------------- | ------ | ------------------------------------------ |
| `message_id`  | string | BlueReacher ID for this message            |
| `contact_id`  | string | BlueReacher ID for the contact             |
| `external_id` | string | The value you sent, echoed back            |
| `line_id`     | string | Line the message was sent from             |
| `from`        | string | Sending line number                        |
| `to`          | string | Recipient number                           |
| `content`     | string | Message body as sent                       |
| `channel`     | string | Channel used for this message              |
| `status`      | string | `queued`, `sent`, `delivered`, or `failed` |
| `created_at`  | string | ISO 8601 timestamp, UTC                    |

### Example request

```bash theme={null}
curl -X POST https://api.bluereacher.com/functions/v1/send-message \
  -H "Authorization: Bearer bluereacher_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "line_id": "line_7f3a91",
    "to": "+14155550123",
    "content": "Hi Dana, saw you booked a demo. Want me to send the pricing sheet?",
    "channel": "imessage",
    "external_id": "0035f00000LmQ2xAAF"
  }'
```

### Example response

```json theme={null}
{
  "message_id": "msg_9d41c07b",
  "contact_id": "cnt_4a10ef",
  "external_id": "0035f00000LmQ2xAAF",
  "line_id": "line_7f3a91",
  "from": "+13235550188",
  "to": "+14155550123",
  "content": "Hi Dana, saw you booked a demo. Want me to send the pricing sheet?",
  "channel": "imessage",
  "status": "queued",
  "created_at": "2026-07-24T16:02:11Z"
}
```

### Errors

| Status | `code`            | Meaning                                 | Fix                                              |
| ------ | ----------------- | --------------------------------------- | ------------------------------------------------ |
| 400    | `invalid_request` | Missing or malformed field              | Check `to` is E.164 and `content` is not empty   |
| 401    | `invalid_api_key` | Key missing, wrong, or revoked          | Recheck the `Authorization` header               |
| 403    | `line_not_owned`  | That `line_id` is not in your workspace | Copy the ID again from Settings, Lines           |
| 429    | `rate_limited`    | Send rate exceeded                      | Wait for the seconds in the `Retry-After` header |
| 500    | `server_error`    | Temporary failure on our side           | Retry with exponential backoff                   |

Errors return a JSON body with `code` and `message`. Log both against the CRM record so failed sends are visible to your team.

## Inbound: replies posted into your CRM

BlueReacher POSTs to your registered webhook URL whenever a contact replies.

| Field         | Type   | Description                                                           |
| ------------- | ------ | --------------------------------------------------------------------- |
| `event`       | string | `message.received` for replies, `message.status` for delivery updates |
| `message_id`  | string | BlueReacher ID for the inbound message                                |
| `contact_id`  | string | BlueReacher ID for the contact                                        |
| `external_id` | string | The CRM record ID you stamped on the outbound message                 |
| `line_id`     | string | Line that received the reply                                          |
| `from`        | string | Contact number                                                        |
| `to`          | string | Your line number                                                      |
| `content`     | string | Reply text                                                            |
| `channel`     | string | `imessage`, `rcs`, or `sms`                                           |
| `media_url`   | string | Attachment URL, or `null`                                             |
| `status`      | string | `received`, or the new delivery status on `message.status`            |
| `created_at`  | string | ISO 8601 timestamp, UTC                                               |

```json theme={null}
{
  "event": "message.received",
  "message_id": "msg_be7712aa",
  "contact_id": "cnt_4a10ef",
  "external_id": "0035f00000LmQ2xAAF",
  "line_id": "line_7f3a91",
  "from": "+14155550123",
  "to": "+13235550188",
  "content": "Yes please, send it over",
  "channel": "imessage",
  "media_url": null,
  "status": "received",
  "created_at": "2026-07-24T16:09:44Z"
}
```

A minimal handler that writes the reply back to your CRM:

```javascript theme={null}
import crypto from "crypto";

export default async function handler(req, res) {
  const raw = req.rawBody;
  const expected = crypto
    .createHmac("sha256", process.env.BLUEREACHER_WEBHOOK_SECRET)
    .update(raw)
    .digest("hex");

  if (req.headers["x-bluereacher-signature"] !== expected) {
    return res.status(401).json({ code: "invalid_signature" });
  }

  res.status(200).json({ received: true }); // ack first, work after

  const e = JSON.parse(raw);
  if (e.event !== "message.received") return;

  await crm.logActivity({
    recordId: e.external_id,
    body: e.content,
    channel: e.channel,
    occurredAt: e.created_at
  });
}
```

Two rules. Verify the `x-bluereacher-signature` HMAC before trusting a payload. Return `200` within 10 seconds, then do the CRM write in the background. Non-200 responses are retried with backoff for 24 hours, so slow handlers cause duplicates.

## Identity mapping with external\_id

Set `external_id` to the primary key of the record you want the conversation attached to. Contact ID, lead ID, deal ID, your choice. Pick one object type and stay with it.

The value is stored on the BlueReacher contact on first send and returned on every event for that contact after that. Your webhook handler never has to normalize a phone number or guess which duplicate record to update. If a contact replies before you have ever messaged them, `external_id` is `null`, so fall back to a phone number lookup and stamp the record on your next send.

## HubSpot

Outbound runs from a workflow. Add a **Send a webhook** action, method POST, URL `https://api.bluereacher.com/functions/v1/send-message`, and a custom payload with `line_id`, `to` mapped to the contact's phone, `content`, and `external_id` mapped to `hs_object_id`. Custom payloads and custom headers need Operations Hub Professional. On lower tiers, use a **Custom code** action in Node and call the API with `fetch`, which also lets you branch on the response.

Inbound goes to your own endpoint, not to HubSpot directly. On `message.received`, call HubSpot's Notes API, then associate the note to the contact whose `hs_object_id` equals `external_id`. Set a `last_imessage_reply` property in the same call so lists and workflows can act on it.

## Salesforce

Create an External Credential with a Custom authentication header, name it `Authorization`, value `Bearer bluereacher_your_api_key`. Attach it to a Named Credential pointing at `https://api.bluereacher.com`. This keeps the key out of Flow.

Build a record-triggered Flow on Lead or Contact. Add an **HTTP Callout** action against that Named Credential, path `/functions/v1/send-message`, method POST. Map `to` from the phone field, `content` from a formula or text template, and `external_id` from `{!$Record.Id}`.

Inbound needs an Apex REST endpoint, for example `@RestResource(urlMapping='/bluereacher/*')` exposed through a Site or Connected App. Query the record where `Id = external_id`, insert a completed Task with the reply text, and update any status field your reps watch.

## Zapier, n8n, and Make

All three use the same shape. No custom code required.

**Outbound.** Zapier: **Webhooks by Zapier**, POST action, add the `Authorization` header and a JSON payload. n8n: **HTTP Request** node, POST, Header Auth credential, JSON body. Make: **HTTP, Make a request** module, POST, header plus raw JSON body. Map `external_id` from the CRM record ID field that your trigger step already returns.

**Inbound.** Zapier: **Catch Hook** trigger. n8n: **Webhook** node. Make: **Custom webhook** module. Copy the generated URL into Settings, Webhooks. Then add a filter for `event` equals `message.received` and a CRM update step keyed on `external_id`.

## Launch checklist

* Send one live message with `external_id` set and confirm it appears on the CRM record.
* Reply from the recipient device and confirm the webhook lands within seconds.
* Break the signature on purpose and confirm your handler returns `401`.
* Return a `500` on purpose and confirm the retry arrives, then confirm your CRM write is idempotent on `message_id`.
* Turn on your automation for a small segment before the full list.
