Custom Workflow Actions Beta

Please note: This API is currently under development and is subject to change based on testing and feedback. By using these endpoints you agree to adhere to our Developer Developer Beta Terms of Use. You also acknowledge and understand the risk associated with testing an unstable API. 
 

 For the latest stable version, check out this page.

This doc describes the process for setting up a custom workflow action. See our the endpoint tab for detailed descriptions of all the available endpoints.

Creating a custom workflow action makes it easy for customers to integrate your service with HubSpot workflows. First, you define your custom action, including the expected inputs (which must be filled out by the workflow user) and the URL that will be requested when the custom action is executed. Then, customers install your application, and add your custom action to their workflows. When those workflows execute, they will send HTTPS requests to the configured URL with the payload that you configured. 


Defining your custom action

The custom action definition contains all the information needed for Workflows to display the custom action in Workflows app. The same definition also specifies the request format for requests coming from HubSpot, as well as the handling of responses from your service.

The custom action definition includes:

  • The action's name: this is the label given to the action in the Workflows app
  • The action's inputs: these are fields that can be filled out by a customer to control the action's behavior; the selected values are included in the request that is sent to the action's actionUrl
  • Input options: these define the set of valid values for the action's inputs. These are optional for each field. Either a static list or a webhook URL can be provided. If a webhook URL is provided, the options will be fetched from that URL whenever the action is edited by a customer in the Workflows tool.
  • The actionUrl: an HTTPS request is sent to this URL whenever the action is executed by Workflows. The request body will contain information about which customer the action is executing on behalf of, and what values were entered for the input fields.
  • Labels: user-facing copy that describes to the customer what the action’s fields represent and what the action does. Labels can be specified in any of the supported languages, but at a minimum English labels must be specified. Supported languages: English (en), French (fr), German (de), Japanese (ja), Spanish (es), Brazilian Portuguese (pt-br), and Dutch (nl).

Note that your app logo is used as the icon for the custom action.

Validating the request source

See this page for more details about validating requests from Hubspot. Requests made for your custom action will use the v2 version of the X-HubSpot-Signature.

Default payloads:

Field option fetch:

Requests to fetch options are made when a user is configuring your custom action in their workflow.

Request format:

JSON
//
{
  "requestOrigin": {
    // The customer's portal ID
    "portalId": 1,

    // Your custom action definition ID
    "actionDefinitionId": 2,

    // Your custom action definition version
    "actionDefinitionVersion": 3
  },

   // The values for the fields that have already been filled out by the workflow user
  "inputFields": {
    "widgetName": {
      "type": "OBJECT_PROPERTY",
      "propertyName": "widget_name"
    },
    "widgetColor": {
      "type": "STATIC_VALUE",
      "value": "blue"
    }
  }
}

Expected response format:

JSON
//
{
  "options": [
    {
      "label": "Big Widget",
      "value": "10"
    },
    {
      "label": "Small Widget",
      "value": "1"
    }
  ]
}

Execution request:

Execution requests are made when a workflow is executing your custom action against an enrolled object.

Request format: 

JSON
// 
{
  "requestOrigin": {
    // The customer's portal ID
    "portalId": 1,

    // Your custom action definition ID
    "actionDefinitionId": 2,

    // Your custom action definition version
    "actionDefinitionVersionId": 3
  },
  "object": {
    // The type of CRM object that is enrolled in the workflow
    "objectType": "CONTACT",

    // The ID of the CRM object that is enrolled in the workflow
    "objectId": 4,
  
    // The values of the properties of the CRM object that were specified
    // on the objectRequestOptions of the definition
    "properties": {
      "firstname": "Test"
    }
  },

  // The field values specified by the workflow user
  "inputFields": {
    "widgetName": "My test widget",
    "widgetColor": "blue",
    "widgetSize": "10"
  },

  // A unique ID for this execution. This can be used for idempotency to
  // deduplicate potential duplicate deliveries from workflows, and it
  // should also be used as the callbackId if your custom action will be
  // executing asynchronously
  "callbackId": "ap-123-456-7-8"
}

Customizing the payload with functions

You can customize the requests that are made for your custom action by configuring serverless functions for your custom action.

Customizing field option fetches

There are two hooks to customize the field option fetch lifecycle: PRE_FETCH_OPTIONS and POST_FETCH_OPTIONS functions. PRE_FETCH_OPTIONS allows you to configure the request that is sent from HubSpot; POST_FETCH_OPTIONS allows you to transform the response from your service into a format that is understood by Workflows.

PRE_FETCH_OPTIONS

Function input argument format:

JSON
// 
{
  "requestOrigin": {
    // The customer's portal ID
    "portalId": 1,

    // Your custom action definition ID
    "actionDefinitionId": 2,

    // Your custom action definition version
    "actionDefinitionVersion": 3
  },

  // Your configured external data field webhook URL
  "webhookUrl": "https://myapi.com/hubspot/widget-sizes",

   // The values for the fields that have already been filled out by the workflow user
  "inputFields": {
    "widgetName": {
      "type": "OBJECT_PROPERTY",
      "propertyName": "widget_name"
    },
    "widgetColor": {
      "type": "STATIC_VALUE",
      "value": "blue"
    }
  }
}

Expected Function output format:

JSON
//

{
  // The webhook URL for HubSpot to call
  "webhookUrl": "https://myapi.com/hubspot/widget-sizes",
  
  // Optional. The request body.
  "body": "{\"widgetName\": \"My new widget\"}",

  // Optional. A map of custom request headers to add.  
  "httpHeaders": {
    "My-Custom-Header": "header value"
  },

  // Optional. The Content-Type of the request. Default is application/json.
  "contentType": "application/json",

  // Optional. The Accept type of the request. Default is application/json.
  "accept": "application/json",
 
  // Optional. The HTTP method with which to make the request.
  // Valid values are GET, POST, PUT, PATCH, and DELETE.
  // Default is POST.
  "httpMethod": "POST"
}
POST_FETCH_OPTIONS

Function input argument format:

JSON
//
{
  // The requested field key
  "fieldKey": "widgetSize",

  // The webhook response body from your service
  "responseBody": "{\"widgetSizes\": [10, 1]}"
}

Expected function output format:

JSON
//
{
  "options": [
    {
      "label": "Big Widget",
      "value": "10"
    },
    {
      "label": "Small Widget",
      "value": "1"
    }
  ]
}

Customizing execution requests

There is one hook into the action execution lifecycle, a PRE_ACTION_EXECUTION function. This function allows you to configure the request that is sent from HubSpot.

Function input argument format:

JSON
//
{
  "requestOrigin": {
    // The customer's portal ID
    "portalId": 1,

    // Your custom action definition ID
    "actionDefinitionId": 2,

    // Your custom action definition version
    "actionDefinitionVersionId": 3
  },
  "object": {
    // The type of CRM object that the custom action is executing against
    "objectType": "CONTACT",

    // The ID of the HubSpot CRM object that the custom action is executing against
    "objectId": 4,
  
    // The values of the properties of the CRM object that were specified
    // on the objectRequestOptions of the definition
    "properties": {
      "firstname":  "Test"
    },
  },

  // The field values specified by the workflow user
  "inputFields": {
    "widgetName": "My test widget",
    "widgetColor": "blue",
    "widgetSize": "10"
  },

  // A unique ID for this execution. This can be used for idempotency to
  // deduplicate potential duplicate deliveries from workflows, and it
  // should also be used as the callbackId if your custom action will be
  // executing asynchronously
  "callbackId": "ap-123-456-7-8",

  // The configured action URL from your definition  
  "webhookUrl": "https://myapi.com/hubspot"
}

Expected function output format:

JSON
//
{
  // The webhook URL for HubSpot to call
  "webhookUrl": "https://myapi.com/hubspot",
  
  // Optional. The request body.
  "body": "{\"widgetName\": \"My new widget\", \"widgetColor\": \"blue\"}",

  // Optional. A map of custom request headers to add.  
  "httpHeaders": {
    "My-Custom-Header": "header value"
  },

  // Optional. The Content-Type of the request. Default is application/json.
  "contentType": "application/json",

  // Optional. The Accept type of the request. Default is application/json.
  "accept": "application/json",
 
  // Optional. The HTTP method with which to make the request.
  // Valid values are GET, POST, PUT, PATCH, and DELETE.
  // Default is POST.
  "httpMethod": "POST"
}

Publishing your custom action

By default, your custom action is created in an unpublished state, and will only be visible in the developer portal associated with your HubSpot application. To make your custom action visible to customers, update the published flag on your action definition to true.

Testing your custom action

You can test your custom action by creating a workflow in the Workflows app and adding your custom action.

Executing your custom action

