HubSpot Example

In this example we show you how you can use Marketplacer webhooks to trigger the creation of entities in HubSpot, then attach the HubSpot IDs to Marketplacer entities.

What you’ll learn

In this guide you’ll learn:

  • How to configure webhooks in Marketplacer
  • How to consume those webhooks to perform subsequent operations
  • How to attach ExternalIds to Marketplacer objects

🎥 Note: A companion video for this playbook is available where we step through the major solution components and run a demo.

Scenario

Today’s scenario illustrates a real-world example on how you can use webhooks to automate downstream operations. We’ll describe it as follows:

As a marketplace operator
I want to create a Seller in Marketplacer
And have an associated Company automatically created in my HubSpot CRM
And have the 2 entities associated with each other
So that I can more effectively manage and service that Seller

This scenario is represented by the following flows of data:


Interaction Diagram

Solution Components

Taking the core interactions illustrated in the last section, we can now map these to the solution components we’re going to use in this build.


Solution Architecture

Each of the illustrated solution components are described in more detail below:

Marketplacer

Marketplacer hosts products from multiple sellers that will be surfaced and sold on the eCommerce frontend. In this scenario we are going to:

  • Configure a Seller Create webhook - this will fire any time a new seller is created in Marketplacer
  • Call the Marketplacer Operator API externalIdUpsert mutation to attach the associated HubSpot ID to the Marketplacer Seller

Local desktop

Aside from Marketplacer and HubSpot which are hosted SaaS offerings, the remaining solution components will be hosted on a local desktop - this is primarily for reasons of simplicity. Our desktop will host:

Ngrok

Ngrok is a tool that allows you to expose an internet accessible URL to other applications, Ngrok can then forward any HTTP traffic hitting that URL to a localhost endpoint. The reason we require Ngrok today is that you cannot configure Marketplacer webhooks to target localhost endpoints directly (in this case our instance of Huginn running in Docker), so we need some other mechanism to route this traffic.

Ngrok would not typically be required in production environments, or any environment where we could expose Huginn directly to the internet.

Docker

Docker is a platform that allows you to select from thousands of pre-packaged images and run them as containers. Images are pre-configured application templates e.g. Huginn, PostreSQL, Redis etc.

The reason we are using Docker today is that it allows us to quickly and easily spin up an instance of Huginn that would otherwise be much more difficult to set up and run natively on an OS.

Huginn

Huginn, (pronounced: hoo-gin), is an open source workflow automation tool that allows you to create agents to perform discrete items of work. Agents can accept and generate events (although this depends on the type of agent you are working with). You can then chain agents together to create scenarios. In our example we will work with 3 agents of 2 different types:


All Huginn Agents


The steps identified above are:

  1. We create a Seller in Marketplacer and emit a Seller Created Webhook which is sent to:
  2. A Huginn Webhook Agent. This agent receives the payload (via Ngrok - not shown) and passes it to:
  3. A Huginn POST Agent. Using the data from the Webhook Agent, this agent is responsible for calling the HubSpot REST API to create a new Company
  4. Upon creation of a new Company in HubSpot, the HubSpot API responds with (amongst other things) the HubSpot Company ID
  5. The Huginn POST agent then emits an event with the results of the HubSpot API call to:
  6. A 2nd Huginn POST agent. Using the data from the 1st POST Agent, this agent is responsible for forming a GraphQL API call to the Marketplacer Operator API to attach an external ID to the Seller.

HubSpot

HubSpot is a cloud-based CRM (Customer Relationship Management) solution designed to help businesses manage contacts, sales, leads and other interactions. In today’s scenario we are going to create a Company entity in HubSport, which in the context of running a marketplace is analogous to a Seller (on the marketplace).

You may wish to model a seller on your marketplace as something other than a company in HubSpot (e.g. a “Contact”), but for the purposes of this guide it’s not that important.

Solution Set Up

So that we have the relevant dependencies in place, we’ll set up our systems in the following order:

  • HubSpot
  • Docker
  • Huginn - Part 1
  • Ngrok
  • Marketplacer Webhook
  • Huginn - Part 2

HubSpot

The steps here assume you already have a HubSpot account.

