Skip to content

Using Product Agents with Klaviyo

This guide explains how to integrate Product Agents with Klaviyo using a metric-triggered flow.

It covers:

  • How Product Agents send events to Klaviyo
  • How to configure the required Klaviyo flow
  • The full event payload structure
  • All supported merge variables
  • How to render product data safely in templates

This guide is intended for developers, agencies, and technical marketers responsible for Klaviyo setup.

Integration model

When using Klaviyo as the delivery channel:

  • Product Agents emit a custom metric event to Klaviyo
  • Klaviyo triggers a flow when the event is received
  • Klaviyo controls consent, suppression, smart sending, and delivery
  • Product Agents do not send emails directly to customers

Each Product Agent message corresponds to one event sent to Klaviyo.

Prerequisites

Before setting up the integration, ensure that:

  • You have access to the Klaviyo account
  • You can create and edit flows
  • You can create and edit email templates
  • You have a Klaviyo API key with permission to send events

Event name and trigger

Product Agents send a custom Klaviyo metric named:

helloretail

Your Klaviyo flow must be a metric-triggered flow using this event.

There should be exactly one flow listening to this event.


Event payload structure

Product Agents send the following properties as part of the Klaviyo event.

Full payload structure
{
  "subject": "string",
  "previewText": "string",
  "headline": "string",
  "body": "string",
  "callToAction": "string",
  "messageType": "string",

  "otherProductsTitle": "string",
  "otherProductsText": "string",
  "otherProductsCallToAction": "string",

  "inputProduct": {
    "title": "string",
    "description": "string",
    "imgUrl": "string",
    "image": {
      "scalableUrl": "string",
      "url": "string"
    },
    "url": "string",
    "originalUrl": "string",
    "trackingCode": "string",
    "productNumber": "string",
    "price": "number|null",
    "oldPrice": "number|null",
    "discountPercentage": "number",
    "currency": "string",
    "onSale": "boolean",
    "brand": "string|null",
    "inStock": "boolean",
    "priceExVat": "number|null",
    "oldPriceExVat": "number|null",
    "keywords": "string",
    "ean": "string|null"
  },

  "primaryProducts": [
    {
      "title": "string",
      "description": "string",
      "imgUrl": "string",
      "image": {
        "scalableUrl": "string",
        "url": "string"
      },
      "url": "string",
      "originalUrl": "string",
      "trackingCode": "string",
      "productNumber": "string",
      "price": "number|null",
      "oldPrice": "number|null",
      "discountPercentage": "number",
      "currency": "string",
      "onSale": "boolean",
      "brand": "string|null",
      "inStock": "boolean",
      "priceExVat": "number|null",
      "oldPriceExVat": "number|null",
      "keywords": "string",
      "ean": "string|null"
    }
  ],

  "otherProducts": [
    {
      "title": "string",
      "description": "string",
      "imgUrl": "string",
      "image": {
        "scalableUrl": "string",
        "url": "string"
      },
      "url": "string",
      "originalUrl": "string",
      "trackingCode": "string",
      "productNumber": "string",
      "price": "number|null",
      "oldPrice": "number|null",
      "discountPercentage": "number",
      "currency": "string",
      "onSale": "boolean",
      "brand": "string|null",
      "inStock": "boolean",
      "priceExVat": "number|null",
      "oldPriceExVat": "number|null",
      "keywords": "string",
      "ean": "string|null"
    }
  ]
}

Note

  • inputProduct is the product that triggered the message (what the customer previously bought or viewed)
  • primaryProducts is always an array and may contain one or more products
  • otherProducts may be empty
  • All fields should be treated as optional unless explicitly required by your template
  • For information about how the image scaling mechanism works refer to Integration Overview

Creating the Klaviyo flow

  1. Log in to Klaviyo
  2. Go to Flows
  3. Create a new flow
  4. Choose Metric-triggered flow
  5. Select the metric helloretail
  6. Save the flow
  7. Add an Email action to the flow

The email action must be set to Live for messages to be sent.

Warning

Do not add additional filters to the trigger unless explicitly required. Filtering the trigger may block Product Agent messages.


Subject line and preview text

The subject line and preview text are provided dynamically by Product Agents.

Configure them in the email settings, not in the template body.

{{ event.subject }}
{{ event.previewText }}

Warning

Do not hard-code subject lines or preview text.

Template data access