An action's success is determined by examining the status code returned by your service.

  • 2xx status codes indicate the action completed successfully
  • 4xx status codes indicate the action failed
    • The exception here is 429 Rate Limited status codes; those are retreated as retries, and the Retry-After header is respected
  • 5xx status codes indicate there was a temporary problem with your service, and your action will be retried at a later time

Blocking custom action execution

Custom actions can block workflow execution. This means that instead of executing the next action (after your custom action) in the workflow after receiving a "completed" (2xx or 4xx status code) response from your service, the workflow will stop executing ("block") until you tell the workflow to continue.

Kicking off a blocking execution

To block a custom action asynchronously, your action execution response must have the following format:

JSON
//
{
  "outputFields": {
    // Required. Must be BLOCK for your custom action to block execution.
    "hs_execution_state": "BLOCK",

    // Optional. If not provided, a default expiration of 1 week is used.
    // Must be specified in ISO 8601 Duration format.
    // See https://en.wikipedia.org/wiki/ISO_8601#Durations
    "hs_expiration_duration": "P1WT1H"
  }
}

The expiration is a default duration after which your custom action will be considered "expired." At this point the execution of the workflow will resume and the action following your custom action will be executed, even though you have not told the workflow to continue.

Completing a blocked execution

To complete a blocked custom action execution, use the following endpoint: /callbacks/{appId}/{callbackId}/complete

The request body format is:

JSON
//
{
  "outputFields": {
    // Required. The final execution state. Valid values are SUCCESS
    // (to indicate that your custom action completed successfully) or
    // FAIL_CONTINUE (to indicate that there was a problem with your
    // custom action execution)
    "hs_execution_state": "SUCCESS"
  }
}

Examples:

Example 1

A basic example with a couple input fields, that is available in contact and deal workflows. There is a static input field, a dropdown field with options, a field whose value is a HubSpot owner, and a field whose value is pulled from a property (that the Workflows user selects) on the enrolled object.

 

JSON
//
{
  "actionUrl": "https://example.com/hubspot",
  "inputFields": [
    {
      "typeDefinition": {
        "name": "widgetName",
        "type": "string",
        "fieldType": "text"
      },
      "supportedValueTypes": ["STATIC_VALUE"],
      "isRequired": true
    },
    {
      "typeDefinition": {
        "name": "widgetColor",
        "type": "enumeration",
        "fieldType": "select",
        "options": [
          { "value": "red", "label": "Red" },
          { "value": "blue", "label": "Blue" },
          { "value": "green", "label": "Green" }
        ]
      },
      "supportedValueTypes": ["STATIC_VALUE"]
    },
    {
      "typeDefinition": {
        "name": "widgetOwner",
        "type": "enumeration",
        "referencedObjectType": "OWNER"
      },
      "supportedValueTypes": ["STATIC_VALUE"]
    },
    {
      "typeDefinition": {
        "name": "widgetQuantity",
        "type": "number"
      },
      "supportedValueTypes": ["OBJECT_PROPERTY"]
    }
  ],
  "labels": {
    "en": {
      "actionName": "Create Widget - Example 1",
      "actionCardContent": "Create widget {{widgetName}}",
      "inputFieldLabels": {
        "widgetName": "Widget Name",
        "widgetColor": "Widget Color",
        "widgetOwner": "Widget Owner",
        "widgetQuantity": "Widget Quantity"
      }
    }
  },
  "objectTypes": ["CONTACT", "DEAL"]
}

Example 2:

A custom action that uses a serverless function to transform the payload that is sent to the configured actionUrl. Since no objectTypes were specified, it is available in all types of workflows.

JSON
//
{
  "actionUrl": "https://api.example.com/v1/widgets",
  "inputFields": [
    {
      "typeDefinition": {
        "name": "widgetName",
        "type": "string",
        "fieldType": "text"
      },
      "supportedValueTypes": ["STATIC_VALUE"],
      "isRequired": true
    }
  ],
  "labels": {
    "en": {
      "actionName": "Create Widget - Example 2",
      "actionCardContent": "Create widget {{widgetName}}",
      "inputFieldLabels": {
        "widgetName": "Widget Name"
      }
    }
  },
  "functions": [
    {
      "functionType": "PRE_ACTION_EXECUTION",
      "functionSource": "exports.main = function(event, callback) { return callback(transformRequest(event)); }\nfunction transformRequest(request) { return { webhookUrl: request.webhookUrl, body: JSON.stringify(request.fields), contentType: 'application/x-www-form-urlencoded', accept: 'application/json', httpMethod: 'POST' }; }"
    }
  ]
}

