HTML Diff
21 added 23 removed
Original 2026-01-01
Modified 2026-03-10
1 <p><em>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 &amp; technically-advanced users.</em></p>
1 <p><em>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 &amp; technically-advanced users.</em></p>
2 - <p><strong><em>Note:</em></strong><em>We do our best to make sure any code and API references are accurate and current when this is published, but you might need to update code and it’s always a best practice to leverage our</em><a><em>latest API versions</em></a><em>. If you have questions, feel free to hop over to our</em><a><em>Developer Community</em></a><em>.</em></p>
2 + <p><em><strong>Note:</strong>We do our best to make sure any code and API references are accurate and current when this is published, but you might need to update code and it’s always a best practice to leverage our<a>latest API versions</a>. If you have questions, feel free to hop over to our<a>Developer Community</a>.</em></p>
3 <p>What you’ll learn</p>
3 <p>What you’ll learn</p>
4 - <p>Klaviyo has powerful built-in <a>segmentation</a> capabilities, where you can target profiles based on their properties and on actions (metrics) they have taken, and can further fine-tune the segmentation to properties of those metrics. This article will build on <a><strong>Solution Recipe 10: Use webhooks in flows to send additional event and profile data into Klaviyo</strong></a>, to permit more fine-grained segmentation based on actions profiles have taken.</p>
4 + <p>Klaviyo has powerful built-in<a>segmentation</a>capabilities, where you can target profiles based on their properties and on actions (metrics) they have taken, and can further fine-tune the segmentation to properties of those metrics. This article will build on<a><strong>Solution Recipe 10: Use webhooks in flows to send additional event and profile data into Klaviyo</strong></a>, to permit more fine-grained segmentation based on actions profiles have taken.One common use case is to target high-spending customers, specifically customers who have placed orders above a certain value threshold in a given time period. Using the built-in Klaviyo tools, you can segment profiles by the<em>total</em>value of their orders in a time period, but not based on the value of<em>individual</em>orders. For example, suppose we want to segment profiles who have placed<em>one</em>order of at least $1,000, versus profiles who have placed 50 orders of $20 apiece (for a total of $1,000).Using a combination of Klaviyo flows, webhooks and template<a>tagging</a>, we can store dynamically-updated profile properties whose values we can use to extend Klaviyo segmentation.</p>
5 - <p>One common use case is to target high-spending customers, specifically customers who have placed orders above a certain value threshold in a given time period. Using the built-in Klaviyo tools, you can segment profiles by the <em>total</em> value of their orders in a time period, but not based on the value of <em>individual</em> orders. For example, suppose we want to segment profiles who have placed <em>one</em> order of at least $1,000, versus profiles who have placed 50 orders of $20 apiece (for a total of $1,000).</p>
 
6 - <p>Using a combination of Klaviyo flows, webhooks and template <a>tagging</a>, we can store dynamically-updated profile properties whose values we can use to extend Klaviyo segmentation.</p>
 
