# 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