Node.js: Create and Test Your First App using Pipedream

Get started on your first ActiveCampaign CX App using Node.js

App Studio is used to build native applications and integrations for the ActiveCampaign platform. We call them CX Apps. CX is short for "Customer Experience." A CX App built with App Studio can be published privately (for internal use by your organization) or publicly in the ActiveCampaign App Ecosystem.

In this tutorial, you will learn how to use App Studio to build an outbound CX App and test the app using Pipedream. Pipedream is a powerful serverless platform for connecting systems. Pipedream can be used to prototype APIs, test integrations, host webhooks, or build production apps and integrations. Outbound CX Apps are used as actions in Automations to send data from ActiveCampaign to other systems.

Tutorial Overview

In this tutorial, you will learn to create the following.

  1. Create a mocked API in Pipedream using Node.js and JavaScript. Your API will authenticate a login, return data to populate a dynamic list of options, and receive contact information from ActiveCampaign.
  2. Create an app in App Studio. You will use create a JSON configuration file that describes how ActiveCampaign will interact with your API.
  3. Create an Automation to test the app. By creating an Automation, you will see how an ActiveCampaign customer would configure your app, and you will be able to inspect the data that is sent from ActiveCampaign to the API as part of the app lifecycle.

Prerequisites

Create an App in App Studio

Login to your ActiveCampaign sandbox account. Click Apps and then App Studio. Click Build a New App.

1596

App Studio, Build a New App

Complete the Create New App form using your email address and click Submit.

1854

Create New App form

Click the Home tab. Click Start Building.

1640

Start building

CX Apps built with App Studio are configured in the App Configuration Editor. An app config is a JSON document that conforms to the App Studio Configuration Specification.

2168

App Studio Editor

Create a Pipedream Workflow

In this step, you will use Pipedream to create a mock API for your CX App to connect and interact. First, create a new workflow in Pipedream. This workflow will become the API for your CX App.

2264

Create a new workflow in Pipedream

Next, select the HTTP / Webhook Requests trigger for the workflow.

2470

Click HTTP / Webhook Requests trigger

Edit the trigger to Return a custom response from your workflow

You now have a URL you can use for your App, but it does not do anything, yet. Click on the plus (+) to add a step to the workflow.

2557

Add step

Click on Run custom code.

2552

Click Run custom code

Enter the following code inside the existing async code block.

    await $.respond( {
    status: 200,
    body: {
      id: "123472",
      name: "Taylor Jones"
    }
  });

Your Pipedream step should now look like the following.

export default defineComponent({
  async run({ steps, $ }) {
      await $.respond( {
    status: 200,
    headers: {"Content-Type": "application/json"},
    body: {
      id: "123472",
      name: "Taylor Jones"
    }
  });
    return steps.trigger.event
  },
})

Next, test the API code you just added. Click the Test button to validate there are no errors, and then the Deploy button. Copy the URL assigned to this Pipedream URL. Open another browser window or tab, paste the URL, and press Enter.

2576

Test and Deploy your changes

📘

Remember to deploy any change you make in Pipedream!

You should see the following data returned in your browser window. This response is an example of what ActiveCampaign expects to receive when authenticating with your API.

{"id":"123472","name":"Taylor Jones"}

Next, complete the Pipedream API. Copy and replace everything in the Pipedream workflow with the following code, and click Save.

export default defineComponent({ props: {
    username: {
      type: "string",
      label: "Username",
    },
    password: {
      type: "string",
      label: "Password",
      secret: true
    },
    accountId: {
      type: "string",
      label: "AccountId",
    },
    name: {
      type: "string",
      label: "Name",
    }
  },
  async run( { steps, $ } ) {

    async function unauthorized( msg ) {
      await $.respond({
        status: 401
      });
      // log the error message
      $.flow.exit(msg)
    }

    // Validate Basic Authentication
    function validateBasicAuth( authorization, username, password ) {
      if (!authorization) {
        return {
          isValid: false,
          error: "No Authorization header present"
        };
      }
      const auth = Buffer.from(authorization.split(" ")[1] || "", 'base64').toString();
      if (!auth) {
        return {
          isValid: false,
          error: "No data in Authorization header"
        };
      }
      const [user, pass] = auth.split(":")
      if (user !== username || pass !== password) {
        return {
          isValid: false,
          error: "Username or password does not match"
        };
      }
      return {
        isValid: true,
        error: ""
      };
    }

    // Get the path from the raw URI
    const path = steps.trigger.event.path;
    const method = steps.trigger.event.method;
    const authHeader = steps.trigger.event.headers.authorization;

    const { isValid, error } = await validateBasicAuth(authHeader, this.username, this.password);

    if (!isValid) {
      return unauthorized(error);
    }

    // GET /me request handler
    if ( path === "/me" && method === "GET" ) {
      return await $.respond({
        status: 200,
        body: {
          id: this.accountId,
          name: this.name
        }
      });
    }

    // GET /options request handler
    if ( path === "/options" && method === "GET" ) {
      return await $.respond({
        status: 200,
        body: {
          options: [{
            id: 1,
            title: "Option 1"
          }, {
            id: 2,
            title: "Option 2"
          }, {
            id: 3,
            title: "Option 3"
          }]
        }
      });
    }

    // GET /fields request handler
    if ( path === "/fields" && method === "GET" ) {
      return await $.respond( {
        status: 200,
        body: {
          fields: [ {
              field_id: "first_name",
              label: "First Name"
            }, {
              field_id: "last_name",
              label: "Last Name"
            }, {
              field_id: "email_address",
              label: "Email Address"
            }, {
              field_id: "phone_number",
              label: "Phone Number"
            }, {
              field_id: "custom_field_1",
              label: "Custom Field 1"
            }, {
              field_id: "custom_field_2",
              label: "Custom Field 2"
            }
          ]
        }
      } );
    }

    // POST /contact request handler
    if (path === "/contact" && method === "POST") {
      return await $.respond({
        status: 200,
        body: steps.trigger.event.body
      });
    }

    // Otherwise respond with a 404 - Not found
    await $.respond({
      status: 404
    });
    return $.flow.exit(`404 - ${ steps.trigger.event.method } ${ steps.trigger.event.url }`);

  },
})