Create a HubSpot private app

In this section we’re going to set up the app that will allow us to create Companies via the HubSpot REST API.

  • Login to your HubSpot account and select Settings:

HubSpor Settings


  • Select: Account Management -> Integrations -> Private Apps

Private Apps


  • Select Create a private app
  • In the Basic Info tab, give your app a name and a description

Basic Info


  • Moving to the Scopes tab, select Add new scope
  • Select the following scopes and click Update when done:
    • crm.objects.companies.read
    • crm.objects.companies.write

Select App Scopes


  • Check that the following scopes have been selected:

Selected Scopes


  • Select Create App, and you should receive this warning, if you’re happy, select Continue creating:

Confirm Creation


  • You will receive notification that the app was successfully created, make sure that you copy the token provided as we’ll be using that later. Then click Close.

App Created

Testing that the app works

At this point we can now test that our HubSpot app is working. You can use any tool you like to do this (Postman, Insomnia etc.) so long as it can generate HTTP POST requests with a JSON body.

We’ll use the command line tool cUrl to do this (you may need to install this if it’s not available on your system). An example of the request we’ll send is shown below, note that you’ll need to insert the token that was generated when we set up the app in HubSpot:

curl -XPOST -H 'authorization: Bearer <INSERT YOUR TOKEN>' -H "Content-type: application/json" -d '{
  "properties": {
    "name": "Acme Corporation",
    "domain": "acme.com",
    "industry": "RETAIL",
    "phone": "555-123-4567"
  }
}' 'https://api.hubapi.com/crm/v3/objects/companies'

This makes a POST request to our app at the following endpoint:

https://api.hubapi.com/crm/v3/objects/companies

using the following body payload:

{
  "properties": {
    "name": "Acme Corporation",
    "domain": "acme.com",
    "industry": "RETAIL",
    "phone": "555-123-4567"
  }
}

If successful, you should receive a JSON payload response in the console, and navigating to Companies in HubSpot (CRM -> Companies) should reveal the company was created:

Company Created


We’re finished with our HubSpot App for the moment, but in a later section we’ll be configuring a Huginn agent to make this call.

Docker

Install Docker Desktop

First install Docker Desktop for your chosen desktop OS. We’ll not provide set up instructions here as the team at Docker have already done a great job of that.

Test Docker is running

Once you have installed Docker Desktop, check that it is running by typing the following at a command prompt:

docker ps

You should see something like this:

CONTAINER ID   IMAGE     COMMAND   CREATED   STATUS    PORTS     NAMES

Note: this command just asked Docker to list any running containers (apps), at this point you should not have any, but you should still get a non-error based response.


If this returns with an error response, then you’ve either:

  • Not installed Docker Desktop
  • Docker Desktop is not running

Important: If you experience an error here, you will need to remediate it before moving on.

Huginn - part 1

Running Huginn in Docker

As already mentioned we’re going to use Docker to host and run Huginn, specifically we are going to create a docker-compose file to contain the necessary config to get us up and running.

With this in mind, using your favorite text editor, (or if you don’t have a favorite - we’d suggest VS Code) create a new file called: docker-compose.yml and populate it as follows:

services:
  huginn:
    image: ghcr.io/huginn/huginn:latest
    restart: always
    ports:
      - "3000:3000"

This file simply tells Docker:

  • Which image to use as a template in order to create a running (container) instance of Huginn.
  • Whether the container should restart on failure
  • The local to internal port assignment of the container

Save the file in a suitable location, and at a command prompt at that location type:

docker compose up -d

Docker will then use the contents of the docker-compose.yml file to spin up an instance of Huginn.


Note: If this is the first time you are doing this, Docker will download the image from a remote image repository which depending on your network connection may take up to a few minutes.


Assuming everything is successful you should see something similar to the following:

lesjackson@Less-MacBook-Pro-6 webhook_receiver % docker compose up -d
[+] Running 1/1
 ✔ Container webhook_receiver-huginn-1  Running                            0.0s

Configuring Huginn

At a web browser on the same machine as Docker Desktop, navigate to: http://localhost:3000/


