# Ghl Voice Integration Guide

## Integrating Telnyx AI Voice Agents with GoHighLevel: A Technical Guide

This document outlines the process for integrating Telnyx AI Voice Agents with GoHighLevel CRM, enabling advanced features like AI-powered lead capture, automated workflows, and appointment booking.

**Table of Contents:**

1.  **Prerequisites**
2.  **Telnyx AI Voice Agent Configuration**
3.  **GoHighLevel Setup**
4.  **Webhook Integration for Lead Capture**
    *   4.1 Webhook Endpoint Design
    *   4.2 Python Webhook Handler Implementation
    *   4.3 Webhook Registration in Telnyx
5.  **Custom Fields Mapping**
6.  **Workflow Automation Triggers**
7.  **Appointment Booking via Voice**
8.  **Follow-Up Sequence Activation**
9.  **Security Considerations**
10. **Troubleshooting**

**1. Prerequisites**

*   **Telnyx Account:**  A Telnyx account with sufficient credit and access to the AI Voice Agents service.
*   **GoHighLevel Account:** A GoHighLevel account with administrative privileges.
*   **Python Environment:** A functional Python environment (version 3.7 or higher) with necessary libraries installed (e.g., Flask, Requests).
*   **Web Server/Hosting:**  A web server (e.g., AWS Lambda, Google Cloud Functions, Heroku, Digital Ocean App Platform) to host the webhook endpoint.  A publicly accessible endpoint is essential (ngrok can be used for testing).
*   **Basic Programming Knowledge:** Familiarity with Python and web development concepts.

**2. Telnyx AI Voice Agent Configuration**

1.  **Create an AI Voice Agent:**  In the Telnyx portal, navigate to the "AI Voice Agents" section and create a new agent.
2.  **Configure Agent Behavior:** Define the agent's personality, purpose, and the questions it will ask when interacting with users.  This includes specifying the initial prompt and subsequent questions.  Pay careful attention to eliciting information useful for lead capture (name, phone number, email, etc.).
3.  **Set up IVR Flows (Optional):**  Use Telnyx IVR flows to route calls to the AI Voice Agent based on predefined conditions. This allows you to deploy AI agents for specific business needs.
4.  **Configure Webhooks:**  Crucially, configure the "Event Webhook" under the AI Voice Agent settings. This is where you'll enter the URL of your webhook endpoint crafted in Section 4.

**3. GoHighLevel Setup**

1.  **Create Location:**  Ensure you have the correct GoHighLevel location set up for your business.
2.  **Custom Fields:**  Define custom fields in GoHighLevel that will store the data captured by the Telnyx AI Voice Agent. Examples:
    *   `ai_agent_name` (Text) - To store the agent's name
    *   `lead_source` (Text) -  To specify the lead source as "Telnyx AI"
    *   `spoken_name` (Text)  -  Captured name
    *   `spoken_email` (Email) - Captured email
    *   `spoken_phone` (Phone)  - Captured phone
    *   `customer_query` (Text) -  The customer's stated reason for the call.
3.  **Workflow Configuration:** Design workflows in GoHighLevel to automate actions based on specific triggers (e.g., a new contact is created, a custom field is updated).

**4. Webhook Integration for Lead Capture**

This section focuses on building a webhook endpoint that receives data from Telnyx and creates/updates contacts in GoHighLevel.

**4.1 Webhook Endpoint Design**

The webhook endpoint should:

1.  **Receive POST requests** from Telnyx.
2.  **Parse the JSON payload** containing information about the agent's interaction with the user.
3.  **Extract relevant data** (name, phone, email, etc.) captured by the AI agent from the `dialogue.results` field.
4.  **Create or Update a contact** in GoHighLevel using the GoHighLevel API.
5.  **Map the extracted data** to the appropriate GoHighLevel custom fields.
6.  **Return a 200 OK response** to Telnyx to acknowledge receipt.

**4.2 Python Webhook Handler Implementation**