{{ event.headline | safe }} {# (1)! #}
{{ event.body | safe }} {# (2)! #}
{{ event.primaryProducts.0.title }} {# (3)! #}
  1. Use | safe on all fields that may contain HTML to allow it to render correctly instead of being escaped.
  2. body may contain <b>, <em>, <ul>, and <li> tags — always use | safe.
  3. Access the first primary product using .0. dot notation. Use a loop if you need to render multiple products.

Core content fields

Field Template syntax Description
subject {{ event.subject }} Email subject line
previewText {{ event.previewText }} Inbox preview text
headline {{ event.headline \| safe }} Main email headline
body {{ event.body \| safe }} Main email content
callToAction {{ event.callToAction }} Primary CTA button text

Product fields

Input product

inputProduct is the product that triggered this message — the item the customer previously bought or viewed. You can reference it in templates to give the email context, for example to remind the customer what they purchased before recommending a replenishment or alternative.

Field Template syntax Description
title {{ event.inputProduct.title }} Product name
description {{ event.inputProduct.description \| safe }} Product description (may contain HTML)
image (scalable) {{ event.inputProduct.image.scalableUrl }} Hello Retail CDN URL supporting dynamic resize via query params
image (direct) {{ event.inputProduct.image.url }} Direct URL to product image
url {{ event.inputProduct.url }} Product page URL with tracking fragment appended
originalUrl {{ event.inputProduct.originalUrl }} Original product page URL without tracking
productNumber {{ event.inputProduct.productNumber }} Product number/SKU as defined in the product catalog
price {{ event.inputProduct.price \| floatformat:2 }} Current price
currency {{ event.inputProduct.currency }} Currency code (e.g., "EUR", "USD")
brand {{ event.inputProduct.brand }} Brand name
inStock {% if event.inputProduct.inStock %} Whether the product is in stock

First primary product

Field Template syntax Description
title {{ event.primaryProducts.0.title }} Product name
description {{ event.primaryProducts.0.description \| safe }} Product description (may contain HTML)
image (scalable) {{ event.primaryProducts.0.image.scalableUrl }} Hello Retail CDN URL supporting dynamic resize via query params
image (direct) {{ event.primaryProducts.0.image.url }} Direct URL to product image
imgUrl {{ event.primaryProducts.0.imgUrl }} Direct URL to product image (legacy alias for image.url)
url {{ event.primaryProducts.0.url }} Product page URL with tracking fragment appended
originalUrl {{ event.primaryProducts.0.originalUrl }} Original product page URL without tracking
trackingCode {{ event.primaryProducts.0.trackingCode }} Tracking identifier for the product in this message
productNumber {{ event.primaryProducts.0.productNumber }} Product number/SKU as defined in the product catalog
price {{ event.primaryProducts.0.price \| floatformat:2 }} Current price
oldPrice {{ event.primaryProducts.0.oldPrice \| floatformat:2 }} Previous price if on sale
discountPercentage {{ event.primaryProducts.0.discountPercentage }} Discount percentage (0 if not on sale)
currency {{ event.primaryProducts.0.currency }} Currency code (e.g., "EUR", "USD")
onSale {% if event.primaryProducts.0.onSale %} Whether the product is on sale
brand {{ event.primaryProducts.0.brand }} Brand name
inStock {% if event.primaryProducts.0.inStock %} Whether the product is in stock
priceExVat {{ event.primaryProducts.0.priceExVat \| floatformat:2 }} Current price excluding VAT
oldPriceExVat {{ event.primaryProducts.0.oldPriceExVat \| floatformat:2 }} Previous price excluding VAT
keywords {{ event.primaryProducts.0.keywords }} Product keywords
ean {{ event.primaryProducts.0.ean }} EAN/barcode

Looping through primary products

{% for product in event.primaryProducts %} {# (1)! #}
  {{ product.title }} {# (2)! #}
  {{ product.url }}
{% endfor %}
  1. Loops over all primary products. Typically one, but the template should handle more.
  2. Inside a loop, access fields directly on product — no need for event.primaryProducts.0.X.
{% for product in event.otherProducts %}
  {{ product.title }}
  {{ product.url }}
{% endfor %}
{% if event.otherProducts | length > 0 %} {# (1)! #}
  {{ event.otherProductsTitle | safe }} {# (2)! #}
  {{ event.otherProductsText | safe }}

  {% for product in event.otherProducts %}
    {{ product.title }}
  {% endfor %}
{% endif %}
  1. Always guard the recommended products section — otherProducts may be an empty array.
  2. Used here as a precaution — only body, headline, and product description fields are documented as containing HTML.
Field Template syntax Description
otherProductsTitle {{ event.otherProductsTitle \| safe }} Section title
otherProductsText {{ event.otherProductsText \| safe }} Section description
otherProductsCallToAction {{ event.otherProductsCallToAction }} CTA text

messageType values

The messageType field identifies which Product Agent generated the message.

These values are stable technical identifiers and can be used in templates if conditional rendering is needed.

messageType User-facing name
REPLENISHMENT_REMINDER Replenishment Reminder
SIMILAR_PRODUCT_RECOMMENDATIONS Alternative Picks
ACCESSORY_RELATED_PRODUCT_UPSELL Recommended Add-ons
PRICE_DROP_VIEWED_PRODUCT Price Drop – Viewed Product
PRICE_DROP_PURCHASED_REPLENISHMENT Price Drop – Purchased Replenishment
PRICE_DROP_ALTERNATIVE_PRODUCT Price Drop – Alternative Product

Smart sending and test mode

Klaviyo Smart Sending rules apply after the Product Agent event is received.

This means:

  • Events may be received even if emails are blocked
  • Emails blocked by Smart Sending do not reach the inbox
  • Product Agents do not retry messages blocked by the channel

During testing, Smart Sending may need to be temporarily disabled to avoid confusion when sending repeated test messages.

These topics are covered in separate documentation: