Integrating Demio and Go High Level: A Step-by-Step Guide to Adding New Registrants to GHL and Tracking Webinar Attendance

Updated on April 15,2023

Integrating Demio and Go High Level can be a powerful way to streamline your workflow and track your webinar attendees. Unfortunately, Demio does not provide webhooks, so we need to use their API to fetch registrants and add them to Go High Level as new contacts. 

In this tutorial, I'll show you how to do just that, and also track which registrants attended or missed your webinar, and update their pipeline stage in Go High Level accordingly. Follow along step-by-step to learn how to integrate these two platforms and simplify your workflow.

Requirements

  • Pro Go High-Level account 
  • A Demio account with API access
  • A web server running Laravel framework
  • Understanding of PHP programming language
  • Basic knowledge of RESTful APIs
  • A MySQL database to store the Demio registrant data

Setting Up Our Application

To get started with our integration, we need to set up our Laravel application and obtain the necessary API keys for Demio and Go High Level.

First, we need to install the Laravel application. For instructions on how to do this, please refer to the Laravel documentation.

Next, to make API calls to Demio, we need to obtain an API key and secret. To do this, log in to your Demio account, click on "Settings," and select the "API" tab. You should see your API key and secret listed on this page. Be sure to keep these keys secure, as they will be used to authenticate our API requests to Demio.

Demio credentials

For Go High Level, we will be using OAuth 2.0 flow for authentication. To get started, we need to register an app with Go High Level and obtain a clientId and a clientSecret.

To do this, sign up for a developer account on the Go High Level Developer Site and create a new app. When creating the app, select "Private" as the app type, and define the app's scopes as:

  •  "contacts.write," 
  • "locations.readonly,"
  •  "opportunities.write," 
  • "contacts.readonly," 
  • "locations/customFields.readonly,"
  • "opportunities.readonly."

Next, define the redirect URL and generate the client ID and client secret. We will be using these credentials to generate the access token in the following sections.

To add the app to a location, the location or agency admin needs to go to the app's Authorization Page URL. 

This URL can be generated by replacing the client_id, redirect_uri, and scope in the following template:

https://marketplace.gohighlevel.com/oauth/chooselocation? response_type=code& redirect_uri={{REDIRECT_URL}}& client_id={{CLIENT_ID}}& scope={{LIST OF SCOPES SEPARATED BY SINGLE SPACE}}

Once the admin selects the location they want to connect and grants access, their browser will be redirected to the specified redirect_uri with an Authorization Code passed in the code query parameter.

We can then use the Authorization Code to get the Access token via the Get Access Token API under OAuth 2.0 and use the Access Token to call any API needed for the integration.

With our Laravel application installed and our API keys and secrets in hand, we are ready to start building our integration.

Set up environment

Before we can start building the integration, we need to set up our development environment. This includes creating a MySQL database, configuring our Laravel application, and setting the necessary environment variables.

Create MySQL database

First, let's create a new MySQL database to store the data for our integration. Log in to your MySQL server and run the following command:

 
create database ghl_demio;

This will create a new database called ghl_demio.

Configure Laravel application

Next, let's configure our Laravel application. Open the application in your favourite editor and copy the contents of .env_example to create a new .env file. Update the file with the correct database details, including the database name, username, and password.

We also need to add the following configurations to our .env file:


GHL_CLIENT_ID={{FROM YOUR GHL MARKETPLACE APP}}
GHL_CLIENT_SECRET={{FROM YOUR GHL MARKETPLACE APP}}
GHL_API_ENDPOINT=https://services.leadconnectorhq.com
GHL_REFRESH_TOKEN={{THE CODE THAT WAS APPENDED TO THE REDICT URL}}
GHL_V1_API_KEY={{FROM YOUR GHL DASHBOARD}}
GHL_PIPELINE_ID=XhU5rh8b2jJfHIIGr4NW
DEMIO_API_KEY={{FROM YOUR DEMIO DASHBOARD}}
DEMIO_API_SECRET={{FROM YOUR DEMIO DASHBOARD}}
DEMIO_API_ENDPOINT=https://my.demio.com/api/v1