If you are familiar with JavaScript or other programming languages, you may be able to follow the code logic. However, it's totally okay if you don't understand all of the code. The features of the API include the following.

  • The API uses Basic Authentication to validate every request. Any request that does not include a valid username and password will be rejected with a standard HTTP 401 error.
  • An API request sent to /me will return a JSON object that includes an account ID and username.
  • An API request sent to /options will return a JSON object that includes an array of options.
  • An API request sent to /fields will return a JSON object with a fields array for the mapping step.
  • JSON data sent to /contact will respond with the same data.

You should now see a section named Configuration on the left. You can enter any values you wish into these fields. For example, the values you enter for the Username and Password will become the credentials you must enter when you test your CX App in ActiveCampaign. The AccountId and Name are the values the /me API will return with a successful authentication. When you're finished editing the Configuration, click Deploy.

2582

Pipedream workflow params

Update the App Config in App Studio

Copy the following JSON and replace the JSON currently in the App Configuration Editor.

{
  "$version": "2",
  "api": {
    "base_url": "{{your-pipedream-url}}"
  },
  "auth": {
    "basic-auth": {
      "type": "basic",
      "verify_url": "{{your-pipedream-url}}/me",
      "defined_fields": {
        "username": {
          "label": "Username",
          "placeholder": "Enter the username for your account",
          "help_text": "This is the username you entered as a param in Pipedream"
        },
        "password": {
          "label": "Password",
          "placeholder": "Enter your password",
          "help_text": "This is the password you entered as a param in Pipedream"
        }
      }
    }
  },
  "workflows": [
    {
      "name": "send-a-contact-to-pipedream",
      "label": "Send a contact to Pipedream",
      "description": "Example of sending a contact to Pipedream",
      "type": "automations",
      "auth": "basic-auth",
      "setup": {
        "connect": {
          "label": "Connect",
          "describe_connection": {
            "!pipe": [
              {
                "!http": {
                  "method": "GET",
                  "path": "/me",
                  "headers": {
                    "accept": "application/json"
                  }
                }
              },
              {
                "!jq": "{ account_id: .id, description: .name }"
              }
            ]
          }
        },
        "select": {
          "label": "Outbound Test Settings",
          "form_fields": [
            {
              "id": "dropdown-example",
              "label": "Dropdown Example",
              "type": "dropdown",
              "required": true,
              "options": {
                "!pipe": [
                  {
                    "!http": {
                      "method": "GET",
                      "path": "/options"
                    }
                  },
                  {
                    "!jq": ".body.options | map({ display: .title, value: .id | tostring })"
                  }
                ]
              }
            },
            {
              "id": "multiselect-example",
              "label": "Multiselect Example",
              "type": "multiselect",
              "required": false,
              "options": {
                "!jq": "[ { display: \"Option A\", value: \"A\" }, { display: \"Option B\", value: \"B\" }, { display: \"Option C\", value: \"C\" } ]"
              }
            },
            {
              "id": "textarea-example",
              "label": "Textarea Example",
              "type": "textarea",
              "required": false,
              "personalize": "ActiveCampaignContact",
              "placeholder": "Enter your text here"
            }
          ]
        },
        "map": {
          "label": "Mapping",
          "describe_source": {
            "label": "ActiveCampaign",
            "options": {
              "!resource": "ActiveCampaignContact.fields"
            }
          },
          "describe_target": {
            "label": "Pipedream Contact",
            "options": {
              "!pipe": [
                {
                  "!http": {
                    "method": "GET",
                    "path": "/fields"
                  }
                },
                {
                  "!jq": "[\"first_name\", \"email_address\"] as $required | .body.fields | map( .field_id as $id | { id: .field_id, title: .label, required: (if ($required | index($id)) == null then false else true end)})"
                }
              ]
            }
          }
        }
      },
      "data_pipeline": {
        "source": {
          "!resource": "ActiveCampaignContact"
        },
        "target": {
          "!pipe": [ 
            {
              "!http": {
                "method": "POST",
                "path": "/contact",
                "body": {
                  "custom_data": "${custom_data}",
                  "dropdown-value": "${custom_data.dropdown-example.value}",
                  "textarea-value": "${custom_data.textarea-example.value | default}",
                  "multiselect-value": "${custom_data.multiselect-example | default}",
                  "contact": "${piped_content.0}",
                  "source_data": "${source_data}"
                }
              }
            }
          ]
        }
      }
    }
  ]
}

