A documented workflow, from Beam's own docs
Updated
How to send iMessage from your CRM
To send iMessage from a CRM, add a Custom Webhook workflow step that posts to Beam's workflow endpoint with your contact, line and message details. GoHighLevel's regular Send SMS action will not do this; it keeps using your default SMS provider. The same webhook shape works from any CRM that supports an outbound webhook action, and you can put an AI agent in the loop to read and answer replies instead of sending a fixed template.
GoHighLevel: the Custom Webhook workflow step
Per Beam's documented GoHighLevel setup, connect the correct GoHighLevel location first in Beam's CRM inbox messaging settings, and create a workspace token in Beam's Developer access with Send real messages permission. Then in GoHighLevel, add a Custom Webhook step to a workflow:
| Method | POST |
| URL | https://beam.aisync.link/api/crm/workflows/send |
| Authorization header | Bearer YOUR_BEAM_WORKSPACE_TOKEN |
| Content-Type header | application/json |
The JSON body uses GoHighLevel's contact-value picker for the contact ID and name, and your own values for the rest:
{
"location_id": "YOUR_GHL_LOCATION_ID",
"contact_id": "{{contact.id}}",
"workflow_id": "welcome-v1",
"from": "+15551234567",
"message": "Hi {{contact.first_name}}, thanks for reaching out. How can we help?",
"request_key": "{{contact.id}}.welcome-v1.step-1",
"consent": true,
"assistant_mode": "preserve",
"dry_run": true
}Beam looks up the phone number from that exact GoHighLevel contact in the connected location; you never paste a separate destination number into the step.
Test before you enable it
- Keep
dry_run: trueand run it on a test contact you own. Expectstatus: validatedandqueued: false. Nothing sends. - Set
dry_run: falseand run it again on the same test contact. Expect a Beam message ID andstatus: queued, which means accepted, not yet confirmed delivered. - Confirm the message actually arrives on your phone from the selected line, and check the same contact's timeline in GoHighLevel.
- Repeat the exact same request with the same
request_key. It should return the same message ID withduplicate: true, and send nothing new. - Only then add the step to a real workflow, and keep it in draft until the test passes. Never bulk-enroll existing contacts as a test.
How request_key prevents duplicate sends
The request_key identifies one intended message, not just one contact. A welcome step and a second follow-up step need different key suffixes, such as step-1 and step-2. For a recurring reminder, build the key from a stable appointment or event ID pulled through GoHighLevel's value picker, plus the reminder step name, so re-enrolling the same event reuses the same key instead of generating a new one on every retry. Reusing a key with different text returns a conflict response instead of silently guessing which version to send.
The generic CRM webhook pattern
Any CRM with an outbound webhook action, not just GoHighLevel, can call the same endpoint contract: a signed or token-authenticated POST with a contact identifier, a sender line, a message body, and a deduplication key. Swap GoHighLevel's {{contact.id}} merge syntax for whatever variable picker your CRM provides, keep the same request_key discipline, and run the identical dry-run-then-send test on one contact you control before enabling it for every record.
Routing inbound replies back to the CRM
Sending is only half the workflow. Beam pushes signed event webhooks, including message.received for an inbound reply, to any HTTPS endpoint you configure in Settings → Event webhooks. Verify every event against the Beam-Signature header, built as t=<unix_seconds>,v1=<hex signature> using HMAC-SHA256 over the raw request body, and reject anything older than five minutes to block replay attempts. Events are pushed once with no automatic retry in this version, so reconcile anything you cannot afford to miss against Beam's message history on a schedule, rather than assuming every webhook arrives.
Adding an AI agent to the loop
Instead of (or alongside) a fixed workflow message, an AI agent connected over Beam's MCP server can read the conversation and decide what to send. Call conversation_pause before an external agent takes over a specific thread, since a human reply also pauses Beam's own assistant automatically, with no automatic unpause. The agent then reads recent messages and calls message_send with explicit send permission on an active assigned line. This fits naturally on top of the webhook pattern above: the CRM workflow starts the conversation, and the agent, Claude or otherwise, handles the reply.
Before you connect
Questions before you enable the workflow
Can GoHighLevel's normal Send SMS action trigger an iMessage send through Beam?
No. Beam documents a separate Custom Webhook workflow step for this. The regular Send SMS action keeps using your default SMS provider; switching that default will not route the send through Beam.
What does the Custom Webhook request body need?
A location ID, the contact ID from GoHighLevel's value picker, a workflow ID, your sending line with its country code, the message text, a request_key, a consent flag, an assistant_mode, and a dry_run flag for testing.
How do I avoid sending the same message twice?
Use a request_key that identifies one specific intended message, not just the contact, and reuse that exact key when the same event re-enrolls. A repeated request with the same key returns the original message ID marked duplicate instead of sending again.
How do I get the customer's reply back into my CRM?
Configure an HTTPS event webhook endpoint in Beam for message.received events, verify the Beam-Signature header against the raw request body, and write the reply into your CRM's contact record from there.
Can an AI agent read and answer instead of a static template?
Yes. Pause the conversation for Beam's own assistant, have your agent read the thread over Beam's MCP server or API, then call message_send with explicit permission once it has a reply ready.
Does this only work with GoHighLevel?
The documented, tested path is GoHighLevel's Custom Webhook step, but the underlying endpoint contract, a POST with contact, line, message and a dedup key, works from any CRM capable of an outbound webhook action.
What should I test before turning this on for every client?
Run the dry_run request on one test contact, confirm the real send, confirm the inbound reply reaches your CRM, and confirm a repeated request with the same key does not send a second message.
Start with a conversation
Test this on a contact you control
Tell our team about your CRM and workflow. We will walk through the Custom Webhook step and the reply webhook together.
Live sending requires a plan and an assigned dedicated line.
iMessage
Today 9:41 AM
Hi! I would love to hear how your coaching works.
Happy to chat. Would Thursday work for a discovery call?
Thursday sounds great! Looking forward to meeting you.
Blue bubbles. Read receipts. Typing.
Want this for your business?
Try a conversation with the Beam team.
Beam is not affiliated with Anthropic or Apple.
Powered by Beam