Make sure to replace the values enclosed in {{ }} with the actual values from your GHL and Demio dashboards.

Now that we have set up our development environment, we are ready to start building the integration. In the next section, we will create the necessary tables and code to fetch new Demio registrants and add them as new contacts in Go High Level, as well as update their pipeline stage.

Add Models and Migrations

To add models and migrations for our Laravel application, please follow the steps below:

  1. Open your terminal and navigate to your application folder.
  2. Run the following commands:
php artisan make:model Event -m
php artisan make:model Session -m
php artisan make:model People -m
php artisan make:model CustomField -m
php artisan make:model Api -m

These commands will create the migration files and models for the five tables.

Open the app/Models/People.php file and add the following code to the class:

 
protected $table = "people";

Open the app/Models/CustomField.php file and add the following code to the class:

 
protected $table = "custom_fields";

Open each of the migration files (database/migrations/xxxx_xxxx_xxxxxx_create_xxxxxxx_table.php) and replace the up() method with the following code:

 
// Migration for events table
Schema::create('events', function (Blueprint $table) {
 $table->id();
 $table->string('labeled_name');
 $table->string('name');
 $table->integer('date_id');
 $table->string('status');
 $table->text('description')->nullable();
 $table->string('zone')->nullable();
 $table->string('registration_url');
 $table->timestamps();
 $table->json("automated")->nullable();
 $table->integer('total')->nullable();
 $table->string("last_person_registered_id")->nullable();
});
// Migration for the sessions table
Schema::create('sessions', function (Blueprint $table) {
 $table->id();;
 $table->integer('date_id');
 $table->string('status');
 $table->string('zone')->nullable();
 $table->timestamps();
 $table->timestamp('expires_in');
 $table->integer('total')->nullable();
 $table->integer("event_id")->nullable();
});
//migrations for the people table
Schema::create('people', function (Blueprint $table) {
 $table->id();
 $table->string("uuid");
 $table->string("name");
 $table->string("last_name")->nullable();
 $table->string("phone_number")->nullable();
 $table->string("contact_id")->nullable();
 $table->string("opportunity_id")->nullable();
 $table->string("email");
 $table->integer("registered")->nullable();
 $table->integer("joined")->default(0);
 $table->integer("missed")->default(0);
 $table->boolean("hasAttended")->default(false);
 $table->string("joinLink")->nullable();
 $table->timestamps();
});
// migration for the custom_fields table
Schema::create('custom_fields', function (Blueprint $table) {
 $table->id();
 $table->string("uuid");
 $table->string("name");
 $table->string("fieldKey");
 $table->string("placeholder")->nullable();
 $table->string("dataType");
 $table->integer("position")->nullable();
 $table->string("documentType")->nullable();
 $table->string("parentId")->nullable();
 $table->timestamps();
});
//Migration for the apis table
Schema::create('apis', function (Blueprint $table) {
 $table->id();
 $table->string('refresh_token')->nullable();
 $table->string('access_token')->nullable();
 $table->string('locationId')->nullable();
 $table->string('hashedCompanyId')->nullable();
 $table->string('userType')->nullable();
 $table->text('scope')->nullable();
 $table->integer("expires_in")->nullable();
 $table->timestamps();
});

Next, run the following command on the terminal to migrate the database:

 
php artisan migrate

Step-by-Step Integration Guide A. Fetching Demio Registrants

Since Demio doesn't have webhooks, we need to periodically fetch the list of registrants using their API. To do this we need to periodically pull all the events and save new ones to the database. 

Add the following code to app/Models/Events.php:


        
    public function checkForNewDemioEvent(){
        $events = $this->getDemioEvents();
        foreach($events as $event){
            $eventExist = Event::find($event["id"]);
            if(!$eventExist){
                $model = new Event();
                $model->id = $event["id"];
                $model->labeled_name = $event["labeled_name"];
                $model->name = $event["name"];
                $model->date_id = $event["date_id"];
                $model->status = $event["status"];
                $model->description = $event["description"];
                $model->zone = $event["zone"];
                $model->registration_url = $event["registration_url"];
                $model->automated = json_encode($event["automated"]);
                $model->save();
            }
        }
    }

    public function getDemioEvents(){
        $endpoint     = config('app.demio.DEMIO_API_ENDPOINT').'/events';
        $response= Http::withHeaders([
            'Api-Key' => config('app.demio.DEMIO_API_KEY'),
            'Api-Secret' => config('app.demio.DEMIO_API_SECRET')
        ])->get($endpoint);
        return $response->json();
    }

    public function getDemioEventRegisteredPeople($event_id,$type="registered",$query="",$session_id="",$page=1,$pageSize=500){
        $endpoint     = config('app.demio.DEMIO_API_ENDPOINT').'/people/'.$event_id.'/activity?type='.$type.'&query='.$query.'&session_id='.$session_id.'&page='.$page.'&pageSize='.$pageSize;
        $response= Http::withHeaders([
            'Api-Key' => config('app.demio.DEMIO_API_KEY'),
            'Api-Secret' => config('app.demio.DEMIO_API_SECRET')
        ])->get($endpoint);
        return $response->json();
    }

    public function getNewDemioEventRegistrants(){
        //increase execution time to accomodate large files
        set_time_limit(2000);
        $events = Event::orderBy("id","DESC")->get();
        // $events = Event::orderBy("id","DESC")->limit(1)->get(); //for now limit to test event
        foreach($events as $event){
            if($event->id == 390498){
                $people = $this->getDemioEventRegisteredPeople($event->id,"registered","",1909759);
            }else{
                $people = $this->getDemioEventRegisteredPeople($event->id);
            }
            if($people['totalEventPeople'] == 0 || ($event->total == $people['totalEventPeople'] && !empty($people['peopleActivity']['items']) && $people['peopleActivity']['items'][0]['person']['id'] == $event->last_person_registered_id )){
                continue;
            }
            $count = 0;
            foreach($people['peopleActivity']['items'] as $person){
                if($event->id == 390498){
                    if($event->last_person_registered_id == $person['person']['id']){
                        break;
                    }
                }else{
                    if(!$person['session']['isUpcoming']){
                        break;
                    }
                }
 
                $sessionExist = Session::where("date_id",$person['session']['id'])->first();
                if(empty($sessionExist->id)){
                    $session = $this->getDemioSession($event->id,$person['session']['id']);
                    $new_session = new Session();
                    $new_session->id = $session['date_id'];
                    $new_session->date_id = $session['date_id'];
                    $new_session->status = ($session['status'] == "finished") ? "scheduled": $session['status'];
                    $new_session->zone = $session['zone'];
                    $new_session->event_id = $event->id;
                    $new_session->expires_in = date("Y-m-d H:i:s", substr($session['timestamp'], 0, 10));
                    $new_session->save();
                }else{
                    if($sessionExist->id == 1909759){
                        $sessionExist->status = "running";
                        $sessionExist->save();
                    }
                }
                $count++;
                $personExistsLocally = People::where("uuid",$person['person']['id'])->first();
                if(!empty($personExistsLocally->id)){
                    continue;
                }
                $s = new Session();
                $pe = new People();
                $contactExist=$s->lookupGHLContact($person['person']['customFields'][1]['value']);
                if(!$contactExist){
                    $data['firstName'] = $person['person']['name'];
                    $data['lastName'] = $person['person']['customFields'][0]['value'];
                    $data['name'] = $data['firstName'] . " " . $data['lastName'];
                    $data['phone'] = $person['person']['customFields'][1]['value'];
                    $data['email'] = $person['person']['email'];
                    $data["tags"] = [
                        $event->name,
                        "demio_webinar_registered",
                    ];
                    $data['source'] = $event->name;
                    $demio_webinar_time = CustomField::where("fieldKey","contact.demio_webinar_time")->first();
                    if(!$demio_webinar_time){
                        $s->checkForNewCustomFields();
                        $demio_webinar_time = CustomField::where("fieldKey","contact.demio_webinar_time")->first();
                    }
                    $data["customFields"][0]['id'] = $demio_webinar_time->uuid;
                    $data["customFields"][0]['field_value'] = Carbon::parse(date("Y-m-d H:i:s", substr($person['session']['timestamp'], 0, 10)))->toRfc3339String();
                    $date_registered_for_webinar =CustomField::where("fieldKey","contact.date_registered_for_webinar")->first();
                    $data["customFields"][1]['id'] = $date_registered_for_webinar->uuid;
                    $data["customFields"][1]['field_value'] = Carbon::parse(date("Y-m-d H:i:s", substr($person['person']['registered'], 0, 10)))->toRfc3339String();
                    $demio_webinar_date =CustomField::where("fieldKey","contact.demio_webinar_date")->first();
                    $data["customFields"][2]['id'] = $demio_webinar_date->uuid;
                    $data["customFields"][2]['field_value'] = $data["customFields"][0]['field_value'];
                    $data["note"] = "Demio Registered ".$event->name." for ".$data["customFields"][0]['field_value'];
                    $response_data = $s->createGHLContact($data);
                    if(!empty($response_data['contact']['id'])){
                        $pe->contact_id = $response_data['contact']['id'];
                    }
                }else{
                    $data["tags"] = $contactExist[0]['tags'];
                    $data["tags"][] = "duplicate";
                    $data["tags"][] = "duplicate_".$event->name;
                    $s->updateGHLContact($contactExist[0]['id'],$data);
                    $note = "Duplicate for ".$event->name." for ".Carbon::parse(date("Y-m-d H:i:s", substr($person['session']['timestamp'], 0, 10)))->toRfc3339String();
                    $s->createGHLNote($contactExist[0]['id'],$note);
                    $pe->contact_id = $contactExist[0]['id'];
                }
                $pe->uuid = $person['person']['id'];
                $pe->name = $person['person']['name'];
                $pe->last_name = $person['person']['customFields'][0]['value'];
                $pe->phone_number = $person['person']['customFields'][1]['value'];
                $pe->email = $person['person']['email'];
                $pe->registered = $person['person']['registered'];
                $pe->hasAttended = $person['person']['hasAttended'];
                $pe->joinLink = $person['person']['joinLink'];
                if(!empty($pe->contact_id)){
                    $data['contact_id'] = $pe->contact_id;
                    $search = $s->searchGHLContactOpportunity($data);
                    $data = [];
                    if(!empty($search['opportunities'])){
                        $pe->opportunity_id = $search['opportunities'][0]['id'];
                        $note = "Contact already in the pipeline, opportunity not created";
                        $s->createGHLNote($pe->contact_id,$note);
                    }else{
                        // create opportunity
                        $opp["pipelineId"] = config('app.ghl.GHL_PIPELINE_ID');
                        $opp['name'] =  $person['person']['name'] . " " .  $person['person']['customFields'][0]['value'];
                        $opp["pipelineStageId"] = config('app.ghl.GLH_REGISTERED_DEMIO_WEBINAR');
                        $opp["status"]= "open";
                        $opp["contactId"] = $pe->contact_id;
                        $opportunity = $s->createGHLOpportunity($opp);
                        if(!empty($opportunity['opportunity'])){
                            $pe->opportunity_id = $opportunity['opportunity']['id'];
                        }
                    }
                }
                $pe->save();
            }
            $event->total = $people['totalEventPeople'];
            $event->last_person_registered_id=$people['peopleActivity']['items'][0]['person']['id'];
            $event->save();
        }
    } 

