Lead Generation Flow

The Lead Generation Form Flow can be used to collect information gathered from potential customers about a business offering (in this example, a gym is looking for customers to experience a new season of exercise classes).

The Flow is designed to present a number of classes based on the user’s interests and to allow the user to have the option to be notified if new classes matching their interests become available.

From a technical perspective this Flow demonstrates how to connect a business endpoint to send and receive data, and to modify which components to display based on the data received.

This Flow also demonstrates how to use dynamic components to introduce dependencies between questions (for example, the Flow will prompt the user to indicate if they want Gym Induction Classes to be included in the results only if they select ‘beginner’ as class level).

This example tutorial breaks out the wrapper scaffolding code from the screen code. However, you should combine all the code in these examples using the Form Builder.

Wrapper Code

Include the following code to "wrap" the 3 screens of the Lead Generation Form (Questions, Gym Classes, and Privacy Policy):

{
    "version": "2.1",
    "data_api_version": "3.0",
    "data_channel_uri": "https://hissing-lava-daffodil.glitch.me/data",
    "routing_model": {
        "QUESTIONS": [
            "SELECTIONS"
        ],
        "PRIVACY_POLICY": [
            "SELECTIONS"
        ],
        "SELECTIONS": []
    },
    "screens": [
  /* screens we'll define in the next sections */
    ]
}

The routing model (required for Flows using an endpoint) defines which screens are allowed transitions between screens.

Questions Screen

{
    "id": "QUESTIONS",
    "title": "Your Preferences",
    "data": {
        "include_gym_induction_visible": {
            "type": "boolean",
            "__example__": false
        }
    },
    "layout": {
        "type": "SingleColumnLayout",
        "children": [
            {
                "type": "Form",
                "name": "questions_form",
                "children": [
                    {
                        "type": "CheckboxGroup",
                        "name": "days_of_week",
                        "label": "Preferred days for training:",
                        "required": true,
                        "data-source": [
                            {
                                "id": "weekends",
                                "title": "Weekends"
                            },
                            {
                                "id": "weekdays",
                                "title": "Week days"
                            }
                        ]
                    },
                    {
                        "type": "CheckboxGroup",
                        "name": "class_level",
                        "label": "Class level:",
                        "required": true,
                        "on-select-action": {
                            "name": "data_exchange",
                            "payload": {
     "class_level": "${form.class_level}",
                                "include_gym_induction_classes": "${form.include_gym_induction_classes}",
                                "refresh_questions_page": true
                            }
                        },
                        "data-source": [
                            {
                                "id": "beginner",
                                "title": "Beginner"
                            },
                            {
                                "id": "intermediate",
                                "title": "Intermediate"
                            },
                            {
                                "id": "advanced",
                                "title": "Advanced"
                            }
                        ]
                    },
                    {
                        "type": "OptIn",
                        "label": "Include gym induction classes.",
                        "name": "include_gym_induction_classes",
                        "visible": "${data.include_gym_induction_visible}"
                    },    
                    {
                        "type": "Footer",
                        "label": "Search Classes",
                        "on-click-action": {
                            "name": "data_exchange",
                            "payload": {
                                "days_of_week": "${form.days_of_week}",
                                "class_level": "${form.class_level}",
                                "include_gym_induction_classes": "${form.include_gym_induction_classes}"
                            }
                        }
                    }
                ]
            }
        ]
    }
}

The Form component organizes all the input fields on the page and sets the footer CTA to be disabled until all required fields have been completed.

The form uses on-select-action on the class_level CheckBoxGroup to dynamically determine if the include_gym_induction_classes components need to be shown or not.

The form also introduces a discriminating property (refresh_questions_page) for the endpoint to be able to distinguish between this refresh request and the one fired from the CTA at the end of the screen. The form's init-values property allows the server to restore the right user’s answers when the screen is refreshed.

The visible property determines if a component needs to be shown or not, based on a dynamic condition (which the endpoint can control) .

Gym Classes Screen

