Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
Complete MVP implementation of the Cast GHL Conversation Provider bridge: - Go module setup with chi router and mongo-driver dependencies - Config loading with env var validation and defaults - MongoDB token store with upsert, get, update, delete operations - Cast.ph SMS client with 429 retry logic and typed errors - Phone number normalization (E.164 ↔ Philippine local format) - GHL OAuth 2.0 install/callback/refresh flow - GHL webhook handler with ECDSA signature verification (async dispatch) - GHL API client for message status updates and inbound message stubs - Multi-stage Dockerfile, docker-compose with MongoDB, Woodpecker CI pipeline - Unit tests for phone normalization, Cast client, GHL webhook, and OAuth handlers Co-Authored-By: SideKx <sidekx.ai@sds.dev>
97 lines
3.3 KiB
Markdown
97 lines
3.3 KiB
Markdown
# Task 07: GHL API Client (Status Updates)
|
|
|
|
## Objective
|
|
Build `internal/ghl/api.go` — client for calling GHL APIs (update message status, post inbound messages).
|
|
|
|
## Reference
|
|
- Update Message Status: https://marketplace.gohighlevel.com/docs/ghl/conversations/update-message-status
|
|
- Add Inbound Message: https://marketplace.gohighlevel.com/docs/ghl/conversations/add-an-inbound-message
|
|
|
|
## Types (add to `internal/ghl/types.go`)
|
|
|
|
```go
|
|
type MessageStatusUpdate struct {
|
|
Status string `json:"status"` // "delivered", "failed", "pending"
|
|
ErrorCode string `json:"error_code,omitempty"`
|
|
ErrorMessage string `json:"error_message,omitempty"`
|
|
}
|
|
|
|
type InboundMessage struct {
|
|
Type string `json:"type"` // "SMS"
|
|
Message string `json:"message"`
|
|
Phone string `json:"phone"` // E.164
|
|
ConversationProviderID string `json:"conversationProviderId,omitempty"`
|
|
}
|
|
|
|
type InboundMessageResponse struct {
|
|
ConversationID string `json:"conversationId"`
|
|
MessageID string `json:"messageId"`
|
|
}
|
|
```
|
|
|
|
## APIClient struct
|
|
|
|
```go
|
|
type APIClient struct {
|
|
baseURL string
|
|
httpClient *http.Client
|
|
}
|
|
|
|
func NewAPIClient() *APIClient {
|
|
return &APIClient{
|
|
baseURL: "https://services.leadconnectorhq.com",
|
|
httpClient: &http.Client{Timeout: 30 * time.Second},
|
|
}
|
|
}
|
|
```
|
|
|
|
## Methods
|
|
|
|
### UpdateMessageStatus
|
|
|
|
```go
|
|
func (c *APIClient) UpdateMessageStatus(ctx context.Context, accessToken, messageID, status string) error
|
|
```
|
|
|
|
1. Build URL: `{baseURL}/conversations/messages/{messageID}/status`
|
|
2. Body: `{ "status": status }`
|
|
3. Headers:
|
|
- `Authorization: Bearer {accessToken}`
|
|
- `Content-Type: application/json`
|
|
- `Version: 2021-04-15` (GHL API version header)
|
|
4. PUT request
|
|
5. On non-2xx: return error with status code + body
|
|
6. On success: return nil
|
|
|
|
### PostInboundMessage (Phase 2 — stub for now)
|
|
|
|
```go
|
|
func (c *APIClient) PostInboundMessage(ctx context.Context, accessToken string, msg *InboundMessage) (*InboundMessageResponse, error)
|
|
```
|
|
|
|
1. Build URL: `{baseURL}/conversations/messages/inbound`
|
|
2. Body: JSON of InboundMessage
|
|
3. Headers: same as above
|
|
4. POST request
|
|
5. Parse and return response
|
|
|
|
**Note:** Implement as a working stub — the full inbound flow is Phase 2, but the API client method should be ready.
|
|
|
|
## Key behaviors
|
|
- **GHL API version header:** `Version: 2021-04-15` — required on all GHL API calls
|
|
- **Bearer auth:** Use the OAuth access token for the specific locationId
|
|
- **Status values:** `"delivered"`, `"failed"`, `"pending"` — GHL expects these exact strings
|
|
- **Error on status update is non-fatal** — log it but don't cascade. The SMS was already sent (or failed to send). The status update is best-effort.
|
|
- **Retry on 401** — if status update returns 401, the token may have expired mid-request. The caller (webhook handler) should refresh and retry once.
|
|
|
|
## Acceptance Criteria
|
|
- [ ] `go build ./cmd/server/` succeeds
|
|
- [ ] `UpdateMessageStatus` sends PUT to correct URL
|
|
- [ ] `Authorization: Bearer` header set
|
|
- [ ] `Version: 2021-04-15` header set
|
|
- [ ] `Content-Type: application/json` header set
|
|
- [ ] Non-2xx returns descriptive error
|
|
- [ ] `PostInboundMessage` stub implemented with correct URL and method
|
|
- [ ] HTTP client has 30s timeout
|
|
- [ ] Context passed to all HTTP requests
|