The methods check for new events, custom fields, and registrants, and add them to the database if they don't already exist. Here is a breakdown of what each method does:

getDemioEvents(): This method fetches all the events from Demio using their API. It sends a GET request to the events endpoint and returns the response as a JSON object.

checkForNewDemioEvent(): This method fetches all the events from Demio using getDemioEvents() method, and then checks if each event already exists in the database. If an event does not exist, it creates a new Event object, sets its attributes using the data from the API response, and saves it to the database.

getNewDemioEventRegistrants(): This method fetches all the registrants for each event from Demio using their API. It then checks if each registrant already exists in the database. If a registrant does not exist, it creates a new Session object, sets its attributes using the data from the API response, and saves it to the database. It then creates a new People object, sets its attributes using the data from the API response, and saves it to the database.

Add the following code to app/Models/Session.php:


    public function getGHLCustomFields(){
        $api = $this->getGHLToken();
        $access_token = $api->access_token;
        $endpoint = config('app.ghl.GHL_API_ENDPOINT').'/locations/'.$api->locationId.'/customFields';
        $response = Http::withToken($access_token)->withHeaders([
            'Version' => "2021-04-15"
        ])->get($endpoint); 
        return $response->json();
    }

    public function checkForNewCustomFields(){
        $custom_fields = $this->getGHLCustomFields();
        foreach($custom_fields["customFields"] as $custom_field){
            $fieldExist = CustomField::where("uuid",$custom_field["id"])->first();
            if(!$fieldExist){
                $model = new CustomField();
                $model->uuid = $custom_field["id"];
                $model->name = $custom_field["name"];
                $model->fieldKey = $custom_field["fieldKey"];
                $model->placeholder = $custom_field["placeholder"];
                $model->dataType = $custom_field["dataType"];
                $model->position = $custom_field["position"];
                $model->documentType = $custom_field["documentType"];
                $model->parentId = $custom_field["parentId"];
                $model->save();
            }
        }
    }

