Keeping track of profiles that are currently in Klaviyo flows

Michaela
11min read
Developer recipes
July 10, 2023
Featured image
Build personalized experiences at scale
Get started

Solution Recipes are tutorials to achieve specific objectives in Klaviyo. They can also help you master Klaviyo, learn new third-party technologies, and come up with creative ideas. They are written mainly for developers and technically-advanced users.

This is a guest post written by Michaela Fooksa, who recently built this as part of Klaviyo’s internal Solution Architect mentorship program.

What you’ll learn

How to create a profile property containing a list of all Flows a profile is currently in and a profile property containing a list of all Flows a profile has successfully completed.

Why it matters

Being able to keep track of which Flows customers are in can provide more control over who is getting your messages. These profile properties allow you to create Segments of profiles currently in Flows and tailor your sending to include or exclude profiles who are actively in other Flows.

Level of sophistication

Moderate

Introduction

The ability to create custom profile properties is a powerful tool in Klaviyo. We can use profile properties to create Segments and apply filters, thus allowing complete personalization of the marketing messaging that each profile receives.

There are several different types of profile properties. A text property consists of a string of characters, while a list property consists of an array of values in the format [“value1”,”value2”]. Klaviyo also supports dictionary properties, which contain sets of key-value pairs in the format {“key1”:”value1”,”key2”:”value2”}.

Challenge

In order to determine if a profile is actively in a Flow, a common strategy is to use an Update Profile Property action at the beginning of a Flow to add a text-type profile property specific to that Flow that signifies that a profile is currently in that Flow. At the end of the Flow, another Update Profile Property action updates this property to signify that the customer is no longer in this Flow. 

However, the native Update Profile Property Flow action does not allow for updating list-type profile properties. Therefore, using Update Profile Property actions requires creating separate profile properties for each Flow, which accumulate on a customer profile. In addition, if a profile enters a Flow and then fails flow filters, they will be skipped from the Update Profile Property action at the end of the Flow, thus showing that they are in the Flow indefinitely.

The strategy described here combines all Flows into one comprehensive property – a list-type property containing all Flows a profile is currently in. It also runs a daily check to make sure that profiles have not exceeded the time scope of each Flow. If they have, the Flow will be removed from each profile’s current_flows property. That way, even if a customer fails flow filters, the Flow will be removed from their profile.

Ingredients

  • A Klaviyo Flow containing at least 2 webhooks
  • A Napkin.io account
  • A Klaviyo List

Instructions

Part 1: Set up Napkin.io functions

  1. Generate a private API key in Klaviyo by clicking Settings > API Keys and clicking “Create Private API Key.” At a minimum, please give this key Full Access to Lists and Profiles API scopes:
  2. Create a Napkin.io account at https://www.napkin.io/
  3. Click Create a new function on the left, followed by Blank Python Function
  4. Select Modules, then search klaviyo-api. Click the arrow next to klaviyo-api to install this module. If it was successfully installed, you should see klaviyo-api listed under Installed Modules on the right:
  5. Name this function active_flows by clicking the pen next to the name of your function at the top and typing in active flows. Then, click the checkmark to save:
  6. Add your private API key as an environment variable by clicking Other, then scrolling down to Environment Variables. Click Add, then type private_key in the box on the left. Paste your private API key from Klaviyo in the box on the right:
  7. Repeat steps C-F to make 3 functions total with the following names:
    1. Active Flows
    2. Past Flows
    3. Flow Cleaner
  8. Copy and paste the appropriate Python code into these new functions. 

Below is the code for the Active Flows function. This function adds the name of the Flow to a profile property called current_flows and adds the profile to a List in your Klaviyo account. It also updates the active_flow_details profile property that keeps track of the length of each Flow and when each profile triggered that Flow.

from napkin import request
from napkin import response
from klaviyo_api import KlaviyoAPI

from datetime import datetime

import os

'''Adds current flow to current flow profile properties.

This module takes four inputs: "id", "flow_name", “days”, and "list_id". It then adds flow_name to the current_flows property, creating the property if it doesn't exist yet. It also adds the flow name and current timestamp as a key-value pair to the active_flow_details profile property. Finally, it adds this profile to the Active Flows List specified by the

provided list_id.

'''

# initiate klaviyo API

private_key = os.environ["private_key"]

klaviyo = KlaviyoAPI(private_key)

# use klaviyo to get profile properties

profile_id = request.body["id"]

profile=klaviyo.Profiles.get_profile(profile_id)

properties = profile["data"]["attributes"]["properties"]

# get current_flows property if it exists, and initiate it as an empty list if not