In the app config, there are two placeholders currently set to {{your-pipedream-url}}. Go to your Pipedream workflow and copy the URL assigned to your workflow. Replace the two placeholders in App Studio with your personal Pipedream URL.

When you have finished updating the JSON in the App Configuration Editor, click Save and click Test App in Sandbox.

2168

Edit, save, and test app

When you click the Test App in Sandbox button, you will be directed to your CX App's connections page. The first time you do this, there will be no connections listed. Once a connection is established you can return here to view a list of active connections. You can also find usage logs for your connected CX App here.

2118

List of existing connections and usage logs

📘

Any time you make a change in App Studio, click both the Save and the Test app in sandbox buttons. If you have your CX App open in another browser tab or window, refresh the page to ensure you are seeing the latest version of your app.

Create a New Automation

So far, you have created an API in Pipedream and CX App using App Studio. The next step is to create an Automation you will use to test the CX App. Click the Automations link in the navigation menu and then click the Create an Automation button.

2238

Create a new automation

Click on the Start from Scratch option then click the Continue button.

1838

Start from Scratch

Click Start without a trigger. You will manually add a Contact to the Automation when you are ready to test it.

1558

Start without a trigger

Click the + button to add a new action to the Automation.

756

Click the + button

Click on the CX Apps tab and then click on your CX App.

1728

Add a new CX App action using your CX App

When your CX App is added to the Automation you will need to establish a connection. This connection will use the authentication scheme defined in the auth section of your CX App's config file. Enter the same Username and Password you set in the Pipedream workflow params section. Click the Connect button.

600

Enter CX App credentials

The username and password will be sent to your Pipedream API to verify it is valid. If the username and password do not match what is stored in Pipedream, you will see an error and you may retry the login.

Next, you will configure your outbound workflow. In the app config, three form fields were defined.

  • A drop-down list that is populated by making a call to your /options API endpoint.
  • A multi-select list populated with a static list of options.
  • A textarea field that can be personalized with contact data.

Pipedream The data entered here will be sent to your Pipedream API when the CX App's outbound workflow is executed. These fields were defined on the select object for the workflow in the App Config file.

600

CX App select step

Next, you will configure the mapping of ActiveCampaign fields to the outbound system. These fields were defined in the map object for the workflow in the App Config file. Mappings are optional, but this example is designed so you can see how the mappings work.

600

CX app mapping step

Enter a name for your automation then click the Save button. Then click the Active button before testing.

2104

Name and save the automation

Test the Automation Using a Contact

Now that the Automation has been created and set to Active you can add a Contact to the Automation to test the CX App. Click on Contacts and then click on a Contact to go to the Contact Details page.

2330

Contacts page and Contact selection

In the Details page for your Contact click the + Add button next to Automations. Select the Automation that you just created and then click Okay.

1880

Add the Contact to the Automation

Once the Contact has been added to the Automation your workflow should have executed. Navigate back to your CX App's Connections page by clicking on Apps and then clicking on Connected Apps. In your CX App's Connections page click View Logs. You should see a log entry for the Contact that you added to the Automation in the last step.

2372

View logs for your CX app

Back in Pipedream you can also view the data that was sent to your API when the outbound workflow was executed.

1952

If you have made it this far, congratulations! You have successfully created and tested a CX App using App Studio and Pipedream!

Next Steps

Feel free to make changes to your CX App configuration, such as lists and textarea, and re-run the Automation as many times as you wish. See how those changes affect the data posted to Pipedream. Explore and tweak the API code and the app config and see how that affects the CX App.

Pipedream is a powerful platform for building custom integrations. You may want to use Pipedream as the "glue" between ActiveCampaign and another system you wish to integrate. Or, continue to use Pipedream to test and mock any part of a CX App you are building whenever you need to inspect requests or data sent from ActiveCampaign.