getGHLCustomFields(): This method fetches all the custom fields from Demio using their API. It sends a GET request to the custom fields endpoint and returns the response as a JSON object.

checkForNewCustomFields(): This method fetches all the custom fields from Demio using getGHLCustomFields(), and then checks if each custom field already exists in the database. If a custom field does not exist, it creates a new CustomField object, sets its attributes using the data from the API response, and saves it to the database.

Creating and Updating GHL Contacts, Notes, Tags and Opportunities

In the getNewDemioEventRegistrants() function, we are calling functions that we are yet to build like creating GHL contact, creating GHL opportunity, creating and updating GHL notes and tags and finally searching GHL contact to check if a contact already exists.

Before we do that, we need to create a function to get the GHL access token needed to authenticate other GHL API calls. GHL access tokens are valid for a day, after which you can use the refresh token to get a new access token that will also be valid for a day.

To handle token expiry, we should always check the expiry date of the token, if it is one hour before expiry, we should use the GHL API to refresh the token and save the new access token and refresh token in the api table. You can then make the request again with the new access token.

Let's go ahead and write a wrapper function to handle token expiry for all the API calls we make to the GHL APIs. This will ensure that our integration continues to work even if the access token expires.

Add the following function to Session model: 


    public function getGHLToken(){
        $api = Api::first();
        if(!empty($api->expires_in)){
            $expiry_date = $api->updated_at->addSeconds($api->expires_in - 3600); // refresh it an hour earlier just incase
            if($expiry_date->gt(Carbon::now())){
                return $api;
            }
        }else{
            $api = new Api();
        }

        $endpoint = config('app.ghl.GHL_API_ENDPOINT').'/oauth/token';
        $response = Http::asForm()->post($endpoint, [
            'client_id' => config('app.ghl.GHL_CLIENT_ID'),
            'client_secret' => config('app.ghl.GHL_CLIENT_SECRET'),
            'grant_type' => 'refresh_token',
            'refresh_token' => !empty($api->refresh_token) ? $api->refresh_token : config('app.ghl.GHL_REFRESH_TOKEN'),
        ]);
        $data = $response->json();
        $api->access_token = $data['access_token'];
        $api->expires_in = $data['expires_in'];
        $api->refresh_token = $data['refresh_token'];
        $api->scope = $data['scope'];
        $api->userType = $data['userType'];
        $api->locationId = $data['locationId'];
        $api->hashedCompanyId = $data['hashedCompanyId'];
        $api->save();

        return $api;
    }

