Update all active config and documentation files to use the correct production domain hl.cast.ph (not ghl.cast.ph). Co-Authored-By: Paperclip <noreply@paperclip.ing>
301 lines
7.4 KiB
Markdown
301 lines
7.4 KiB
Markdown
# GHL Conversation Provider — API Reference
|
|
|
|
**GHL API Base URL:** `https://services.leadconnectorhq.com`
|
|
**GHL Marketplace:** `https://marketplace.gohighlevel.com`
|
|
**API Version Header:** `Version: 2021-04-15`
|
|
|
|
---
|
|
|
|
## Overview
|
|
|
|
This document covers the GHL APIs used by the Cast GHL Provider bridge service. The bridge acts as a custom SMS Conversation Provider, receiving outbound webhooks from GHL and posting inbound messages + status updates back.
|
|
|
|
---
|
|
|
|
## Authentication
|
|
|
|
### OAuth 2.0
|
|
|
|
All GHL API calls require a Bearer token obtained via the OAuth 2.0 flow.
|
|
|
|
**Header:**
|
|
```
|
|
Authorization: Bearer <access_token>
|
|
Version: 2021-04-15
|
|
```
|
|
|
|
Access tokens expire after ~24 hours. Use the refresh token to obtain a new one.
|
|
|
|
---
|
|
|
|
## OAuth Endpoints
|
|
|
|
### 1. Authorization URL (user-facing redirect)
|
|
|
|
```
|
|
GET https://marketplace.gohighlevel.com/oauth/chooselocation?
|
|
response_type=code&
|
|
redirect_uri={REDIRECT_URI}&
|
|
client_id={CLIENT_ID}&
|
|
scope={SCOPES}
|
|
```
|
|
|
|
| Param | Description |
|
|
|-------|-------------|
|
|
| `response_type` | Always `code` |
|
|
| `redirect_uri` | Your OAuth callback URL (must match app config) |
|
|
| `client_id` | From GHL Marketplace app settings |
|
|
| `scope` | Space-separated list of scopes |
|
|
|
|
**Required scopes:**
|
|
```
|
|
conversations/message.write conversations/message.readonly conversations.write conversations.readonly contacts.readonly contacts.write
|
|
```
|
|
|
|
After authorization, GHL redirects to: `{redirect_uri}?code={authorization_code}`
|
|
|
|
### 2. Token Exchange
|
|
|
|
**`POST /oauth/token`**
|
|
|
|
Exchange an authorization code for access + refresh tokens.
|
|
|
|
```
|
|
Content-Type: application/x-www-form-urlencoded
|
|
|
|
client_id={CLIENT_ID}&
|
|
client_secret={CLIENT_SECRET}&
|
|
grant_type=authorization_code&
|
|
code={CODE}&
|
|
redirect_uri={REDIRECT_URI}
|
|
```
|
|
|
|
**Response — 200 OK:**
|
|
```json
|
|
{
|
|
"access_token": "eyJhbGc...",
|
|
"refresh_token": "def50200...",
|
|
"expires_in": 86400,
|
|
"token_type": "Bearer",
|
|
"locationId": "GKAWb4yu7A4LSc0skQ6g",
|
|
"companyId": "GK12345...",
|
|
"userType": "Location"
|
|
}
|
|
```
|
|
|
|
### 3. Token Refresh
|
|
|
|
**`POST /oauth/token`**
|
|
|
|
```
|
|
Content-Type: application/x-www-form-urlencoded
|
|
|
|
client_id={CLIENT_ID}&
|
|
client_secret={CLIENT_SECRET}&
|
|
grant_type=refresh_token&
|
|
refresh_token={REFRESH_TOKEN}
|
|
```
|
|
|
|
**Response:** Same shape as token exchange.
|
|
|
|
---
|
|
|
|
## Webhook: ProviderOutboundMessage
|
|
|
|
GHL sends this webhook to your Delivery URL whenever a user sends an SMS from GHL (Conversations, Workflows, Bulk Actions, Mobile App).
|
|
|
|
**Method:** POST
|
|
**Signature header:** `x-wh-signature` (ECDSA P-256 + SHA-256, base64-encoded ASN.1 DER)
|
|
|
|
### Payload Schema
|
|
|
|
```json
|
|
{
|
|
"contactId": "GKBhT6BfwY9mjzXAU3sq",
|
|
"locationId": "GKAWb4yu7A4LSc0skQ6g",
|
|
"messageId": "GKJxs4P5L8dWc5CFUITM",
|
|
"type": "SMS",
|
|
"phone": "+15864603685",
|
|
"message": "The text message to be sent to the contact",
|
|
"attachments": ["https://example.com/image.png"],
|
|
"userId": "GK56r6wdJDrkUPd0xsmx"
|
|
}
|
|
```
|
|
|
|
| Field | Type | Description |
|
|
|-------|------|-------------|
|
|
| `contactId` | string | GHL contact ID |
|
|
| `locationId` | string | GHL sub-account (location) ID |
|
|
| `messageId` | string | GHL message ID — use for status updates |
|
|
| `type` | string | `"SMS"` or `"Email"` |
|
|
| `phone` | string | Recipient phone in E.164 format |
|
|
| `message` | string | Message body to send |
|
|
| `attachments` | array | URLs of attached media (MMS) |
|
|
| `userId` | string | GHL user who sent the message |
|
|
|
|
### Webhook Signature Verification
|
|
|
|
1. Read the raw request body
|
|
2. Compute SHA-256 hash of the body bytes
|
|
3. Base64-decode the `x-wh-signature` header value
|
|
4. Verify the ECDSA ASN.1 DER signature using the public key from your GHL app settings
|
|
|
|
```go
|
|
hash := sha256.Sum256(body)
|
|
sigBytes, _ := base64.StdEncoding.DecodeString(signatureHeader)
|
|
valid := ecdsa.VerifyASN1(publicKey, hash[:], sigBytes)
|
|
```
|
|
|
|
The public key is provided in PEM format in your GHL Marketplace app settings under the webhook configuration.
|
|
|
|
---
|
|
|
|
## Conversation APIs
|
|
|
|
### Update Message Status
|
|
|
|
**`PUT /conversations/messages/{messageId}/status`**
|
|
|
|
Update the delivery status of an outbound message. Only the conversation provider's own marketplace app token can update status.
|
|
|
|
**Headers:**
|
|
```
|
|
Authorization: Bearer <access_token>
|
|
Content-Type: application/json
|
|
Version: 2021-04-15
|
|
```
|
|
|
|
**Request Body:**
|
|
```json
|
|
{
|
|
"status": "delivered"
|
|
}
|
|
```
|
|
|
|
| Status Value | Meaning |
|
|
|-------------|---------|
|
|
| `delivered` | Message delivered to recipient |
|
|
| `failed` | Message failed to send |
|
|
| `pending` | Message accepted, delivery in progress |
|
|
|
|
**Response — 200 OK:**
|
|
```json
|
|
{
|
|
"success": true
|
|
}
|
|
```
|
|
|
|
### Add Inbound Message
|
|
|
|
**`POST /conversations/messages/inbound`**
|
|
|
|
Post an inbound (received) message into a GHL conversation.
|
|
|
|
**Headers:**
|
|
```
|
|
Authorization: Bearer <access_token>
|
|
Content-Type: application/json
|
|
Version: 2021-04-15
|
|
```
|
|
|
|
**Request Body (SMS — default provider):**
|
|
```json
|
|
{
|
|
"type": "SMS",
|
|
"message": "Reply from the recipient",
|
|
"phone": "+639171234567"
|
|
}
|
|
```
|
|
|
|
| Field | Type | Required | Description |
|
|
|-------|------|----------|-------------|
|
|
| `type` | string | Yes | `"SMS"` for default provider |
|
|
| `message` | string | Yes | Inbound message body |
|
|
| `phone` | string | Yes | Sender's phone in E.164 format |
|
|
| `conversationProviderId` | string | No | Not required for default SMS provider |
|
|
|
|
**Response — 200 OK:**
|
|
```json
|
|
{
|
|
"conversationId": "...",
|
|
"messageId": "..."
|
|
}
|
|
```
|
|
|
|
**Note:** The `phone` number must match an existing contact in GHL. If no contact exists with that phone number, you may need to create one first via the Contacts API.
|
|
|
|
---
|
|
|
|
## Conversation Provider Setup
|
|
|
|
### Required Scopes
|
|
|
|
| Scope | Purpose |
|
|
|-------|---------|
|
|
| `conversations/message.write` | Outbound webhook events, inbound messages, status updates |
|
|
| `conversations/message.readonly` | Read message data, recordings, transcriptions |
|
|
| `conversations.write` | Create/update/delete conversations |
|
|
| `conversations.readonly` | Query conversations |
|
|
| `contacts.readonly` | Look up contacts by phone |
|
|
| `contacts.write` | Create contacts for unknown inbound numbers |
|
|
|
|
### Provider Configuration (in GHL Marketplace app)
|
|
|
|
1. Type: **SMS**
|
|
2. Name: **Cast SMS**
|
|
3. Delivery URL: `https://hl.cast.ph/api/ghl/v1/webhook/messages`
|
|
4. Do NOT check "Is this a Custom Conversation Provider"
|
|
|
|
### Enabling the Provider (per sub-account)
|
|
|
|
After OAuth install:
|
|
1. Go to sub-account Settings → Phone Numbers → Advanced Settings
|
|
2. Select "Cast SMS" as the SMS Provider
|
|
3. Save
|
|
|
|
### Supported GHL Features
|
|
|
|
| Feature | Supported |
|
|
|---------|-----------|
|
|
| Conversations (web app) | Yes |
|
|
| Conversations (mobile app) | Yes |
|
|
| Workflows (SMS module) | Yes |
|
|
| Bulk Actions | Yes |
|
|
| Missed Call Text-Back | No (falls back to Twilio) |
|
|
| Review Requests | No (falls back to Twilio) |
|
|
| Internal SMS Notifications | No (falls back to Twilio) |
|
|
| Conversational AI | No (GHL limitation) |
|
|
|
|
---
|
|
|
|
## Rate Limits
|
|
|
|
GHL API rate limits vary by plan. The bridge should handle 429 responses gracefully:
|
|
|
|
- Retry after the `Retry-After` header value
|
|
- Max 3 retries per request
|
|
- Log rate limit events for monitoring
|
|
|
|
---
|
|
|
|
## Error Handling
|
|
|
|
GHL API errors return JSON with an error description:
|
|
|
|
```json
|
|
{
|
|
"statusCode": 401,
|
|
"message": "Unauthorized"
|
|
}
|
|
```
|
|
|
|
Common errors:
|
|
|
|
| Status | Cause | Action |
|
|
|--------|-------|--------|
|
|
| 401 | Token expired or invalid | Refresh token and retry |
|
|
| 403 | Insufficient scopes | Check app scope configuration |
|
|
| 404 | Message or contact not found | Log and skip |
|
|
| 422 | Invalid request body | Log and fix request format |
|
|
| 429 | Rate limited | Retry after Retry-After header |
|