```python
from flask import Flask, request, jsonify
import requests
import json

app = Flask(__name__)

# Replace with your actual GoHighLevel API Key and Location ID
GOHIGHLVL_API_KEY = "YOUR_GOHIGHLVL_API_KEY"
LOCATION_ID = "YOUR_GOHIGHLVL_LOCATION_ID"
BASE_URL = "https://rest.gohighlevel.com/v1"

def create_or_update_contact(data):
    """Creates or updates a contact in GoHighLevel based on the provided data."""
    headers = {
        "Authorization": f"Bearer {GOHIGHLVL_API_KEY}",
        "Content-Type": "application/json",
        "Version": "2021-07-28"  # Recommended version.
    }

    phone = data.get("spoken_phone") #required for lookup
    email = data.get("spoken_email") #optional

    # Attempt to find an existing contact based on phone number
    if phone:   #PHONE NUMBER IS REQUIRED SO DO CALL BEFORE SEARCHING BY PHONE
        search_url = f"{BASE_URL}/contacts/?phone={phone}&locationId={LOCATION_ID}"
        search_response = requests.get(search_url, headers=headers)
        search_response_json = search_response.json()

        if search_response.status_code == 200 and search_response_json['contacts']:
            contact_id = search_response_json['contacts'][0]['id']
            update_url = f"{BASE_URL}/contacts/{contact_id}?locationId={LOCATION_ID}"
            response = requests.put(update_url, headers=headers, data=json.dumps(data)) # Update existing contact
            if response.status_code == 200:
                print(f"Contact updated successfully.  Contact ID: {contact_id}")
                return response.json()
            else:
                print(f"Error updating contact: {response.status_code} - {response.text}")
                return None

        #  If no contact found with phone, check using email
        elif email:
            search_url = f"{BASE_URL}/contacts/?email={email}&locationId={LOCATION_ID}"
            search_response = requests.get(search_url, headers=headers)
            search_response_json = search_response.json()
            if search_response.status_code == 200 and search_response_json['contacts']:
                contact_id = search_response_json['contacts'][0]['id']
                update_url = f"{BASE_URL}/contacts/{contact_id}?locationId={LOCATION_ID}"
                response = requests.put(update_url, headers=headers, data=json.dumps(data)) # Update existing contact
                if response.status_code == 200:
                    print(f"Contact updated successfully.  Contact ID: {contact_id}")
                    return response.json()
                else:
                    print(f"Error updating contact: {response.status_code} - {response.text}")
                    return None
        # If no contact found via either phone or email:
            else: #Create new Contact
                create_url = f"{BASE_URL}/contacts?locationId={LOCATION_ID}"
                response = requests.post(create_url, headers=headers, data=json.dumps(data))
                if response.status_code == 200:
                    print("Contact created successfully.")
                    return response.json()
                else:
                    print(f"Error creating contact: {response.status_code} - {response.text}")
                    return None


    else:
        print("Phone number is required to find or create a contact.")
        return None




@app.route('/telnyx-webhook', methods=['POST'])
def telnyx_webhook():
    """Handles the Telnyx webhook."""
    try:
        data = request.get_json()
        print("Received Telnyx webhook payload:", data) #Log the full json for debugging

        #THIS NEEDS TO BE ADAPTED BASED ON THE TELNYX AGENT'S RESPONSES
        # Extract relevant data from the Telnyx payload.  Adapt to your agent's configuration.
        try:
            results = data.get("data", {}).get("payload", {}).get("dialogue", {}).get("results", {})
            first_name = results.get("spoken_name", {}).get("value")
            phone_number = results.get("spoken_phone", {}).get("value")
            email_address = results.get("spoken_email", {}).get("value")
            query = results.get("customer_query", {}).get("value") #Reason for call.
        except Exception as e:
            print(f"Error processing dialogue results: {e}")
            return jsonify({"status": "error", "message": "Error processing dialogue results"}), 400

        # Prepare data for GoHighLevel
        gohighlevel_data = {
            "firstName": first_name if first_name else "AI Agent Lead", # Provide a default value
            "phone": phone_number,
            "email": email_address,
            "customField": {
                "ai_agent_name": data.get("data", {}).get("payload", {}).get("actor", {}).get("name", "Unknown Agent"),  #Get the Agent name
                "lead_source": "Telnyx AI",
                "spoken_name": first_name,
                "spoken_email": email_address,
                "spoken_phone": phone_number,
                "customer_query": query
            },
            "locationId": LOCATION_ID
        }


        #Remove empty values from the phone number or email to prevent accidental erasure
        if not gohighlevel_data['phone']:
            del gohighlevel_data['phone']
        if not gohighlevel_data['email']:
            del gohighlevel_data['email']


        # Create or update the contact in GoHighLevel
        result = create_or_update_contact(gohighlevel_data)

        if result:
            return jsonify({"status": "success", "message": "Contact created/updated successfully in GoHighLevel"}), 200
        else:
            return jsonify({"status": "error", "message": "Failed to create/update contact in GoHighLevel"}), 500

    except Exception as e:
        print(f"An error occurred: {e}")
        return jsonify({"status": "error", "message": str(e)}), 500

if __name__ == '__main__':
    app.run(debug=True, host='0.0.0.0', port=int(os.environ.get('PORT', 5000)))  # Use environment variable for port

```

**Important Notes:**

*   **Error Handling:** The code includes `try...except` blocks for robust error handling.  Enhance error logging and implement retry mechanisms as needed.
*   **Data Validation:**  Add data validation to ensure that the data received from Telnyx is in the expected format before sending it to GoHighLevel.
*   **API Rate Limiting:**  Be mindful of GoHighLevel's API rate limits and implement appropriate throttling mechanisms in your webhook handler.
*   **Asynchronous Processing:**  For high-volume calls, consider using asynchronous task queues (e.g., Celery, Redis Queue) to offload the GoHighLevel API calls and prevent the webhook from being overwhelmed.
*   **Adapt the `results` extraction:** The specific keys within the `results` dictionary depend on your AI agent's design and the questions it asks. *Carefully inspect the Telnyx webhook payload* to determine the correct keys for each piece of information.
*   **Custom Field Structure:** The `customField` dictionary reflects the custom field names you defined in GoHighLevel.  Ensure these names match exactly.
*   **Default Values:** The code provides a default value ("AI Agent Lead") for the `firstName` field.   Provide sensible default values for other fields as well, or handle cases where the agent fails to capture the information.
* **Phone number is required**: GHL requires a phone number for searching for existing contact, and for creating a new contact. In this example the code doesn't create or search if a phone number isn't found.