The code defines a function called "getGHLToken" that retrieves an access token from the GHL API using the refresh token stored in the database or the authorization code in the env file. If the access token is still valid (expires_in), it returns the existing API token from the database. Otherwise, it makes an API call to refresh the token and then saves the new token information to the database before returning it.

Next, let's go ahead and create these functions:

Add the following function to Session model: 


    public function createGHLContact($data){
        $api = $this->getGHLToken();
        $access_token = $api->access_token;
        $data['locationId'] = $api->locationId;
        $note = $data['note'];
        unset($data['note']);
        $endpoint = config('app.ghl.GHL_API_ENDPOINT').'/contacts/';
        $response = Http::withToken($access_token)->withHeaders([
            'Version' => "2021-04-15"
        ])->asForm()->post($endpoint, $data);

        $response_data = $response->json();

        if(!empty($response_data['contact'])){
            $this->createGHLNote($response_data['contact']['id'],$note);
        }
        return $response_data;
    }

    public function updateGHLContact($contact_id,$data){
        $api = $this->getGHLToken();
        $access_token = $api->access_token;
        $endpoint = config('app.ghl.GHL_API_ENDPOINT').'/contacts/'.$contact_id;
        $response = Http::withToken($access_token)->withHeaders([
            'Version' => "2021-04-15"
        ])->asForm()->put($endpoint, $data);
        return $response->json();
    }

    public function createGHLNote($contact,$note){
        $api = $this->getGHLToken();
        $access_token = $api->access_token;
        $data['body'] = $note;
        $endpoint = config('app.ghl.GHL_API_ENDPOINT').'/contacts/'.$contact.'/notes';
        Http::withToken($access_token)->withHeaders([
            'Version' => "2021-04-15"
        ])->asForm()->post($endpoint, $data); 
    }
    public function createGHLOpportunity($data){
        $api = $this->getGHLToken();
        $access_token = $api->access_token;
        $data['locationId'] = $api->locationId;
        $endpoint = config('app.ghl.GHL_API_ENDPOINT').'/opportunities/';
        $response = Http::withToken($access_token)->withHeaders([
            'Version' => "2021-04-15"
        ])->asForm()->post($endpoint, $data); 
        return $response->json();
    }


    public function updateGHLOpportunity($opportunity_id,$data){
        $api = $this->getGHLToken();
        $access_token = $api->access_token;
        $endpoint = config('app.ghl.GHL_API_ENDPOINT').'/opportunities/'.$opportunity_id;
        $response = Http::withToken($access_token)->withHeaders([
            'Version' => "2021-04-15"
        ])->asForm()->put($endpoint, $data); 
        return $response->json();
    }

    public function searchGHLContactOpportunity($data){
        $api = $this->getGHLToken();
        $data['location_id'] = $api->locationId;
        $access_token = $api->access_token;
        $endpoint = config('app.ghl.GHL_API_ENDPOINT').'/opportunities/search';
        $response = Http::withToken($access_token)->withHeaders([
            'Version' => "2021-04-15"
        ])->get($endpoint,$data); 
        return $response->json();
    }

createGHLContact($data): This function is used to create a new contact in the GHL API. The function takes in an array of data that includes the details of the contact such as name, email, phone number, etc. If the request is successful, the function creates a note for the contact with the given note text using the createGHLNote function.

updateGHLContact($contact_id,$data): This function is used to update an existing contact in the GHL API. The function takes in the ID of the contact to be updated and an array of data that includes the updated details of the contact. 

createGHLNote($contact,$note): This function is used to create a new note for a contact in the GHL API. The function takes in the ID of the contact and the text of the note to be created. 

createGHLOpportunity($data): This function is used to create a new opportunity in the GHL API. The function takes in an array of data that includes the details of the opportunity such as name, description, etc. 