Note: You may need to wait a few minutes for Huginn to start up for the first time, so if you get a: This site can’t be reached, or something similar - just wait a few more minutes then try again.


Login with the following credentials:

  • Username: admin
  • Password: password

After logging in, select Agents from the top menu to reveal that we have some pre-configured agents - don’t get too attached as we are going to delete them:


Preconfigured agents


So that we can start from a clean slate, for each agent select Delete agent from the Actions menu as shown below:


Delete agents


Webhook agent 1

We’re going to create the first of our 3 Huginn agents, that being Webhook agent #1. This agent is responsible for receiving the webhook from Marketplacer (passed to it via Ngrok), and cascading it to the POST Agent.

Webhook Agent 1

In the Agents menu, select New Agent, then select Webhook Agent from the list as shown below:


Webhook agent


All you need to do here is give the agent a name, e.g.: Receive Seller Create Webhook then click Save.

In order get the full URL that we can target this endpoint with, we need to open it again by selecting Edit agent from the Actions menu as shown below:


Edit agent


Here you will see the generated URL that we can use for this agent:


Huginn webhook URL


To test that this is working (and receiving traffic) we can make a direct call to the endpoint again using something like Postman, Insomnia, or in our case cUrl:

curl -X POST -H 'Content-type: application/json' --data '{"text":"hello"}' http://localhost:3000/users/1/web_requests/15/bee40fe1-d0a7-4ae4-8180-60704858b664

You should get a response similar to the following:

Event Created

Looking at the agent list in the Huginn admin portal you should see that this was successful:


Successful Agent


Make a note of this URL as we’ll be using (part of) this when we come to configure the webhook in Marketplacer.

We’ll step away from Huginn for the moment, as in order to make sense of creating the remaining 2 agents, we need to work with a “real” webhook payload that is emitted from Marketplacer. Therefore, we need to move on to setting up Ngrok and the webhook in Marketplacer.

Ngrok

As with Docker, we won’t detail the full install and set up instructions for Ngrok as that is maintained and kept up to date here.

Having installed Ngrok, we simply want to start it and point it at our instance of Huginn, we do that by issuing the command below:

ngrok http --domain="my-static-domain.ngrok-free.app" http://localhost:3000

Here we are:

  • Specifying that we want to use a static domain
    • If you don’t provide this, then you will be issued with a dynamic URL - see call-out above.
  • Directing traffic to: http://localhost:3000
    • Note: that we only provide the host name and port (not any further parts of any routes)

If started successfully you should see something similar to the following:

Session Status                online                                            
Account                       Les Jackson (Plan: Free)                          
Version                       3.22.1                                            
Region                        Europe (eu)                                       
Web Interface                 http://127.0.0.1:4040                             
Forwarding                    https://my-static-domain.ngrok-free.app -> http://localhost:3000
                                                                                
Connections                   ttl     opn     rt1     rt5     p50     p90       
                              0       0       0.00    0.00    0.00    0.00  

We can now test sending traffic to Ngrok to see whether it propagates through to our Huginn webhook agent:

cUrl with Ngrok


The URL we need to provide to cUrl (and subsequently used when configuring the Marketplacer webhook) is comprised of 2 parts:

  1. The public hostname provided by Ngrok,
    • e.g.: https://my-static-domain.ngrok-free.app
  2. The route to the Huginn Webhook agent,
    • e.g.: /users/1/web_requests/15/bee40fe1-d0a7-4ae4-8180-60704858b664

So in our example the full URL would be:

https://my-static-domain.ngrok-free.app/users/1/web_requests/15/bee40fe1-d0a7-4ae4-8180-60704858b664

Plug this into cUrl (or whatever tool you are using) and make a POST request to it:

curl -X POST -H 'Content-type: application/json' --data '{"text":"hello"}' https://my-static-domain.ngrok-free.app/users/1/web_requests/15/bee40fe1-d0a7-4ae4-8180-60704858b664

You should get a response similar to the following:

Event Created

Retain this URL as we’ll be using next when we configure the webhook in Marketplacer

Assuming everything has worked as expected, we can now say that:

  • Ngrok has been configured to send traffic from its publicly exposed endpoint to Huginn
  • The Huginn Webhook Agent is working

