As part of the composable DXP initiative for Umbraco, discussed recently at Codegarden, we’ll be looking via various means to highlight the different ways applications and services can be integrated with Umbraco.
In this first blog post I’m going to look at an integration with Hubspot CRM and Umbraco Forms, showing how you can connect these two products to allow you to store information from form posts that your website visitors submit as contacts in your CRM platform.
The full source code is also available here on GitHub. We’re happy to take PRs and respond to issues, but please be aware that this isn’t a supported Umbraco product, so there are no guarantees on response time. The aim for these initial releases - an MVP in this first version - is much more to show how you are able to integrate Umbraco with other platforms. If you can use the code or package directly, that’s great. But we hope it’ll also be useful to show the techniques involved in the integration of Umbraco solutions with other platforms that support APIs.
Credit for most of the development effort and code samples shared goes to my colleague Warren Buckley, who used some of his “hacking time” from his new role at Umbraco to implement the solution shown here.
Customising Umbraco Forms
Umbraco Forms supports the concept of workflows - processes that run after a form is submitted to allow you to do more with the provided information than just store it in the database for subsequent review in the back-office. Out of the box, you can configure operations like sending an email with the information or posting it to an external URL, but workflows are something you can build yourself for the specific needs of your application.
Working with HubSpot
Hubspot provides a wealth of services related to customer relationship management, most of which are available for integration with third-party applications via a comprehensive and well-documented API. We’re only going to be working with the small part that makes sense for introducing new information to the CRM - the Contact, which stores specific information about individuals.
The use case we’re looking to meet is where an anonymous user fills out a form on our website, we want to pass the information through to HubSpot as a new contact record - that from there may be picked up as a lead for marketing or sales.
To work with the HubSpot API, you need an API key, which you can access first by registering for an account and can then create and copy a key via the Settings > Integrations > API Key menu available from the default dashboard.
Building the Custom Workflow with Umbraco Forms
Dependencies and Configuration
For this example, we’ll be working with Umbraco CMS and Forms version 8. Within Visual Studio, we’ve created a new class library targeting .NET Framework and added references to recent versions of two NuGet packages.
In a second project, we’ve created a website for testing the integration. This is a standard Umbraco CMS V8 installation, with the Umbraco Forms NuGet package added, and one additional configuration value added to the UmbracoForms.config file, for storage of the HubSpot API key:
This project references the class library where we are developing the integration, and we have a post-build script setup to copy over the client-side assets we’ll be creating.
Encapsulating the HubSpot Connectivity
Within the implementation, we’ll have two places where we want to exchange information with HubSpot:
- In the back-office, when an editor is creating a form and mapping fields from the form to those found on the HubSpot contact record, we’ll need to retrieve the full list of fields to map to.
- When the user submits the form, in our custom workflow’s execution we’ll want to send the submitted and mapped information to HubSpot.
Rather than implementing this code directly in the components responsible for these two features, we preferred to create a single service component responsible for this - allowing us to share the code, encapsulate the functionality and provide mock implementations for unit tests.
We have the following interface declaring two methods, for the two requirements we have. It uses a fairly generic name of IContactService and doesn’t depend on anything HubSpot specific, allowing us, should we want to later, to swap for different implementations with other CRM providers:
This is implemented by HubspotContactService that breaks down as follows. Firstly we inject a couple of dependencies - one for accessing Umbraco Forms configuration, and one for Umbraco’s logger.
We also declare a static HttpClient that we’ll use for making requests to the API. Access to this client is via an internal function, ClientFactory(), which we can replace in our unit tests with a client appropriately mocked for the test and avoiding an actual call to the HubSpot API.
We have a couple of helper methods, used for safely retrieving the API key from configuration and constructing the required HubSpot API URL, respectively:
The first public method implementation - to retrieve all properties available for contacts - looks as follows. You’ll see we make a GET request to the properties/contacts endpoint and, with a successful response, deserialize the content to a strongly typed object that we’ve created. We don’t need all the fields in the response, but as our object defines just the ones we need, we return only them to the calling code.
The strongly typed objects for the response and the collection of properties look as follows:
The second method implemented - to send the details to HubSpot - is similar, but requires a few more steps. Here we’re issuing a POST request in order to create a record in the HubSpot contacts database. Firstly though we need to map the fields stored in the Umbraco forms record, to those fields used by HubSpot. The information for all of this is provided in the two parameters provided to the method - the forms’ Record which stores all details of the form submission, and a collection of MappedProperty objects which are defined as follows:
Here’s the method implementation with the steps for mapping, submitting the data, and returning an appropriate response to the calling code:
Implementing the Custom Workflow
With our HubSpot functionality created and encapsulated in a service, we can make use of it in a custom Umbraco Forms workflow.
To implement a custom workflow it’s necessary to inherit from the base class WorkflowType, provided by Umbraco Forms. In the constructor we pass in the necessary dependencies - the service we’ve created and a logger, and set up some information for the workflow that will be surfaced in the back-office user interface:
We can then create one or more settings properties to allow editors to configure the workflow. In this case, we have just one, for defining the mapping to the HubSpot fields. Rather than using one of the built-in settings editors that can be used for simple things like text fields and checkboxes, we’ve defined a custom view.
A custom workflow needs to override two methods. The first provides a hook allowing us to validate the settings provided by the editor; if something is required, we would ensure it by populating the collection of Exception objects, but currently, we don’t have a need to do anything here, so can simply return an empty list:
The second method to override is where we implement the execution logic for the workflow. Because of our efforts previously in encapsulating the details of HubSpot API access, we mostly just defer to our service class here, providing the necessary information retrieved from the form submission and the field mapping, and translating the response into the appropriate return type for the workflow method:
Providing a Custom Settings Editor
As mentioned above, to provide an interface for the form creator to map between the form and HubSpot fields, we need something above and beyond the default settings editors available with Umbraco Forms, so have referenced our own custom view.
The interface looks like this:
The view and associate client-side assets will be hosted within App_Plugins at the location referenced, and for potential re-use, we’ve implemented it as a component.
hubspotfields.html looks as follows:
Which references a component defined like this:
The component provides the logic for retrieving the fields and adding the mapping logic, and depends on a template file defined as follows:
The component also depends upon various other helpers and services provided via the constructor, all of which are available from Umbraco or angularjs itself. We have one custom one, hubspot.resource.js which is used to encapsulate the API requests:
Creating the Custom Controller
Switching back server-side now, we have one more piece to build. The client-side code running in the form’s editor interface, via the resource file, needs to be able to retrieve the list of available fields from HubSpot. We can get that via a method available on the contact service we built, but we need a controller to map between the request and that service.
Again, because we’ve encapsulated the HubSpot API access in its own service, the controller becomes quite thin, taking an instance of our service class as a dependency and delegating to it:
Wiring Up Dependencies
Into the final straight, now we have all our components built we just need to do some last steps to wire everything up.
Firstly with a composer, which does two things: registers our service with the IoC container, so it can be injected into the workflow and controller and adds a new component.
The component is necessary for one final action, which is to make available the URL for the controller returning the list of HubSpot contact fields to the client-side resource that needs to call it. We do this via a standard Umbraco pattern used in CMS and other packages like Umbraco Deploy, to add a new record to the dictionary of umbracoUrls exposed to the back-office client-side code:
Testing It Out
With all that in place, we should be able to see the functionality in action.
- Via the back-office, create a new form with fields for example for first name, last name, and email address.
- Select the new workflow available in the back-office
- Create the mapping between the form fields and the Hubspot contact fields.
- Add the form to a web page.
- View the page, complete and submit the form.
As a final check, if everything is wired up and functioning correctly, you should see the information from the submitted form via your HubSpot account dashboard:
Building Via a CI/CD Pipeline
Within Umbraco we use Azure DevOps for all our CI/CD builds and releases, and within the solution, you’ll find the .yaml file used for defining the pipeline. This will restore dependencies, build the solution, run tests and finally generate NuGet and our packages.
In order to create the Umbraco package, we’ve utilised UmbPack, developed by the community package team.
We’ve shared here with this package one of the techniques available for extending Umbraco Forms - via a custom workflow type. If you are using HubSpot for a project, then hopefully this package will prove of direct use in integrating that product with Umbraco. Even if not, the methods demonstrated are applicable to many other integration scenarios, where there is a need to provide information to third-party systems with APIs from Umbraco Form submissions.