Example 3

A custom action with field dependencies and options that are fetched from an API. Because the widget size depends on the widget color, the Workflows user won't be able to input a value for the widget size until a widget color is chosen. The widget cost is also dependent on the widget color, but it is conditional on the value that the user selects for the widget color; the Workflows user won't be able to input a value for the widget cost unless "Red" is selected as the widget color.

JSON
//
{
  "actionUrl": "https://example.com/hubspot",
  "inputFields": [
    {
      "typeDefinition": {
        "name": "widgetName",
        "type": "string",
        "fieldType": "text"
      },
      "supportedValueTypes": ["STATIC_VALUE"],
      "isRequired": true
    },
    {
      "typeDefinition": {
        "name": "widgetColor",
        "type": "enumeration",
        "fieldType": "select",
        "options": [
          { "value": "red", "label": "Red" },
          { "value": "blue", "label": "Blue" },
          { "value": "green", "label": "Green" }
        ]
      },
      "supportedValueTypes": ["STATIC_VALUE"],
    },
    {
      "typeDefinition": {
        "name": "widgetSize",
        "type": "enumeration",
        "fieldType": "select",
        "optionsUrl": "https://api.example.com/v1/widget-sizes"
      },
      "supportedValueTypes": ["STATIC_VALUE"]
    },
    {
      "typeDefinition": {
        "name": "widgetCost",
        "type": "number",
        "fieldType": "number"
      },
      "supportedValueTypes": ["OBJECT_PROPERTY"]
    }
  ],
  "inputFieldDependencies": [
    {
      "dependencyType": "SINGLE_FIELD",
      "controllingFieldName": "widgetColor",
      "dependentFieldNames": ["widgetSize"]
    },
    {
      "dependencyType": "CONDITIONAL_SINGLE_FIELD",
      "controllingFieldName": "widgetColor",
      "controllingFieldValue": "red",
      "dependentFieldNames": ["widgetCost"]
    }
  ],
  "labels": {
    "en": {
      "actionName": "Create Widget - Example 3",
      "actionCardContent": "Create widget {{widgetName}}",
      "inputFieldLabels": {
        "widgetName": "Widget Name",
        "widgetColor": "Widget Color",
        "widgetSize": "Widget Size",
        "widgetCost": "Widget Cost"
      }
    }
  },
  "objectTypes": ["CONTACT", "DEAL"],
  "functions": [
    {
      "functionType": "PRE_FETCH_OPTIONS",
      "id": "widgetSize",
      "functionSource": "exports.main = function(event, callback) { return callback(transformRequest(event)); }\nfunction transformRequest(request) { return { webhookUrl: request.webhookUrl + '?color=' + request.fields.widgetColor.value, body: JSON.stringify(request.fields), httpMethod: 'GET' }; }"
    }
  ]
}

Example 4

A blocking custom action. The callbacks API can be used to tell Workflows to complete the action and have the enrolled object continue to the next action in the workflow. Note that you don't have to specify that the action blocks at the time that you create the action; that is determined by the response from your configured actionUrl.

JSON
//
{
  "actionUrl": "https://example.com/hubspot",
  "inputFields": [
    {
      "typeDefinition": {
        "name": "moonPhase",
        "type": "enumeration",
        "fieldType": "select",
        "options": [
          { "value": "full_moon", "label": "Full" },
          { "value": "half_moon", "label": "Half" },
          { "value": "quarter_moon", "label": "Quarter" },
          { "value": "new_moon", "label": "New" }
        ]
      },
      "supportedValueTypes": ["STATIC_VALUE"]
    }
  ],
  "objectRequestOptions": {
    "properties": ["email"]
  },
  "labels": {
    "en": {
      "actionName": "Wait For Moon Phase",
      "actionCardContent": "Wait until a {{moonPhase}} moon",
      "inputFieldLabels": {
        "widgetName": "Moon Phase"
      }
    },
    "fr": {
      "actionName": "Attendez la phase lunaire",
      "actionCardContent": "Attendez la lune {{moonPhase}}",
      "inputFieldLabels": {
        "widgetName": "Phase de lune"
      }
    }
  },
  "objectTypes": ["CONTACT"]
}