Marketplacer webhook

We now want to set up a webhook in Marketplacer that will trigger when a seller is created. As the creation of Sellers is exclusively the remit of the Operator (and not the Seller), we’ll focus on creating the webhook in the operator portal.


This article fully covers how to create a webhook in Marketplacer so we won’t repeat all those steps in detail here. Instead, we’ll just provide the values that you need to supply when creating your webhook.

query ($id: ID!) {
  node(id: $id) {
    ... on Seller {
      __typename
      id
      businessName
      trade
      phone
      emailAddress
      metadata {
        key
        value
      }
      tags
      externalIds {
        key
        value
      }
    }
  }
}

The GraphQL query describes what attributes we want to send with the webhook, in this case we are really in the businessName as we’ll use that to create the company in HubSpot. We may use 1 or 2 of the others as well, but we’ll keep it pretty minimal for this exercise.

Creating a seller

Having created our webhook (make sure it’s enabled!) we can test it by creating a seller.

The easiest way to do this is by creating one using the Operator Portal (you could use the API as well of course), by following the steps below.

  • Login to the Operator Portal
  • Expand Sellers then select Manage Sellers and click Create New Seller

Create New Seller


The fill out the following mandatory details:

  • Business Name
  • Legal Business Name
  • First Name
  • Last Name
  • User’s Email Address
  • Address
  • Email
  • Phone (this is optional, but we can use it in our call to HubSpot)
  • Account Type (set to Retailer)

When you’re happy, click Create New Account, you should see that the new Seller has been created successfully:


Seller Created

Checking the webhook

We now want to ensure that Marketplacer emitted a webhook event. To do so, go to where you created the webhook in the Operator portal and check for the events on the webhook by selecting the “+” sign next to the webhook

In the following example using the Operator Portal we can see that a Seller Create event was emitted successfully:

Webhook Sent


We should also see an entry on the Ngrok Console representing the request:

Session Status                online                                                             
Account                       Les Jackson (Plan: Free)                                           
Version                       3.22.1                                                             
Region                        Europe (eu)                                                        
Latency                       23ms                                                               
Web Interface                 http://127.0.0.1:4040                                              
Forwarding                    https://my-static-domain.ngrok-free.app  -> http://localhost:3000
                                                                                                 
Connections                   ttl     opn     rt1     rt5     p50     p90                        
                              1       0       0.00    0.00    0.03    0.03                       
HTTP Requests                                                                                    
-------------                                                                                    
15:05:01.096 BST POST /users/1/web_requests/10/bee40fe1-d0a7-4ae4-8180-60704858b664 201 Created

And finally we can look at the Webhook Agent in Huginn to see the events it has emitted (the emitted event should match what it has received by way of inputs). You can do this by:

  • Selecting Agents from the main Huginn Portal menu
  • Clicking the Events Created number for the Webhook agent

Events Created


  • Select Show Event for the emitted event:

Show Event


And you can see the emitted event payload:


Received Event

We will refer back to this payload when we configure the next agent in Huginn, as it will be reliant upon the payload structure and the values it provides.

Huginn - part 2

We have 2 agents left to create:

  • POST Agent # 1 - Create company in HubSpot
  • POST Agent # 2 - Attach HubSpot ID to Marketplacer Seller

POST Agent #1

In this step we are going to creat the 1st POST agent:

Post Agent 1


This agent:

  1. Takes the event input from the Webhook agent
  2. Calls the HubSpot API
  3. Passed the result to the 2nd POST agent

We’ll create it as follows:

  • In the Huginn management portal, select Agents -> New Agent
  • Select Post Agent from the list of available agents

Post Agent


  • Name the agent, e.g.: Create Company in HubSpot
  • Select the Webhook Agent you created earlier as a source:

Post Agent 1 Source


We’re going to configure the Options by manipulating the underlying JSON, to do so select Toggle View to display the JSON editor, then paste in the following config:

{
  "post_url": "https://api.hubapi.com/crm/v3/objects/companies",
  "expected_receive_period_in_days": "1",
  "content_type": "json",
  "method": "post",
  "payload": {
    "properties": {
      "name": "{{payload.data.node.businessName}}",
      "domain": "test3.com",
      "industry": "RETAIL",
      "phone": "{{payload.data.node.phone}}"
    }
  },
  "headers": {
    "authorization": "Bearer <YOUR TOKEN HERE>"
  },
  "emit_events": "true",
  "parse_body": "true",
  "no_merge": "true",
  "output_mode": "merge"
}

You’ll see how this config reflects the information required of the HubSpot API. The fields of note are:

FieldDescription
post_urlHubSpot companies endpoint - used to create a company
methodSet to post
payloadThis contains the nested JSON the HubSpot API expects
payload.properties.nameThis is the businessName from the Marketplacer webhook. You can see that we navigate through the webhook payload to get to this value
payload.properties.domainHard coded website for the company
payload.properties.industryHard coded industry segment
payload.properties.phoneWe are pulling this value dynamically from the Marketplacer webhook
headers.authorizarionThis is where you need to configure the Bearer Token for the HubSpot app
emit_eventsSet to true as we want this agent to trigger our final (3rd agent)
output_modeSet to merge, this means we combine and retain the triggering payload and any new event payloads. Our 3rd Agent will be reliant upon info from both payloads (I.e. the Marketplacer webhook and the HubSpot API response)

Click save when done.

Testing

We can test what we have done so far by creating another user in Marketplacer, this is a useful exercise as it will:

  • Prove what we have done to date works
  • Provide us with an example emitted payload - we’ll need this for the final agent.

Repeat the steps to create another new seller using the Operator Portal (not shown - refer to the section above if you can remember)

Having done so check to see that this agent emitted an event, do this in the Huginn Management Portal and select Agents and you should see that you have a newly emitted event for this agent (be patient as this may take a few minutes to propagate):

Post Agent 1 event

Click on this number and show the payload, you should see something similar to this (some redundant detail has been removed):

{
  "id": "V2ViaG9va0V2ZW50LTE3MDQw",
  "event_name": "create",
  "payload": {
    "data": {
      "node": {
        "id": "U2VsbGVyLTEz",
        "tags": [],
        "phone": "",
        "trade": null,
        "metadata": [],
        "__typename": "Seller",
        "externalIds": [],
        "businessName": "Standard Equipment",
        "emailAddress": "dave.book@email.com"
      }
    }
  },
  "headers": {
    "Date": "Fri, 30 May 2025 14:14:09 GMT",
    "Content-Type": "application/json;charset=utf-8",
    "Content-Length": 795,
    "Location": "https://api.hubapi.com/crm/v3/objects/companies/163772320956",
  },
  "body": {
    "id": "163772320956",
    "properties": {
      "createdate": "2025-05-30T14:14:09.432Z",
      "domain": "test3.com",
      "hs_annual_revenue_currency_code": "USD",
      "hs_lastmodifieddate": "2025-05-30T14:14:09.432Z",
      "hs_num_blockers": "0",
      "hs_num_child_companies": "0",
      "hs_num_contacts_with_buying_roles": "0",
      "hs_num_decision_makers": "0",
      "hs_num_open_deals": "0",
      "hs_object_id": "163772320956",
      "hs_object_source": "INTEGRATION",
      "hs_object_source_id": "13368886",
      "hs_object_source_label": "INTEGRATION",
      "hs_pipeline": "companies-lifecycle-pipeline",
      "hs_task_label": "Standard Equipment",
      "industry": "RETAIL",
      "lifecyclestage": "lead",
      "name": "Standard Equipment",
      "num_associated_contacts": "0",
      "num_notes": "0",
      "phone": null,
      "website": "test3.com"
    },
    "createdAt": "2025-05-30T14:14:09.432Z",
    "updatedAt": "2025-05-30T14:14:09.432Z",
    "archived": false
  },
  "status": 201
}

You can see that we have retained the original Marketplacer webhook payload, and have merged the response from the HubSpot API. Our final agent will be interested in just 2 pieces of information:

  • payload.data.node.id: This is the Marketplacer Seller Id
  • body.id: This is the HubSpot company object Id

POST Agent #2

