Integrate a Business with Brick-and-Mortar Locations

read
Last updated at:

Overview

If you have brick and mortar locations and the POS platform you use is not yet supported by one of our pre-built integrations, you can integrate with Klaviyo using our server-side API and Custom Catalog integration. If your store also has an online component that we do not yet have a prebuilt integration for, please take a look at our guide on integrating an online store.

The key components of integrating a brick and mortar location are:

  • Customer data
  • Location data
  • In-store activity
  • Order activity

This guide focuses on how to sync important metrics, or key customer activities, to Klaviyo. While this guide is geared toward brick and mortar locations, a similar layout can be used for other on site/in-person situations (e.g. a trade show or convention.)

Keep in mind Klaviyo is optimized for using email address as the primary identifier so we highly recommend using the email address when tracking data. While we do support alternative primary identifiers, these will require more thoughtfulness during implementation. This guide features examples using a person's email address.

Use this as a checklist when setting up your integration:

  • Use our server-side Track API for the following:
    • Spoke with Representative - When someone has spoken with a representative to whom you’d like to associate that customer/prospective customer
    • Expressed Interest - When someone lets you know their interest in a particular item or category via in-store interaction, survey, or observation
    • Placed Order - When an order successfully processes on your system
    • Ordered Product - An event for each item in a processed order
    • Fulfilled Order - When an order is sent to the customer
    • Cancelled Order - When a customer cancels their order
    • Refunded Order - When a customer’s order is refunded
  • Use our Custom Catalog Feed integration for the following:
    • Catalog Feed - An XML feed or JSON feed of your product catalog
    • Store Feed - An XML feed or JSON feed of your store locations

The level of detailed data you send to Klaviyo within these in-store and purchase events will determine how you can filter and segment based on these events in Klaviyo. To understand how data must be structured so that key event details are available for segmentation, check out our guide on segment conditions.

Note that the snippets in this guide use example data. You will need to update the values of the JSON properties in these snippets such that they dynamically pull from the relevant information needed for that property.

If you have questions about custom integrations, check out our custom integration FAQ and/or reach out to our Support Team for help.

Server-Side Metrics

Our server-side Track API can be used via our libraries for Python, Ruby, and PHP, but in a general sense, the API just requires making an HTTP GET request with a base64 encoded JSON payload. More information on our Track and Identify APIs can be found here.

You'll want to send server-side requests to Klaviyo in one of two ways: real-time or batch.

  • Real-time - You'll make requests as soon as an action occurs/is logged
  • Batch - You’ll write a script that will run at least once an hour to send all events that occurred in that past hour

Spoke with Representative

If someone enters your store and primarily interacts with a specific representative or if you want to perform some reporting/messaging around a person’s interaction with specific store representatives, track a Spoke with Representative event. This event should include any information the representative has gathered about the person (e.g. first name) as profile properties in the customer_properties dictionary. The event should also include any information specific to the interaction itself (e.g. in which department the interaction took place or notes about the conversation) in the properties dictionary.

{
   "token": "PUBLIC_API_KEY",
   "event": "Spoke with Representative",
   "customer_properties": {
     "$email": "john.smith@test.com",
     "$first_name": "John",
     "$last_name": "Smith",
     "$phone_number": "5551234567",
     "$address1": "123 Abc st",
     "$address2": "Suite 1",
     "$city": "Boston",
     "$zip": "02110",
     "$region": "MA",
     "$country": "USA",
     "MostRecentStoreLocation": "Boston",
     "MostRecentStoreID": "1212"
   },
   "properties": {
     "$event_id": "1234",
     "RepresentativeName": "Jane Doe",
     "StoreID": "1212",
     "Department": "Kitchen supplies",
     "Notes": "Looking for a dutch oven large enough for a turkey"
   },
   "time": 1387302423
}

Expressed Interest

An Expressed Interest event may have some overlap with a Spoke with Representative metric, but it will be important to track nonetheless if you would like to perform any segmentation or flow filtering based on what a customer has expressed interest in. For example, data for this event may be supplied by a store representative who has interacted with or observed the customer. Data for this event may also be supplied through an in-store survey.  

{
   "token": "PUBLIC_API_KEY",
   "event": "Expressed Interest",
   "customer_properties": {
     "$email": "john.smith@test.com",
     "MostRecentStoreLocation": "Boston",
     "MostRecentStoreID": "1212"
   },
   "properties": {
     "$event_id": "1234",
     "Categories": ["Kitchen supplies", "Kitchen wholesale", "Thanksgiving"],
     "Items": ["Dutch Ovens","Basters","Roasting Trays"],
     "Brands": ["KitchenAid", "Le Creuset"]
   },
   "time": 1387302423
}

Placed Order

After an order is placed, you should make a Track request to our server-side API.