updateGHLOpportunity($opportunity_id,$data): This function is used to update an existing opportunity in the GHL API. The function takes in the ID of the opportunity to be updated and an array of data that includes the updated details of the opportunity.

searchGHLContactOpportunity($data): This function is used to search for a contact or opportunity in the GHL API. The function takes in an array of data that includes the search parameters such as name, email, phone number, etc. If the search is successful, the function returns the search results as an array.

Tracking Webinars/Sessions and Updating Pipeline Stage

We have synced contacts to GHL and have a list of upcoming sessions stored in our database. We need to periodically check the expired sessions, use the Demio API to get the attendance list, and based on the list, update each of the contact's stages in the pipeline accordingly. 

Let's go ahead and create the functions.

Add the following code to app/Models/Session.php:


    public function checkCompleteSessions(){
        //increase execution time to accomodate large files
		set_time_limit(2000);
        $me = new Event();
        $sessions = Session::where("status","<>","finished")->whereDate('expires_in', '<=', date("Y-m-d"))->get();
        foreach($sessions as $session){
            // add 60 minutes grace period
           $grace_period = $session->expires_in->addSeconds(3600);
           if($grace_period->gt(Carbon::now())){
                continue;
           }
            $people = $me->getDemioEventRegisteredPeople($session->event_id,"registered","",$session->id);
            $event = Event::find($session->event_id);
            $cont = 0;
            foreach($people['peopleActivity']['items'] as $person){
                // to avoid looping through all people in on-demand
                if($session->id == 1909759 && $person['person']['id'] == $session->last_person){
                    break;
                }
                $pe = People::where("uuid",$person['person']['id'])->first();
                if(!empty($pe->id) && ($pe->joined >0 || $pe->missed >1)){
                    continue;
                }
                $has_attended = $person['person']['hasAttended'];
                $contactExist=$this->lookupGHLContact($person['person']['customFields'][1]['value'],$person['person']['email']);
                if(!empty($contactExist[0])){
                    $contact_id = $contactExist[0]['id'];
                    $data['contact_id'] = $contact_id;
                    $search = $this->searchGHLContactOpportunity($data);
                    $data = [];
                    $additional = "";
                    if(!empty($search['opportunities'])){
                        $allowed_stages = [
                            config('app.ghl.GLH_REGISTERED_DEMIO_WEBINAR'),
                            config('app.ghl.GHL_MISSED_REGISTERING_FOR_DEMIO'),
                            config('app.ghl.GHL_REGISTERED_FOR_DEMIO_WAIT')
                        ];
                        if(in_array($search['opportunities'][0]['pipelineStageId'],$allowed_stages)){
                            // update opportunity
                            $opp["pipelineStageId"] = $has_attended ? config('app.ghl.GHL_JOINED_DEMIO_WEBINAR') : config('app.ghl.GHL_MISSED_DEMIO_WEBINER');
                            $opp["pipelineId"] = config('app.ghl.GHL_PIPELINE_ID');
                            $opp['name'] =  $person['person']['name'] . " " .  $person['person']['customFields'][0]['value'];
                            $opp["status"]= "open";
                            $this->updateGHLOpportunity($search['opportunities'][0]['id'],$opp);
                        }else{
                            $additional = "additional_";
                            $note = "Contact has already progressed in the pipeline, opportunity not updated";
                            $this->createGHLNote($contact_id,$note);
                        }
                    }
                    //update the notes and tags
                    if($event){
                        $data["tags"] = $contactExist[0]['tags'];
                        $data["tags"][] = $has_attended ? $additional."demio_webinar_joined-".$event->name : $additional."demio_webinar_missed-".$event->name;
                        $this->updateGHLContact($contact_id,$data);
                        $joined = $has_attended ? "Joined " : "Missed ";
                        $note = $joined.$event->name." on ".Carbon::parse(date("Y-m-d H:i:s", substr($person['session']['timestamp'], 0, 10)))->toRfc3339String();
                        $this->createGHLNote($contact_id,$note);
                        if(!empty($pe->id)){
                            if($has_attended){
                                $pe->joined = 1;
                            }else{
                                $pe->missed =1;
                            }
                            $pe->save();
                        }
                    }
                }
                $cont++;
            }
            if($cont >0){
                $session->last_person=$people['peopleActivity']['items'][0]['person']['id'];
                $session->status = "finished";
                $session->save();
            }

        }
    }