if 'current_flows' in properties.keys():

   current_flows = properties["current_flows"]

else:

   current_flows=[]

# append current flow name to current_flows property

this_flow = request.body["flow_name"]

in_flow=current_flows.count(this_flow)

if in_flow == 0:

   current_flows.append(this_flow)

# get active_flow_details property if it exists, and initiate it as an empty dictionary if not

if 'active_flow_details' in properties.keys():

   flow_details = properties["active_flow_details"]

else:

   flow_details={}

# update flow_details dictionary with length of flow in days and the current timestamp

length = request.body["days"]

flow_details[this_flow]={"length": length, "time":datetime.now()}

# update profile with new properties - current_flows and active_flow_details

body = { "data": { "type": "profile","id":profile_id, "attributes": { "properties": { "current_flows":current_flows, "active_flow_details":flow_details}}}}

klaviyo.Profiles.update_profile(profile_id,body)

# add profile to active flows list

list_id = request.body["list_id"]

list_body = { "data": [ {"type": "profile","id": profile_id}]}

klaviyo.Lists.create_list_relationships(list_id, list_body)

response.status_code = 200

This code can also be found here: https://www.napkin.io/n/ff072951b6ee4733 

Paste the following Python code in the Past Flows function. This function removes the Flow name from the current_flows and active_flow_details properties and adds it to the past_flows profile property.

from napkin import response

from napkin import request

from klaviyo_api import KlaviyoAPI

from pprint import pprint

import os

'''Removes flow from current_flows and adds it to past_flows.

Takes profile id as "id" and name of flow as "flow_name". If the current_flows property exists on the given profile, removes flow_name from current_flows. Otherwise, current_flows is set to an empty list. It also adds flow_name to past_flows, if it exists, or creates past_flows as a list just containing flow_name. Finally, it removes this flow and associated timestamp from active_flow_details property.

'''

# initiate klaviyo API

private_key = os.environ["private_key"]

klaviyo = KlaviyoAPI(private_key)

# use klaviyo to get profile properties

profile_id = request.body["id"]

profile=klaviyo.Profiles.get_profile(profile_id)

properties = profile["data"]["attributes"]["properties"]

# get current_flows property if it exists, and initiate it as an empty list if not

if 'current_flows' in properties.keys():

   current_flows = properties["current_flows"]

   current_flows.remove(this_flow)

else:

   current_flows=[]

# add current flow name to past_flows property, creating a new property if past_flows does not yet exist

this_flow = request.body["flow_name"]

if 'past_flows' in properties.keys():

   past_flows = properties["past_flows"]

   in_flow=past_flows.count(this_flow)

   if in_flow == 0:

       past_flows.append(this_flow)

else:

   past_flows=[this_flow]

# remove current flow information from active_flow_details property

if 'active_flow_details' in properties.keys():

   flow_details=properties["active_flow_details"]

   del flow_details[this_flow]

# update current_flows, past_flows, and active_flow_details properties

body = { "data": { "type": "profile","id":profile_id, "attributes": { "properties": { "active_flow_details":"empty"}}}}

klaviyo.Profiles.update_profile(profile_id,body)

body = { "data": { "type": "profile","id":profile_id, "attributes": { "properties": { "current_flows":current_flows, "past_flows":past_flows, "active_flow_details":flow_details}}}}

klaviyo.Profiles.update_profile(profile_id,body)

response.status_code = 200

This code can also be found here: https://www.napkin.io/n/f93c9c2972ac4dc9 

Finally, in the Flow Cleaner function, paste the following Python code. This function runs daily to check all profiles in the Active Flows List to see if any Flows in the current_flows property have lapsed. If so, these Flows are removed from each profile’s current_flows and active_flow_details properties.

from datetime import datetime, timedelta

from napkin import response

from klaviyo_api import KlaviyoAPI

from napkin import request

import os

'''If flow start date is longer ago than the length of the flow, removes flow from profile.

Takes "list_id", the ID of the List that contains profiles currently in Flows. It checks every profile in this list, looks up their active_flow_details, and if any Flow has a timestamp that is longer ago than the length of the Flow, it is removed from active_flow_details and current_flows.

'''

# initiate klaviyo API

private_key = os.environ["private_key"]

klaviyo = KlaviyoAPI(private_key)

# get all profiles in active flows list

list_id = 

profiles = klaviyo.Lists.get_list_profiles(list_id)

# iterate over profiles, getting current_flows and active_flow_details properties