It's a good practice to send your historical order data as well. This will enhance your ability to segment off that data and improve historical accuracy in revenue tracking. Historical data can be sent to us by iterating through your historical orders and generating Placed Order and Ordered Product Track API requests for each. The special “time” property for these events should be the UNIX timestamp of when that order occurred.  More details on these metrics below.

For each order, we recommend you send two types of events:

  • One event named Placed Order for the entire order
    • This includes a $value property that represents the total value of an entire order including shipping, tax, discounts, etc.
  • One event for each line item named Ordered Product
    • This includes a $value property that represents the total cost of an item in the order before any adjustments as well as more SKU-level detailed information about the item.

Key things to be aware of when tracking server-side events:

  • Make sure to replace PUBLIC_API_KEY with your public API key.
  • The $event_id should be a unique identifier for the order (eg. Order ID).
  • If the same combination of event and $event_id are sent more than once, we will skip all tracked events after the first with the same combination.
  • $value is a special property that allows Klaviyo to track revenue; this should be the total numerical, monetary value of the event with which it’s associated.
  • The "Items" array should contain one dictionary for each line item.
  • time is a special property that should be a UNIX timestamp of the order date and time.

Here’s an example Track request payload for Placed Order:

{
   "token": "PUBLIC_API_KEY",
   "event": "Placed Order",
   "customer_properties": {
     "$email": "john.smith@test.com",
     "$first_name": "John",
     "$last_name": "Smith",
     "$phone_number": "5551234567",
     "$address1": "123 Abc st",
     "$address2": "Suite 1",
     "$city": "Boston",
     "$zip": "02110",
     "$region": "MA",
     "$country": "USA",
     "MostRecentStoreLocation": "Boston",
     "MostRecentStoreID": "1212"
   },
   "properties": {
     "$event_id": "1234",
     "$value": 335.99,
     "Categories": ["Kitchen Supplies", "Cooking", "Preparation"],
     "ItemNames": ["8 quart Dutch Oven", "Turkey Baster"],
     "Brands": ["Le Creuset", "Betty Crocker"],
     "StoreID": "1212",
     "Items": [{
         "ProductID": "1111",
         "SKU": "LC-8QT",
         "ProductName": "8 quart Dutch Oven",
         "Quantity": 1,
         "ItemPrice": 330.00,
         "RowTotal": 330.00,
         "ImageURL": "http://www.example.com/path/to/product/image.png",
         "Categories": ["Kitchen Supplies", "Cooking"],
         "Brand": "Le Creuset"
       },
       {
         "ProductID": "1112",
         "SKU": "BC-TKYBST",
         "ProductName": "Turkey Baster",
         "Quantity": 1,
         "ItemPrice": 5.99,
         "RowTotal": 5.99,
         "ImageURL": "http://www.example.com/path/to/product/image2.png",
         "Categories": ["Kitchen Supplies", "Preparation"],
         "Brand": "Betty Crocker"
       }
     ],
      "BillingAddress": {
        "FirstName": "John",
        "LastName": "Smith",
        "Company": "",
        "Address1": "123 abc street",
        "Address2": "apt 1",
        "City": "Boston",
        "Region": "Massachusetts",
        "RegionCode": "MA",
        "Country": "United States",
        "CountryCode": "US",
        "Zip": "02110",
        "Phone": "5551234567"
      },
   },
   "time": 1387302423
}

Ordered Product

For each line item, you should also make Track request payload for an Ordered Product event. This event becomes useful if you plan to create any filters or triggers based on product-specific information (as opposed to an order as a whole) that isn't "top-level" on the Placed Order event. This event is also used in conjunction with your Catalog Feed in order to enable personalized recommendations:

{
   "token": "PUBLIC_API_KEY",
   "event": "Ordered Product",
   "customer_properties": {
     "$email": "john.smith@test.com",,
     "MostRecentStoreLocation": "Boston",
     "MostRecentStoreID": "1212"
   },
   "properties": {
     "$event_id": "1234_LC-8QT",
     "$value": 330.00,
     "StoreID": "1212",
     "ProductID": "1111",
     "SKU": "LC-8QT",
     "ProductName": "8 quart Dutch Oven",
     "Quantity": 1,
     "ImageURL": "http://www.example.com/path/to/product/image.png",
     "ProductCategories": [
       "Kitchen Supplies",
       "Cooking"
     ],
     "ProductBrand": "Le Creuset"
   },
   "time": 1387302423
}

Fulfilled Order, Cancelled Order, and Refunded Order

Depending on how your products are sent to the customer or whether they are able to be cancelled or refunded, you may want to send additional events that reflect these actions. Each of these order-related events will have a payload similar to the Placed Order event.

For Cancelled Order and Refunded Order to be included in CLV calculations, they must have $event_ids that correspond to a previously tracked Placed Order event.

For Fulfilled Order, the only update to be made is the event name and the time at which the fulfillment took place:

{
   "token": "PUBLIC_API_KEY",
   "event": "Fulfilled Order",
   "customer_properties": {
     "$email": "john.smith@test.com",
     "$first_name": "John",
     "$last_name": "Smith",
     "$phone_number": "5551234567",
     "$address1": "123 Abc st",
     "$address2": "Suite 1",
     "$city": "Boston",
     "$zip": "02110",
     "$region": "MA",
     "$country": "USA",
     "Most recent store location": "Boston",
     "Most recent store ID": "1212"
   },
   "properties": {
     "$event_id": "1234",
     "$value": 335.99,
     "Categories": ["Kitchen Supplies", "Cooking", "Preparation"],
     "ItemNames": ["8 quart Dutch Oven", "Turkey Baster"],
     "Brands": ["Le Creuset", "Betty Crocker"],
     "StoreID": "1212",
     "Items": [{
         "ProductID": "1111",
         "SKU": "LC-8QT",
         "ProductName": "8 quart Dutch Oven",
         "Quantity": 1,
         "ItemPrice": 330.00,
         "RowTotal": 330.00,
         "ImageURL": "http://www.example.com/path/to/product/image.png",
         "Categories": ["Kitchen Supplies", "Cooking"],
         "Brand": "Le Creuset"
       },
       {
         "ProductID": "1112",
         "SKU": "BC-TKYBST",
         "ProductName": "Turkey Baster",
         "Quantity": 1,
         "ItemPrice": 5.99,
         "RowTotal": 5.99,
         "ImageURL": "http://www.example.com/path/to/product/image2.png",
         "Categories": ["Kitchen Supplies", "Preparation"],
         "Brand": "Betty Crocker"
       }
     ],
     "ShippingAddress": {
        "FirstName": "John",
        "LastName": "Smith",
        "Company": "",
        "Address1": "123 abc street",
        "Address2": "apt 1",
        "City": "Boston",
        "Region": "Massachusetts",
        "RegionCode": "MA",
        "Country": "United States",
        "CountryCode": "US",
        "Zip": "02110",
        "Phone": "5551234567"
      },
      "BillingAddress": {
        "FirstName": "John",
        "LastName": "Smith",
        "Company": "",
        "Address1": "123 abc street",
        "Address2": "apt 1",
        "City": "Boston",
        "Region": "Massachusetts",
        "RegionCode": "MA",
        "Country": "United States",
        "CountryCode": "US",
        "Zip": "02110",
        "Phone": "5551234567"
      },
   },
   "time": 1387312956
}

For Cancelled Order and Refunded Order, update the event name and the timestamp and add an additional property for the cancellation or refund reason:

Cancelled Order

{
   "token": "PUBLIC_API_KEY",
   "event": "Cancelled Order",
   "customer_properties": {
     "$email": "john.smith@test.com",
     "$first_name": "John",
     "$last_name": "Smith",
     "$phone_number": "5551234567",
     "$address1": "123 Abc st",
     "$address2": "Suite 1",
     "$city": "Boston",
     "$zip": "02110",
     "$region": "MA",
     "$country": "USA",
     "Most recent store location": "Boston",
     "Most recent store ID": "1212"
   },
   "properties": {
     "$event_id": "1234",
     "$value": 335.99,
     "Reason": "No longer needed",
     "Categories": ["Kitchen Supplies", "Cooking", "Preparation"],
     "ItemNames": ["8 quart Dutch Oven", "Turkey Baster"],
     "Brands": ["Le Creuset", "Betty Crocker"],
     "StoreID": "1212",
     "Items": [{
         "ProductID": "1111",
         "SKU": "LC-8QT",
         "ProductName": "8 quart Dutch Oven",
         "Quantity": 1,
         "ItemPrice": 330.00,
         "RowTotal": 330.00,
         "ImageURL": "http://www.example.com/path/to/product/image.png",
         "Categories": ["Kitchen Supplies", "Cooking"],
         "Brand": "Le Creuset"
       },
       {
         "ProductID": "1112",
         "SKU": "BC-TKYBST",
         "ProductName": "Turkey Baster",
         "Quantity": 1,
         "ItemPrice": 5.99,
         "RowTotal": 5.99,
         "ImageURL": "http://www.example.com/path/to/product/image2.png",
         "Categories": ["Kitchen Supplies", "Preparation"],
         "Brand": "Betty Crocker"
       }
     ],
      "BillingAddress": {
        "FirstName": "John",
        "LastName": "Smith",
        "Company": "",
        "Address1": "123 abc street",
        "Address2": "apt 1",
        "City": "Boston",
        "Region": "Massachusetts",
        "RegionCode": "MA",
        "Country": "United States",
        "CountryCode": "US",
        "Zip": "02110",
        "Phone": "5551234567"
      },
   },
   "time": 1387312956
}

Refunded Order

{
   "token": "PUBLIC_API_KEY",
   "event": "Refunded Order",
   "customer_properties": {
     "$email": "john.smith@test.com",
     "$first_name": "John",
     "$last_name": "Smith",
     "$phone_number": "5551234567",
     "$address1": "123 Abc st",
     "$address2": "Suite 1",
     "$city": "Boston",
     "$zip": "02110",
     "$region": "MA",
     "$country": "USA",
     "MostRecentStoreLocation": "Boston",
     "MostRecentStoreID": "1212"
   },
   "properties": {
     "$event_id": "1234",
     "$value": 335.99,
     "Reason": "No longer needed",
     "Categories": ["Kitchen Supplies", "Cooking", "Preparation"],
     "ItemNames": ["8 quart Dutch Oven", "Turkey Baster"],
     "Brands": ["Le Creuset", "Betty Crocker"],
     "StoreID": "1212",
     "Items": [{
         "ProductID": "1111",
         "SKU": "LC-8QT",
         "ProductName": "8 quart Dutch Oven",
         "Quantity": 1,
         "ItemPrice": 330.00,
         "RowTotal": 330.00,
         "ImageURL": "http://www.example.com/path/to/product/image.png",
         "Categories": ["Kitchen Supplies", "Cooking"],
         "Brand": "Le Creuset"
       },
       {
         "ProductID": "1112",
         "SKU": "BC-TKYBST",
         "ProductName": "Turkey Baster",
         "Quantity": 1,
         "ItemPrice": 5.99,
         "RowTotal": 5.99,
         "ImageURL": "http://www.example.com/path/to/product/image2.png",
         "Categories": ["Kitchen Supplies", "Preparation"],
         "Brand": "Betty Crocker"
       }
     ],
      "BillingAddress": {
        "FirstName": "John",
        "LastName": "Smith",
        "Company": "",
        "Address1": "123 abc street",
        "Address2": "apt 1",
        "City": "Boston",
        "Region": "Massachusetts",
        "RegionCode": "MA",
        "Country": "United States",
        "CountryCode": "US",
        "Zip": "02110",
        "Phone": "5551234567"
      },
   },
   "time": 1387312956
}

Catalog Feed Integration

Integrating your catalog will allow you to use our Product Feeds and Product Blocks in emails. In order to set up a custom catalog integration, please reach out to our Support Team. They will pass along the documentation and examples for this setup. They will also need to be notified once the setup is complete in order to activate the feed on your account.

Product Catalog Feed vs. Store Location Feed

Each of these pieces of the integration will be set up as a feed but will use different formats. Because our Catalog Feed feature requires specific fields, some fields must be included (for example, the product url must be included but there may not be an online location to link to for the product). For fields that are unavailable, pass "unavailable" or "n/a". Passing an empty string will cause our validation to ignore that product.

For the feed of your catalog items, include the following information and mapping for each item in the feed:

Item Field

Klaviyo Field to Map to

Expected Data Type

Klaviyo Data Type to Map to

A Unique ID (could be SKU)

$id

string

String

Product title

$title

string

String

Product URL

$link

string

URL

Product description

$description

string

String

Product price

price

number (float)

Number

Image URL

$image_link

string

URL

Categories

categories

array/list of strings

Categories

Comma-separated list of store location IDs ($id from Store Location Feed mapping)

store_ids

string

String

Hiding in-store-only products from Product Feeds: Klaviyo's product feed filters must be based on either the inclusion or exclusion of categories of items in the catalog. If you would like to hide items that only appear at in-store locations from Product Feeds, make sure to include this attribute as one of the categories of that item (eg. a category of "in-store-only").

For the feed of your store locations, include the following information and mapping for each store in the feed.

Item Field

Klaviyo Field to Map to

Expected Data Type

Klaviyo Data Type to Map to

A Unique ID

$id

string

String

Store name

$title

string

String

Store URL

$link

string

URL

Store Description

$description

string

String

Image URL

$image_link

string

URL

Categories

categories

array/list of strings

Categories

Comma-separated list of product IDs ($id from Product Catalog Feed mapping)

product_ids

string

String

Hiding store locations from Product Feeds: Klaviyo's product feed filters must be based on either the inclusion or exclusion of categories of items in the catalog. If you would like to hide store locations from Product Feeds, make sure to include this attribute as one of the categories of that item (eg. a category of "Store Location").

 

Syncing Historical Data

It is good practice to send us your historical order data. This will enhance your ability to segment off that data and improve historical accuracy in revenue tracking. This data can be sent to us by iterating through your historical orders and generating Placed Order and Ordered Product Track API requests for each.

x
Was this article helpful?
3 out of 6 found this helpful