In questa nuovo articolo vedremo come mantenere sincronizzati i contatti di un ipotetico gestionale o CRM sviluppato in WinterCMS con i contatti della piattaforma Hubspot che per chi non la conoscesse è un software di Inbound Marketing e Sales per ottimizzare e automatizzare tutte le attività di marketing e vendita.
Questo articolo vuole mostrare come è possibile utilizzare i due eventi beforeCreate e beforeUpdate interagendo tra le due piattaforme (WinterCMS e Hubspot), quindi sono considerati come prerequisti il possesso di una API KEY di una app di Hubspot, un'entità di contatti su Winter preesistente e la disponibilità e la conoscenza base della libreria Guzzle (facilmente installabile tramite composer) per le chiamate alle API di Hubspot.
Come procurarsi un api key di Hubspot
Per ottenrer un'api key su Hubspot è molto semplice, una volta creato un account sviluppatore potremo creare una account di test dove fare le nostre prove come se fosse un account di un piano reale di un ipotetico utente.
Una volta creato l'account di test è sufficiente andare nelle impostazioni dell'account tramite l'icona a forma di ingranaggio presente in alto a destra poi aprire il menù alla voce 'integrazioni' sul menu di sinistra e selezionare 'app private' quindi creare un'app assegnado gli scopes che riteniamo ci servano, per lavorare con i contatti come descritto in questo articolo lo scopes necessario è 'crm.objects.contacts' al quale potete aggiungere tutti quelli che ritenete opportuni. Per approfondimenti sulle app private rimando alla documentazione ufficiale disponibile qui
Mettiamo il caso che abbiamo appena pubblicato la nostra fanastica landing page che raccoglierà contatti oppure che abbiamo il nostro bel gestionale o CRM su misura e ora dal reparto marketing ci chiedono di poter avere da ora in avanti (per importazioni massive di contatti già presenti vedremo in seguito) tutti i contatti inseriti nel CRM disponibili su Hubspot per le loro attività.
Per fare questo noi andremo ad intercettare l'inserimento degli utenti del CRM e con gli stessi dati immessi creeremo un contatto su Husbspot.
La API di Hubspot ci risponderà con i dati dell'utente inserito e il rispettivo id utente (contatto di hubspot) che salveremo nel record dell'utente del CRM e che ci servirà dopo per gli eventuali aggiornamenti dell'utente.
Quindi abbiamo tre fasi da gestire:
- Creazione nuovo utente su Hubspot quando viene creato su Winter
- Inserimento dell'id del contatto di Hubspot sull'utente di Winter
- Aggiornamento del contatto su Hubspot quando viene modificato l'utente su Winter
Creazione dell'utente e del contatto
In realtà i punti uno e due si risolvono nello stesso momento in quanto noi andremo a intercettare la creazione del nuovo utente prima che venga creato sul CRM tramite il metodo beforeCreate disponibile nella classe del model di Winter che viene chiamato prima che venga creato il model, all'interno del metodo abbiamo disponibili tutti i dati inseriti nel form di backend di Winter nell'oggetto $this che rappresenta l'istanza del model che stiamo creando quindi facciamo la chiamata all'endpoint di Hubspot passandogli i dati che abbiamo, lui risponderà con un oggetto json contenente i dati dell'utente appenda creato, sarà qualcosa di questo tipo:
namespace Mio\Crm\Models;
use Model;
use GuzzleHttp\Client;
class Customer extend Model
...
...
public function beforeCreate()
{
// Istanza del client Guzzle
$client = new Client();
$url = "https://api.hubspot.com/crm/v3/objects/contacts";
$token = 'ilmiotokensegreto';
$headers = [
'Authorization' => 'Bearer ' . $token,
'Content-Type' => 'application/json',
'Accept' => 'application/json',
];
$body = [
'headers' => $headers,
'body' => json_encode([
'properties' => [
'firstname' => $this->name,
'lastname' => $this->surname,
'email' => $this->email
]
]),
];
/* Qui facciamo la chiamata all'endpoint, $response sarà
* un'istanza di \GuzzleHttp\Psr7\Response
*/
$response = $client->post($url, $body);
/* estraiamo il body dal response del client convertendolo da json a oggetto così
* l'oggetto $hsData conterrà tutti i campi ritornati da Hubspot
*/
$hsData = json_decode($response->getBody(), false);
// assegnamo al campo hs_id il valore 'id' del contatto Hubspot
$this->hs_id = $hsData->id;
}
L'oggetto ritornato da Hubspot sarà una cosa del genere:
{
"id": "201",
"properties": {
"company": "Concreta09",
"createdate": "2022-07-04T10:17:18.912Z",
"email": "info@concreta09.com",
"firstname": "Luca",
"hs_all_contact_vids": "201",
"hs_email_domain": "concreta09.com",
"hs_is_contact": "true",
"hs_is_unworked": "true",
"hs_marketable_status": "false",
"hs_marketable_until_renewal": "false",
"hs_object_id": "201",
"hs_pipeline": "contacts-lifecycle-pipeline",
"hs_searchable_calculated_phone_number": "134567890",
"lastmodifieddate": "2022-07-04T10:17:18.912Z",
"lastname": "Benati"
},
"createdAt": "2022-07-04T10:17:18.912Z",
"updatedAt": "2022-07-04T10:17:18.912Z",
"archived": false
}
Il campo hs_id che abbiamo utilizzato nel model dell'entità dell'ipotetico CRM è stato aggiunto preventivamente, nel caso dovessimo operare su un model di un plugin esterno come potrebbe essere il plugin degli utenti di frontend Winter.User dovremo prima procedere come abbiamo visto in questo articolo su come estendere i plugin di terze parti dal nostro creando ed eseguendo una migration che vada ad alterare la tabella originale.
Aggiornamento del contatto
Ora andiamo a fare allo stesso modo l'aggiornamento del contatto quando andremo a modificare un utente del CRM, in modo analogo al metodo beforeCreate, che viene invocato solo alla creazione di un nuovo model, utilizzeremo il metodo beforeUpdate che invece viene invocato tutte le volte che il model viene aggiornato, fondamentalmente l'unica cosa che cambia è che l'endpoint conterrà l'id del contatto di Hubspot da aggiornare come ultimo segmento e passandogli tutti i campi che vogliamo mantenere sincronizzati nelle properties il record verrà aggiornato di conseguenza, in questo caso anche se l'API risponderà sempre con i dati salvati e l'id del contatto a noi non serviranno, quindi faremo una cosa del genere:
namespace Mio\Crm\Models;
use Model;
use GuzzleHttp\Client;
class Customer extend Model
...
...
public function beforeSave()
{
// Istanza del client Guzzle
$client = new Client();
// Aggiungiamo l'id del contatto di Hubspot all'endpoint
$url = "https://api.hubspot.com/crm/v3/objects/contacts/" . $this->hs_id;
$token = 'ilmiotokensegreto';
$headers = [
'Authorization' => 'Bearer ' . $token,
'Content-Type' => 'application/json',
'Accept' => 'application/json',
];
$body = [
'headers' => $headers,
'body' => json_encode([
'properties' => [
'firstname' => $model->name,
'lastname' => $model->surname
]
]),
];
/* Qui facciamo la chiamata all'endpoint, $response sarà
* un'istanza di \GuzzleHttp\Psr7\Response
*/
$response = $client->post($url, $body);
}
In questo caso quando andremo a correggere il nome o i cognome dell'utente su Winter verrà aggironato di conseguenza anche il contatto su Hubspot.
Naturalmente questi sono solo esempi di come è possibile utilizzare i due eventi su un model di WinterCMS, non è certo codice che possa andare in produzione, ad esempio bisogna gestire le eccezioni in caso le chiamate alle API non vadano a buon fine, la separazione delle responsabilità ecc, più che altro volevo mostrare l'uso del client http Guzzle interponendosi tra la creazione e i salvataggi dei model.
In un caso d'uso reale di integrazione con Hubspot è sicuramente meglio utilizzare il client sviluppato da Hupspot stessa che mette a disposizione dei metodi già pronti molto comodi per gestire tutte le chiamate alle proprie API, dei middelware per la gestione dei rate limit che vengono applicati ecc lo is può trovare su github a questo link (hubspot-api-php), oltretutto è anche possibile istanziarlo passandogli un'istanza personalizzata del client di Guzzle il che lo rende veramente molto versatile e personalizzabile.
Per una vera sincronizzazione bidirezionale inoltre dovremo anche aggiornare il model su Winter nel caso venisse modificato su Hubspot, per fare questo ci si può avvalere dei webhooks di Hupspot che invia i dati al nostro CRM che si occuperà a sua volta di aggiornare il model in questione magari passando per un Job messo in coda come spiegato in questo articolo sull'uso delle code per invii massivi di email su WinterCMS
Come abbiamo visto Winter ci mette come sempre a disposizione dei metodi molto comodi per manipolare il workflow dei nostri plugin senza farci perdere tempo in implementazioni ripetitive e lasciandoci invece liberi di dedicarci alle logiche di business necesarie al nostro prodotto.
Per ora è tutto, ma ritorneremo sicuramente su questo argomento più avanti per approfondirlo e ampliarlo in contesti un po' più complessi.
Happy coding!
Lunga vita e prosperità