The final agent is used to call the Marketplacer GraphQL API and attach an external Id to the Seller object, thus ensuring we have a link between the Marketplacer Seller and the HubSpot Company entities.

Post Agent 2


We’ll create it as follows:

  • In the Huginn management portal, select Agents -> New Agent
  • Select Post Agent from the list of available agents

Post Agent


  • Name the agent, e.g.: Attach HubSpot Id to Marketplace Seller
  • Select the Post Agent you created in the last step as a source:

Post Agent 2 Source


As before, we’re going to configure the Options by manipulating the underlying JSON, to do so select Toggle View to display the JSON editor, then paste in the following config:

{
  "post_url": "https://<your-instance>.marketplacer.com/graphql",
  "expected_receive_period_in_days": "1",
  "content_type": "json",
  "method": "post",
  "payload": {
    "query": "mutation AddHubspotIdToSeller($input: ExternalIDUpsertMutationInput!){  externalIdUpsert(input: $input  ) {status errors {field messages} externalId {key      value owner {... on Seller {id legacyId updatedAt externalIds {key value}}}}}}",
    "operationName": "AddHubspotIdToSeller",
    "variables": {
      "input": {
        "owner": "{{payload.data.node.id}}",
        "key": "HubSpotId",
        "value": "{{body.id}}"
      }
    }
  },
  "headers": {
    "marketplacer-api-key": "<YOUR OPERATOR API KEY HERE>"
  },
  "emit_events": "true",
  "parse_body": "true",
  "no_merge": "true",
  "output_mode": "clean"
}

This configuration reflects the config you’ll need to pass to the Marketplacer Operator API to call the externalIdUpsert mutation. Fields of note are described below:

FieldDescription
post_urlThe GraphQL API endpoint, be sure to replace <your-endpoint> to something that resolves to your Marketplacer instance.
methodSet to post
payload.queryThis contains the mutation definition for externalIdUpsert. You can read more about this mutation here
payload.operationNameThis is the name we have given our mutation definition - see payload.query. For more information on naming GraphQL operations, refer to our Best Practices Guide
payload.variablesThese are the values that the externalIdUpsert mutation requires.
payload.variables.input.ownerThis is the Marketplacer object Id we want to attach an External Id to, in this case it’s the Seller Id retrieved from the webhook payload
payload.variables.input.keyExternal Ids require a key and a value in this case we just hard code the key
payload.variables.input.valueThis is the actual value of the External Id, in this case we are populating it from the response we received from the HubSpot API.
headers.marketplacer-api-keyYou’ll need to provide a valid Operator API key - more information on how to generate a key can be found here.
emit_eventsWhile we don’t have any further down-stream agents, we are still going to emit an event in order that we can easily observe the response we get back from the Marketplacer API.
output_modeWe set this to clean as at this point we’re only interested in the response from the Marketplacer API, and do not need to retain the prior agent event outputs

Testing

Again we can test this has worked by creating another seller Markertplacer and waiting for the events to percolate through the system.

As before you can look at the emitted event payload from the final agent by following the steps we have performed before. A successful response from the Marketplacer API is shown below:

{
  "body": {
    "data": {
      "externalIdUpsert": {
        "status": 200,
        "errors": null,                 👈 No errors
        "externalId": {                 👈 The external Id has been attached
          "key": "HubSpotId",
          "value": "163772320956",
          "owner": {
            "id": "U2VsbGVyLTEz",
            "legacyId": 13,
            "updatedAt": "2025-05-31T00:15:10+10:00",
            "externalIds": [
              {
                "key": "HubSpotId",
                "value": "163772320956"
              }
            ]
          }
        }
      }
    }
  },
  "status": 200
}

Final thoughts

  • The aim of this example was to demonstrate some core Marketplacer integration concepts, the choice of tech to perform that integration (Huginn in this case) could of course be swapped out for something else
  • The creation of a company in HubSpot is fast enough to make a call synchronously, for longer running calls (e.g. a batch type situation), rather than waiting for the response from the 3rd party (HubSpot in this case) you could employ webhooks to undertake the same type of process in an asynchronous way (assuming the 3rd party supports this).