for profile in profiles["data"]:

   properties=profile["attributes"]["properties"]

   if 'active_flow_details' in properties.keys():

       flow_details = properties["active_flow_details"]

   else:

       flow_details=[]

   if 'current_flows' in properties.keys():

       current_flows = properties["current_flows"]

   else:

       current_flows=[]

   #iterate over flows in active_flow_details and find any flows that were added before the length of the flow

   flow_names=[]

   for item in flow_details:

       time_str=flow_details[item]["time"]

       length=flow_details[item]["length"]

       time = datetime.strptime(time_str,"%Y-%m-%dT%H:%M:%S.%f") 

       endtime = time+timedelta(days=length)

       if datetime.now() > endtime:

           current_flows.remove(item)

           flow_names.append(item)

   # remove these old flows from active_flow_details

   for name in flow_names:

       del flow_details[str(name)]

   #update profile with new active_flow_details and current_flows

   profile_id = profile["id"]

   body = { "data": { "type": "profile","id":profile_id, "attributes": { "properties": { "active_flow_details":"empty"}}}}

   klaviyo.Profiles.update_profile(profile_id,body)

   body = { "data": { "type": "profile","id":profile_id, "attributes": { "properties": { "current_flows":current_flows, "active_flow_details":flow_details}}}}

   klaviyo.Profiles.update_profile(profile_id,body)

response.status_code = 200

This code can also be found here: https://www.napkin.io/n/4584222442374ffd 

Part 2: Set up current_flows webhook

  1. Create a List in Klaviyo called “Profiles currently in Flows.” This List will keep track of all profiles who are currently in any Flow. Copy the ID of this List from the List URL.
  2. For any Klaviyo Flow in which you want to track the current_flow or past_flow properties, drag a webhook action to the beginning of the Flow, immediately beneath the Flow trigger.
  3. Configure the webhook body to pass four parameters:
    1. Profile ID as id. Profile ID can be accessed with the Django tag 1 {{ person.KlaviyoID }}.
    2. The name of the Flow as 1 flow_name
    3. The ID of the List from step 1 as1 list_id
    4. The length of the Flow in days as 1 days
      1. An example webhook body is:
        1 {
    5. This would pass the Flow name “TESTING” to be added to the 1 current_flows property. The ID of the Active Flows List is 123ABC, and this Flow is only 1 day long, so “days” is passed over as 1.
  4. Navigate to your Active Flows function in Napkin.io and set up authentication by clicking Other, scrolling down to API Authentication, and toggling it to On. This will create a napkin-account-api-key. Copy the value of this key.
  5. In Napkin, click on the function URL to copy it:
  6. Back in your Klaviyo Flow, add this URL as your webhook’s destination URL
  7. Under Headers, add napkin-account-api-key as the Key
  8. Add the napkin API key value you created in Step 4 as the Value next to napkin-account-api-key within the Headers

Part 3: add past flows webhook

  1. In your Klaviyo Flow, click the three dots next to the webhook at the beginning of your Flow. Then, select Clone.
  2. Drag your cloned webhook to the end of your Flow. 
  3. Navigate to your Past Flows function in Napkin.io and set up authentication by clicking Other, scrolling down to API Authentication, and toggling it to On.
  4. In Napkin, copy the Past Flows function’s URL. Replace the destination URL for your new cloned webhook with this URL. 
  5. If your Flow has multiple branches, please clone this new webhook again until there is one webhook hitting Past Flows at the end of every branch of the Flow.

Part 5: set up flow cleaner function

  1. Navigate to your flow cleaner function in Napkin.io. On Line 20, which currently reads 1 list_id = , please add the ID of your Active Flows List surrounded by double quotes. For instance, if the ID of the Active Flows List is X25sJg, this line should read 1 list_id = "X25sJg"
  2.  For the Flow Cleaner function only, click Schedule, then toggle On the option to Turn Schedule on/off. Schedule the function to run Daily, then click Save:

Impact

These three functions – Active Flows, Past Flows, and Flow Cleaner – will automatically keep track of which Flows every profile is in. Having access to this information can provide a greater ability to control and customize your interactions with your customers. You can create Segments based on these properties to identify which profiles are currently in which Flows. 

For instance, let’s say you wish to exclude profiles currently in your Welcome Series from receiving regular Newsletters. You can leverage the current_flows profile property to create a Segment of profiles who are currently in the Welcome Series:

You can then exclude this Segment from Campaign sends.

Segmenting based on the current_flows property allows you complete control over the messages a customer is receiving simultaneously, thus enhancing the customer experience. 

Additional resources

Check out these resources to learn more about webhooks in Flows: 

More information about profile property data types can be found here: Understanding data types. 

Michaela
Michaela Fooksa