7 <p>Why it matters</p>
5 <p>Why it matters</p>
8 <p>Klaviyo’s segmentation capabilities are among its most powerful features, and being able to further segment profiles based on the actions they have taken allows you to take full advantage of Klaviyo.</p>
6 <p>Klaviyo’s segmentation capabilities are among its most powerful features, and being able to further segment profiles based on the actions they have taken allows you to take full advantage of Klaviyo.</p>
9 <p>Level of sophistication</p>
7 <p>Level of sophistication</p>
10 <p>Moderate</p>
8 <p>Moderate</p>
11 <h2>Prerequisite</h2>
9 <h2>Prerequisite</h2>
12 - <p>Read <a><strong>Solution Recipe 10: Use webhooks in flows to send additional event and profile data into Klaviyo</strong></a> to learn how Klaviyo flow webhooks work, and how you can use them to call the Klaviyo track and identify APIs to set profile properties and add custom metrics.</p>
10 + <p>Read<a><strong>Solution Recipe 10: Use webhooks in flows to send additional event and profile data into Klaviyo</strong></a>to learn how Klaviyo flow webhooks work, and how you can use them to call the Klaviyo track and identify APIs to set profile properties and add custom metrics.</p>
13 - <p>Because this recipe uses webhook flow actions, you will need to enable <a>two-step authentication</a> on your Klaviyo account.</p>
11 + <p>Because this recipe uses webhook flow actions, you will need to enable<a>two-step authentication</a>on your Klaviyo account.</p>
14 <h2>Introduction</h2>
12 <h2>Introduction</h2>
15 - <p>In our example, we have a basic Placed Order metric (created with the <a>Track Profile Activity</a> API), that contains a timestamp, an array of ordered items, and a value.</p>
13 + <p>In our example, we have a basic Placed Order metric (created with the<a>Track Profile Activity</a>API), that contains a timestamp, an array of ordered items, and a value.</p>
16 { "created_at": "2022-09-22T20:41:17.298858", "items": [ { "ProductName": "Brooklyn Bridge Canvas Print", "Size": "small" }, { "ProductName": "Brooklyn Bridge Canvas Print", "Size": "medium" }, { "ProductName": "Brooklyn Bridge Canvas Print", "Size": "small" } ], "$value": 1359 }<p>We have two goals:</p>
14 { "created_at": "2022-09-22T20:41:17.298858", "items": [ { "ProductName": "Brooklyn Bridge Canvas Print", "Size": "small" }, { "ProductName": "Brooklyn Bridge Canvas Print", "Size": "medium" }, { "ProductName": "Brooklyn Bridge Canvas Print", "Size": "small" } ], "$value": 1359 }<p>We have two goals:</p>
17 <ul><li>build a segment of all profiles who have placed an individual order of at least a certain value within the past 30 days</li>
15 <ul><li>build a segment of all profiles who have placed an individual order of at least a certain value within the past 30 days</li>
18 <li>build a segment of all profiles who have ordered an item whose size (photo print size in this case) is a certain value (e.g. “medium” or “large”) in the past 30 days</li>
16 <li>build a segment of all profiles who have ordered an item whose size (photo print size in this case) is a certain value (e.g. “medium” or “large”) in the past 30 days</li>
19 - </ul><p>We will build a flow that is triggered off the Placed Order event, and which uses webhooks with the <a>Identify Profile</a> API to save profile properties that come from the event data, e.g. the order’s value and the size property of ordered items.</p>
17 + </ul><p>We will build a flow that is triggered off the Placed Order event, and which uses webhooks with the<a>Identify Profile</a>API to save profile properties that come from the event data, e.g. the order’s value and the size property of ordered items.</p>
20 <h2>Instructions</h2>
18 <h2>Instructions</h2>
21 <h3>Step 1: Build the flow</h3>
19 <h3>Step 1: Build the flow</h3>
22 <p>Here is how the flow will look when completed. It is triggered off the “Placed Order” event that our e-commerce integration sends to Klaviyo when customers place orders.</p>
20 <p>Here is how the flow will look when completed. It is triggered off the “Placed Order” event that our e-commerce integration sends to Klaviyo when customers place orders.</p>
23 <p>We are going to store two profile properties, one whose value is the date of the highest value order the profile placed within the past 30 days, and a second property whose value is the monetary value of that order.</p>
21 <p>We are going to store two profile properties, one whose value is the date of the highest value order the profile placed within the past 30 days, and a second property whose value is the monetary value of that order.</p>
24 - <p>We first check whether the profile has placed any other orders in the past 30 days. If they have not, then we know that this current order <em>is</em> by definition the highest value order in the past 30 days, and we do not need to do any comparison of the current order’s value with the profile properties we are storing. Because the <em>current</em> order that triggered the flow is included in the conditional split count, we set the condition to “has placed at most one order in the past 30 days”.</p>
22 + <p>We first check whether the profile has placed any other orders in the past 30 days. If they have not, then we know that this current order<em>is</em>by definition the highest value order in the past 30 days, and we do not need to do any comparison of the current order’s value with the profile properties we are storing. Because the<em>current</em>order that triggered the flow is included in the conditional split count, we set the condition to “has placed at most one order in the past 30 days”.</p>
25 <p>In this case, we add the following webhook action.</p>
23 <p>In this case, we add the following webhook action.</p>
26 - <p>The destination URL of the webhook is Klaviyo’s Identify API - <a>https://a.klaviyo.com/api/identify</a></p>
24 + <p>The destination URL of the webhook is Klaviyo’s Identify API -<a>https://a.klaviyo.com/api/identify</a></p>
27 <p>The webhook JSON body is as follows:</p>
25 <p>The webhook JSON body is as follows:</p>
28 - { "token": "[public key]", "properties": { "$email": "{{ person.email }}", "highest_order_value": {{ event|lookup:'$value'|default:0 }}, "highest_order_date": "{{ event.created_at }}" } }<p>Replace <strong>[public key]</strong> with your Klaviyo account’s 6-character <a>public key</a>.</p>
26 + { "token": "[public key]", "properties": { "$email": "{{ person.email }}", "highest_order_value": {{ event|lookup:'$value'|default:0 }}, "highest_order_date": "{{ event.created_at }}" } }<p>Replace<strong>[public key]</strong>with your Klaviyo account’s 6-character<a>public key</a>.</p>
29 - <p>You may need to change <strong>{{ event.created_at }}</strong> to use whatever event property stores the date of the current order. You can also use the Django 1 now <a>tag</a>.</p>
27 + <p>You may need to change<strong>{{ event.created_at }}</strong>to use whatever event property stores the date of the current order. You can also use the Django now<a>tag</a>.</p>
30 <p>Suppose the following Placed Order event triggered the flow:</p>
28 <p>Suppose the following Placed Order event triggered the flow:</p>
31 <p>If this is the profile’s first order in 30 days, the following profile properties will be saved:</p>
29 <p>If this is the profile’s first order in 30 days, the following profile properties will be saved:</p>
32 - <p>If the profile <em>has</em> placed at least one other order in the past 30 days, then we need to compare the value of the current order against the value of 1 highest_order_value stored in the profile, and only update the profile property if the current order’s value is higher.</p>
30 + <p>If the profile<em>has</em>placed at least one other order in the past 30 days, then we need to compare the value of the current order against the value of highest_order_value stored in the profile, and only update the profile property if the current order’s value is higher.</p>
33 - <p>Here is the webhook action JSON body for the “No” branch of the conditional split, meaning the profile <em>has</em> placed at least one other order in the past 30 days. The URL remains <a>https://a.klaviyo.com/api/identify</a></p>
31 + <p>Here is the webhook action JSON body for the “No” branch of the conditional split, meaning the profile<em>has</em>placed at least one other order in the past 30 days. The URL remains<a>https://a.klaviyo.com/api/identify</a></p>
34 - {% with profile_order_value=person|lookup:'highest_order_value'|floatformat:"0" %} {% with order_value=event|lookup:'$value'|floatformat:"0" %} {% if profile_order_value &lt; order_value %} { "token": "[public key]", "properties": { "$email": "{{ person.email }}", "highest_order_value": {{ order_value }}, "highest_order_date": "{{ event.created_at }}" } } {% else %} { "token": "[public key]", "properties": { "$email": "{{ person.email }}" } } {% endif %} {% endwith %} {% endwith %}<p>We only update <strong>highest_order_value</strong> and <strong>highest_order_date</strong> if the value stored in the profile for the highest recent order value is <em>less than</em> the value of the current order. Otherwise, we would ideally want to cancel the webhook call, but since we cannot do that, we send a “dummy” webhook call that does not update any new profile properties.</p>
32 + {% with profile_order_value=person|lookup:'highest_order_value'|floatformat:"0" %} {% with order_value=event|lookup:'$value'|floatformat:"0" %} {% if profile_order_value &lt; order_value %} { "token": "[public key]", "properties": { "$email": "{{ person.email }}", "highest_order_value": {{ order_value }}, "highest_order_date": "{{ event.created_at }}" } } {% else %} { "token": "[public key]", "properties": { "$email": "{{ person.email }}" } } {% endif %} {% endwith %} {% endwith %}<p>We only update<strong>highest_order_value</strong>and<strong>highest_order_date</strong>if the value stored in the profile for the highest recent order value is<em>less than</em>the value of the current order. Otherwise, we would ideally want to cancel the webhook call, but since we cannot do that, we send a “dummy” webhook call that does not update any new profile properties.</p>
35 <h3>Step 2: Build the segment</h3>
33 <h3>Step 2: Build the segment</h3>
36 <p>Once the flow is live and the profile properties start being added, we can create a segment of profiles who have placed an order of at least a certain value in the past 30 days:</p>
34 <p>Once the flow is live and the profile properties start being added, we can create a segment of profiles who have placed an order of at least a certain value in the past 30 days:</p>
37 - <p>We want to look for all profiles whose <strong>highest_order_date</strong> is within 30 days, and whose <strong>highest_order_value</strong> is at least our threshold. As new profiles place orders above this threshold, they will be added to this segment, and as profiles who had placed such orders at one point but no longer are, they will be removed from the segment.</p>
35 + <p>We want to look for all profiles whose<strong>highest_order_date</strong>is within 30 days, and whose<strong>highest_order_value</strong>is at least our threshold. As new profiles place orders above this threshold, they will be added to this segment, and as profiles who had placed such orders at one point but no longer are, they will be removed from the segment.</p>
38 - <p>If we want to find profiles who have <em>ever</em> placed such a high-valued order, regardless of when, we can remove the first condition of the segment definition.</p>
36 + <p>If we want to find profiles who have<em>ever</em>placed such a high-valued order, regardless of when, we can remove the first condition of the segment definition.</p>
39 <h3>Step 3 (optional): Save ordered item properties to profiles</h3>
37 <h3>Step 3 (optional): Save ordered item properties to profiles</h3>
40 <p>Our example Placed Order data includes details about the ordered items, in this case, a photo size:</p>
38 <p>Our example Placed Order data includes details about the ordered items, in this case, a photo size:</p>
41 - { "ProductName": "Brooklyn Bridge Canvas Print", "Size": "small" }<p>Suppose we want to segment on profiles who have ordered photos of certain sizes. With the native segmentation features, we can segment on <a><em>top-level</em> properties</a> of event data. With this solution recipe, we can save profile properties whose values are <em>nested</em> non-top level values of the event data.</p>
39 + { "ProductName": "Brooklyn Bridge Canvas Print", "Size": "small" }<p>Suppose we want to segment on profiles who have ordered photos of certain sizes. With the native segmentation features, we can segment on<a><em>top-level</em>properties</a>of event data. With this solution recipe, we can save profile properties whose values are<em>nested</em>non-top level values of the event data.</p>
42 - <p>Add a webhook action to the flow, with the same URL of <a>https://a.klaviyo.com/api/identify</a>, with the following JSON body. It should be outside the “has placed an order in the past 30 days” conditional split, because we do not have to worry about comparing numeric values of orders like we did with the original webhook.</p>
40 + <p>Add a webhook action to the flow, with the same URL of<a>https://a.klaviyo.com/api/identify</a>, with the following JSON body. It should be outside the “has placed an order in the past 30 days” conditional split, because we do not have to worry about comparing numeric values of orders like we did with the original webhook.</p>
43 - { "token": "[public key]", "properties": { "$email": "{{ person.email }}", "small_order_date": "{% for item in event.items %}{% if item.Size == "small" %}{{ event.created_at }}{% endif %}{% endfor %}", "medium_order_date": "{% for item in event.items %}{% if item.Size == "medium" %}{{ event.created_at }}{% endif %}{% endfor %}", "large_order_date": "{% for item in event.items %}{% if item.Size == "large" %}{{ event.created_at }}{% endif %}{% endfor %}", "xlarge_order_date": "{% for item in event.items %}{% if item.Size == "xlarge" %}{{ event.created_at }}{% endif %}{% endfor %}" } }<p>For each possible value of the print size, we save a profile property such as <strong>medium_order_date</strong>, and to determine its value, we loop through each of the items of the order and check whether the item’s<strong> Size</strong> property is the value in question. If so, we set the value to the order’s date, so the profile property reflects the date of the most recent order that had an item meeting the corresponding condition.</p>
41 + { "token": "[public key]", "properties": { "$email": "{{ person.email }}", "small_order_date": "{% for item in event.items %}{% if item.Size == "small" %}{{ event.created_at }}{% endif %}{% endfor %}", "medium_order_date": "{% for item in event.items %}{% if item.Size == "medium" %}{{ event.created_at }}{% endif %}{% endfor %}", "large_order_date": "{% for item in event.items %}{% if item.Size == "large" %}{{ event.created_at }}{% endif %}{% endfor %}", "xlarge_order_date": "{% for item in event.items %}{% if item.Size == "xlarge" %}{{ event.created_at }}{% endif %}{% endfor %}" } }<p>For each possible value of the print size, we save a profile property such as<strong>medium_order_date</strong>, and to determine its value, we loop through each of the items of the order and check whether the item’s<strong>Size</strong>property is the value in question. If so, we set the value to the order’s date, so the profile property reflects the date of the most recent order that had an item meeting the corresponding condition.</p>
44 <p>(Note again that you will need to use the actual event data property names for your particular e-commerce platform.)</p>
42 <p>(Note again that you will need to use the actual event data property names for your particular e-commerce platform.)</p>
45 <p>Here are example profile properties that will be saved from this webhook:</p>
43 <p>Here are example profile properties that will be saved from this webhook:</p>
46 <h3>Step 4 (optional): Build segments for additional order properties</h3>
44 <h3>Step 4 (optional): Build segments for additional order properties</h3>
47 <p>Suppose we want to find all profiles who have placed an order with a medium photo print within the past 30 days. Here is the segment we would add:</p>
45 <p>Suppose we want to find all profiles who have placed an order with a medium photo print within the past 30 days. Here is the segment we would add:</p>
48 <h2>Enhancements and limitations</h2>
46 <h2>Enhancements and limitations</h2>
49 <h3>Further date-based segmentation</h3>
47 <h3>Further date-based segmentation</h3>
50 - <p>We could add conditional splits for other date ranges beyond 30 days, e.g. 60 and 90 days, and save profile properties such as<strong>60_day_highest_order_value</strong>and <strong>60_day_highest_order_date</strong>. We can then build segments based on profiles who have placed orders of certain values within these varying date ranges.</p>
48 + <p>We could add conditional splits for other date ranges beyond 30 days, e.g. 60 and 90 days, and save profile properties such as<strong>60_day_highest_order_value</strong>and<strong>60_day_highest_order_date</strong>. We can then build segments based on profiles who have placed orders of certain values within these varying date ranges.</p>
51 <h3>Historical orders</h3>
49 <h3>Historical orders</h3>
52 <p>Because we need the flow to save the profile properties as orders are placed, we will not be able to start building segments until the flow is live and orders start coming into Klaviyo. Orders placed before the flow was enabled, or orders imported via a historical CSV upload, will not trigger the flow, and therefore won’t cause the profile properties to be set.</p>
50 <p>Because we need the flow to save the profile properties as orders are placed, we will not be able to start building segments until the flow is live and orders start coming into Klaviyo. Orders placed before the flow was enabled, or orders imported via a historical CSV upload, will not trigger the flow, and therefore won’t cause the profile properties to be set.</p>
53 - <p>If you have an offline process that can take historical orders and use the same logic as the flow to generate the values of the profile properties that the flow sets, you could do a <a>bulk CSV update</a> of profiles with earlier placed orders, using the same profile property names the flow updates. Your segments will then include profiles who placed orders before the flow was created and turned on.</p>
51 + <p>If you have an offline process that can take historical orders and use the same logic as the flow to generate the values of the profile properties that the flow sets, you could do a<a>bulk CSV update</a>of profiles with earlier placed orders, using the same profile property names the flow updates. Your segments will then include profiles who placed orders before the flow was created and turned on.</p>
54 <h2>Learn more!</h2>
52 <h2>Learn more!</h2>
55 - <p>If you’re interested in learning more about Klaviyo’s developer experience and APIs, please visit <a>developers.klaviyo.com</a>!</p>
53 + <p>If you’re interested in learning more about Klaviyo’s developer experience and APIs, please visit<a>developers.klaviyo.com</a>!</p>
56  
54