{
    "id": "SELECTIONS",
    "title": "Classes for you",
    "data": {
        "classes_selection": {
            "type": "array",
            "items": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "string"
                    },
                    "title": {
                        "type": "string"
                    },
                    "description": {
                        "type": "string"
                    }
                }
            },
            "__example__": [
                {
                    "id": "base_bootcamp",
                    "title": "Basic Bootcamp",
                    "description": "Mon 10am - Modified version of bootcamp that focuses on building endurance and strength through basic exercises."
                },
                {
                    "id": "ashtanga_yoga",
                    "title": "Ashtanga Yoga",
                    "description": "Sun 2pm - Fast-paced style of yoga that involves a series of poses linked by a specific breathing technique to build strength, flexibility, and endurance."
                }
            ]
        },
        "no_results_visible": {
            "type": "boolean",
            "__example__": false
        },
        "results_visible": {
            "type": "boolean",
            "__example__": true
        }                
    },
    "terminal": true,
    "layout": {
        "type": "SingleColumnLayout",
        "children": [
            {
                "type": "Form",
                "name": "classes_selection_form",
                "children": [
                    {
                    "type": "TextSubheading",
                    "text": "No Results.",
                    "visible": "${data.no_results_visible}"
                    },
                    {
                    "type": "TextBody",
                    "text": "We're sorry there are no classes matching your preferences at the moment, use the checkbox below to be notified of changes.",
                    "visible": "${data.no_results_visible}"
                    },                            
                    {
                        "type": "CheckboxGroup",
                        "name": "chosen_classes",
                        "visible": "${data.results_visible}",
                        "data-source": "${data.classes_selection}"
                    },
                    {
                        "type": "OptIn",
                        "label": "Notify me of future classes matching my preferences.",
                        "name": "notify_new_classes"
                    },
                    {
                        "type": "EmbeddedLink",
                        "text": "Privacy Policy",
                        "on-click-action": {
                            "name": "navigate",
                            "next": {
                                "type": "screen",
                                "name": "PRIVACY_POLICY"
                            },
                            "payload": {}
                        }
                    },
                    {
                        "type": "Footer",
                        "label": "Submit and Close",
                        "on-click-action": {
                            "name": "complete",
                            "payload": {
                                "classes": "${form.chosen_classes}",
                                "subscribe_new_classes": "${form.notify_new_classes}"
                            }
                        }
                    }
                ]
            }
        ]
    }
},

The screen uses the data property to both declare dynamic data and provide example data, so the components can be rendered in the preview in a realistic fashion.

The screen also utilizes the navigate and complete actions, both designed to keep the navigation on the client.

Privacy Policy Screen

{
    "id": "PRIVACY_POLICY",
    "title": "Privacy Policy",
    "data": {},
    "layout": {
        "type": "SingleColumnLayout",
        "children": [
            {
                "type": "TextHeading",
                "text": "Privacy Policy"
            },
            {
                "type": "TextBody",
                "text": "<Privacy Policy Text>"
            }
        ]
    }
}

The Privacy Policy screen only allows people to go back, because typically there would not be a CTA on this type of screen.

Testing

Now that Flow development is complete, you should test it prior to publishing.

To test the Flow before publishing you can:

  • Use the interactive preview in the Builder UI

Or

  • Send the Flow as a draft message using the API

You can test different configurations of the dynamic data by changing the relevant __example__ properties.

Once everything has been tested successfully, you can publish the Flow (via the Builder UI or the API) and start sending Feedback Form Flows to users.

The next step is create and connect the endpoint.

Create and Connect the Endpoint

The data_exchange is limited to the two actions which require it (showing/hiding the induction classes option and returning the subset of classes matching the user’s preferences). For all other actions the navigation is on the client, to reduce latency and make the experience fast and smooth for the user.

Wrapper Code

Include the following code to "wrap" the 3 screens of the Lead Generation Form (Questions, Gym Classes, and Privacy Policy):

The goal of this function is to decrypt and process the request with the information from the user and to encrypt and send the response.

fastify.post("/data", async function (request, reply) {
  try {
    const privKey = await getPrivateKey();
    const { decryptedBody, aesKeyBuffer, initialVectorBuffer } = decryptRequest(
      request.body,
      privKey
    );
    const { screen, data, version } = decryptedBody;
    const {
      days_of_week,
      class_level,
      include_gym_induction_classes,
      refresh_questions_page,
    } = data;
    let responseBody = {};
    
    switch (screen) {
      case "QUESTIONS":
        if (refresh_questions_page) {
  // logic to hide/display the induction classes selector
        } else {
  // logic to return the subset of classes matching the user's preferences
        }
        break;
    }

    return encryptResponse(responseBody, aesKeyBuffer, initialVectorBuffer);
  } catch (err) {
    // handle error.
    throw err;
  }
});

The function assumes the endpoint will respond on the ‘/data’ path. It uses fastify, but can easily be replaced with any nodejs web application framework.

This function decrypts the request and the body, then checks if the action has been invoked from the class level selector (in which case refresh_questions_page would be true) or from the user’s preference’s page CTA.

Finally the function encrypts and returns responseBody to the client.