**4.3 Webhook Registration in Telnyx**

1.  **Deploy the Webhook:**  Deploy your Python application to your chosen web server or hosting platform (e.g., AWS Lambda, Heroku).  Ensure the application is running and accessible through a public URL (HTTPS is strongly recommended).
2.  **Test the Endpoint:**  Use tools like Postman or `curl` to send a sample POST request to your webhook endpoint to verify that it's working correctly.
3.  **Configure Telnyx AI Agent:**  In the Telnyx portal, navigate to your AI Voice Agent settings and configure the "Event Webhook" field.  Enter the URL of your deployed webhook endpoint.
4.  **Trigger a Test Call:**  Initiate a test call to your Telnyx number associated with the AI Voice Agent.  Complete the call and verify that the webhook is triggered and that a contact is created or updated in GoHighLevel with the captured data.  Inspect the webhook logs for any errors.

**5. Custom Fields Mapping**

The `customField` dictionary in the Python code directly maps the data extracted from the Telnyx webhook payload to the custom fields defined in GoHighLevel. Ensure:

*   **Exact Name Matching:**  The keys in the `customField` dictionary *must* exactly match the names/labels of the custom fields created in GoHighLevel, including capitalization and spacing.
*   **Data Type Consistency:**  Ensure the data types are compatible. For example, if you're storing a date in a custom field, format the date accordingly.

**6. Workflow Automation Triggers**

In GoHighLevel, you can configure workflows that are triggered by:

1.  **New Contact Creation:**  When a new contact is created via the webhook.
2.  **Custom Field Update:**  When a specific custom field is updated (e.g., `lead_source` is set to "Telnyx AI").

Example Workflow Triggers:

*   **Trigger:** New Contact
    *   **Action:** Send an introductory SMS message (e.g., "Welcome to our company!  We received your call...").
*   **Trigger:** Custom Field Value Change -  `lead_source` = "Telnyx AI"
    *   **Action:** Add the contact to a specific follow-up campaign.
    *   **Action:** Assign the contact to a sales representative.
    *   **Action:** Update the contact's pipeline stage.

**7. Appointment Booking via Voice**

This is a more complex feature requiring the AI Voice Agent to:

1.  **Inquire about appointment availability:**  The agent must have knowledge of available appointment slots. This can be achieved through:
    *   **API Integration:** A direct API integration with the GoHighLevel calendar or another scheduling system.  This provides real-time access to availability.
    *   **Pre-populated Data:**  Load a list of available appointment times into the agent's knowledge base. (Less dynamic but simpler to implement initially).
2.  **Suggest appointment times:**  Based on user preferences and available slots.
3.  **Confirm the appointment:**  Get explicit confirmation from the user.
4.  **Create the appointment in GoHighLevel:**  Use the GoHighLevel API to create the appointment and associate it with the contact.

**Python Considerations:**

The Python webhook handler would need to be modified to:

*   **Manage State:**  Maintain state across turns in the conversation (e.g., remember the proposed appointment time). This can be done using a database or caching mechanism.
*   **Interact with GoHighLevel Calendar API:** Use the GoHighLevel API to retrieve available appointment slots and create new appointments.  Refer to the GoHighLevel API documentation for specific details.

**Workflow Integration:**

The GoHighLevel workflow could be configured to send automated reminders before the appointment.

**8. Follow-Up Sequence Activation**

The Python webhook handler can be extended to:

1.  **Trigger a campaign:**  Based on data received from Telnyx, the webhook can trigger a specific campaign.
2.  **Add to Campaign:** Contact is added to a specific follow-up campaign based on the call outcome.

```python
def add_contact_to_campaign(contact_id, campaign_id):
    """Adds a contact to a specific GoHighLevel campaign."""
    headers = {
        "Authorization": f"Bearer {GOHIGHLVL_API_KEY}",
        "Content-Type": "application/json",
        "Version": "2021-07-28"
    }

    url = f"{BASE_URL}/contacts/{contact_id}/campaigns/{campaign_id}"  # Adjust endpoint if necessary
    payload = {} # Can add custom start at date

    response = requests.post(url, headers=headers, data=json.dumps(payload))

    if response.status_code == 200:
        print(f"Contact added to campaign {campaign_id} successfully.")

    else:
        print(f"Error adding contact to campaign: {response.status_code} - {response.text}")
        raise Exception(f"Failed adding to campaign: {response.status_code} , response: {response.text}")

#Inside the telnyx_webhook method:

        #Determine which campaign to add the contact to, based on outcome
        campaign_id = "CAMPAIGN_ID_TO_ADD" # e.g., leads from AI voice
        try:
            if result: