Source: ai-research/ghl-2026-05-01/support-solutions-articles-155000007340-conversations-api-add-inbound-message-with-contact-id.md
The Add Inbound Message endpoint records an inbound message into the correct contact conversation thread using only a Contact ID — no prior conversation lookup or creation required. It is the canonical way to ingest external messaging events (provider webhooks, third-party SMS/email, hybrid messaging integrations) back into the GHL inbox so agents see one unified thread per contact. Threading rules, idempotency, and retry behavior are first-class concerns; the endpoint is also backward-compatible with existing Conversation-ID-based flows.
Key Takeaways
- Contact-ID-first design. Provide
contactId+channel+contentand the system handles thread association. If an open conversation on the same channel exists, the message is appended; otherwise a new conversation is created andconversationIdis returned in the response. - Endpoint:
POST /conversations/inbound-messageswithAuthorization: Bearer <token>andContent-Type: application/json. The conceptual reference doc lives at the HighLevel API Documentation - Send a new message. - Channel coverage. SMS/MMS, Email, WhatsApp, Messenger, Instagram, Web Chat. Each channel enforces its own size, format, and template constraints; validate before posting.
- Disambiguation via
endpointhint. When a contact has multiple phones or emails for the same channel, includeendpoint.phoneorendpoint.emailin the payload so the system threads to the correct address. - Idempotency is mandatory in practice. Always send an
idempotencyKey; reuse the same key on retries to prevent duplicate messages on5xx/timeout retries. - Backfill supported. Set
metadata.timestampto the original message time and the message orders correctly in the timeline — useful for historical imports. - Auth and scope. Use OAuth or API key per environment; confirm Conversations/Contacts scopes for the role and that the auth context resolves to the correct location/workspace.
Endpoint Reference
POST /conversations/inbound-messages
Authorization: Bearer <token>
Content-Type: application/jsonRepresentative Request
{
"contactId": "CONTACT_ID",
"channel": "sms | whatsapp | email | messenger | instagram | webchat",
"endpoint": {
"phone": "+15551234567",
"email": "user@example.com"
},
"content": {
"text": "Hello from our provider",
"attachments": [
{
"type": "image|file|video",
"url": "https://example.com/file.jpg",
"filename": "file.jpg",
"sizeBytes": 123456
}
]
},
"metadata": {
"providerMessageId": "ext-abc-123",
"externalThreadKey": "optional-correlation",
"timestamp": "2026-02-04T10:15:30Z"
},
"idempotencyKey": "fd2d5f6f-5a9f-4b0a-8d68-0d5f6a1c9e5a"
}Representative Response
{
"messageId": "MSG_123",
"conversationId": "CONV_987",
"contactId": "CONTACT_ID",
"channel": "sms",
"direction": "inbound",
"createdAt": "2026-02-04T10:15:31Z"
}The source notes that field names are representative — production implementations should align with the latest developer docs.
Threading & Association Rules
- Existing thread (same channel): message is appended to the contact’s open conversation on that channel.
- No suitable thread: the system creates a new conversation and returns a fresh
conversationId. - Multiple endpoints per contact: include
endpoint.phone/endpoint.emailto disambiguate. - Multi-location context: auth must target the correct location/workspace that owns the contact.
- Archived/closed threads only: a new conversation is created.
Error Handling
| Status | Meaning |
|---|---|
| 400 | Invalid contactId, missing channel, malformed content, unsupported attachment type/size |
| 401 / 403 | Auth failed or insufficient scope/role for the location |
| 404 | contactId not found or not visible in your location context |
| 409 | Idempotency key collision or duplicate submission detected |
| 413 | Attachments exceed size limits |
| 429 | Rate limit exceeded — back off and retry, respect Retry-After |
| 5xx | Transient — retry with the same idempotencyKey |
Common Use Cases
- Logging external SMS/email/voice into GHL. A third-party SMS or email provider sends a webhook; your integration POSTs that event back into GHL so the inbox shows the full conversation history.
- Hybrid messaging integrations. Outbound messages go through your own provider (cost, deliverability, compliance), but inbound replies still need to land in the GHL agent inbox.
- Historical backfill / migrations. Importing message history from another CRM with original timestamps preserved via
metadata.timestamp. - Multi-channel routing. A single integration ingests messages across SMS, WhatsApp, Messenger, Instagram, and Email without per-channel branching logic.
Related
- GoHighLevel
- GoHighLevel API Guide
- API Endpoints Overview
- OAuth 2.0
- API Rate Limits
- Webhooks
- API Troubleshooting
Try It
- Confirm your OAuth token has Conversations + Contacts scopes for the target location.
- Resolve the recipient
contactIdupstream (lookup-by-phone or lookup-by-email). - Build the payload with
contactId,channel,content.text, and a UUIDidempotencyKey. - POST to
/conversations/inbound-messagesand capture the returnedconversationIdfor downstream tracking. - Add retry logic: on 429 honor
Retry-After; on 5xx/timeout retry with the sameidempotencyKey; on 409 treat as success (duplicate already recorded). - Verify by opening the contact in the GHL inbox and confirming the message lands in the correct thread.