checkCompleteSessions() function periodically checks for expired webinar sessions and updates the corresponding contacts' pipeline stages in the database based on their attendance status. 

It first retrieves a list of unfinished sessions that have expired and loops through each session. It then uses the Demio API to get the list of people who registered for the session, and for each person, it checks whether they have already attended the webinar or missed it. 

If the person is registered and has not attended or missed the webinar before, the function looks up the person's contact in GHL, and if the contact is found, it searches for the corresponding opportunity in the GHL pipeline. 

If the opportunity is at one of the allowed stages (registered, missed registering, or registered and waiting), the function updates the opportunity's stage based on whether the person has attended or missed the webinar. It also updates the contact's tags and adds a note about the person's attendance status. 

If the person has already progressed in the pipeline, the function adds a note saying the opportunity was not updated. If the person has attended or missed the webinar, the function updates the corresponding record in the database to prevent redundant updates. If at least one person attended or missed the session, the session's status is changed to "finished" and the last person's ID is saved.

Scheduling the API Calls

We need a way to automate the process of syncing new registrants and updating their attendance. This is where Artisan commands come in handy.

Run the artisan commands below:


php artisan make:command checkCompleteSessions
php artisan make:command checkForNewCustomFields
php artisan make:command checkNewEvents
php artisan make:command checkNewRegistrants

Open the file app/Console/Commands/checkCompleteSessions.php and update the handle method and the signature variable as shown below:


<?php
use App\Models\Session;
class checkCompleteSessions extends Command
{
   protected $signature = 'check:session';
   public function handle()
   {
        $session = new Session();
        $session->checkCompleteSessions();
   }
}

Open the file app/Console/Commands/checkForNewCustomFields.php and update the handle method and the signature variable as shown below:


<?php
use App\Models\Session;
class checkForNewCustomFields extends Command
{
   protected $signature = 'check:custom-fields';
   public function handle()
   {
        $custom_field= new Session();
        $custom_field->checkForNewCustomFields();
   }
}

Open the file app/Console/Commands/checkNewEvents.php and update the handle method and the signature variable as shown below:


<?php
use App\Models\Event;
class checkNewEvents extends Command
{
   protected $signature = 'check:event';
   public function handle()
   {
       $event = new Event();
       $event->checkForNewDemioEvent();
   }
}

Open the file app/Console/Commands/checkNewRegistrants.php and update the handle method and the signature variable as shown below:


<?php
use App\Models\Event;
class checkNewEvents extends Command
{
   protected $signature = 'check:registrant';
   public function handle()
   {
        $event = new Event();
        $event->getNewDemioEventRegistrants();
   }
}

Register the command in the Laravel Scheduler.

 

Update the app/Console/Kernel.php schedule method with the code below:


    protected function schedule(Schedule $schedule)
    {
        $schedule->command('check:event')->hourly();
        $schedule->command('check:registrant')->everyFifteenMinutes();
        $schedule->command('check:session')->everyThirtyMinutes();
        $schedule->command('check:custom-fields')->everySixHours();
    }

Finally, add the following Cron entry to your server.


* * * * * cd /path-to-your-project && php artisan schedule:run >> /dev/null 2>&1

This Cron will call the Laravel command scheduler every minute. When the schedule:run command is executed, Laravel will evaluate the scheduled tasks and run the tasks that are due.

Testing our App

Let's go ahead and register for a webinar in our made-up event:

Registering for a webinar

Let's check if the contact has been added in GHL:

New contact in Go High level

There you go! We have managed to integrate Demio and Go High Level using Laravel.

Do you need help creating such integrations? Book a call with me today.



More in this category: How to automatically Sync your Google Sheet with Monday.com »