Integrate a business with brick-and-mortar locations

Learn how to sync important metrics, or key customer activities, to Klaviyo for a business with brick-and-mortar locations.

❗️

This guide refers to Klaviyo's v1/v2 legacy APIs.

We are in the process of updating these guides, please check back later! For more information about the differences between legacy and new APIs, check out the comparison chart and new API overview.

A similar layout to that shown in this guide can also be used for other in-person situations (e.g., a trade show or convention). If your business has brick-and-mortar locations, and the point-of-sale platform you use is not supported by one of Klaviyo’s pre-built integrations, you can integrate with Klaviyo using our server-side API and custom catalog integration. If your business also has an online store that Klaviyo does not yet have a pre-built integration for, please take a look at our general guide.

Key integration components

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

  • Customer data, such as names, email addresses, etc. 
  • Location data, such as what stores customers have visited
  • In-store activity, such as orders placed by customers
  • Order activity, such as what products customers are ordering

About JavaScript and server-side Track and Identify APIs

🚧

Klaviyo is optimized for using email as a profile’s primary identifier, so we highly recommend using email addresses when tracking data. While we do support alternative primary identifiers, this guide features examples that use email addresses.

Use this as a checklist when setting up your integration:

  • Use our server-side Track API for the following events:

    • Placed Order — when an order 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, if you want to have these accessible in Klaviyo

Note that if you have both an ecommerce and a brick-and-mortar store, you can have a unified metric for both sources. For example, Placed Order can have the same name coming from both sources. Alternatively, if you want to keep the sources separate, you can rename the above events as Placed Order Retail, Ordered Product Retail, etc. 

The level of detailed data you send to Klaviyo within these 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.

🚧

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

Check out our custom integration FAQ for any questions about custom integrations.

Tracking server-side metrics

We recommend tracking certain metrics on the server-side due to potential limitations of frontend code, security concerns, and general availability of data on the server-side versus the front-end. For example, if someone has a slow connection or a JavaScript-blocking plugin on their browser, the JavaScript requests might not fire. In the case of more crucial metrics (e.g., transactional events and properties) or ones that may contain sensitive data, use our server-side Track API. For more information on the differences between the two, check out our custom integration FAQ on the topic

We recommend tracking the following metrics:

  • Placed Order
  • Ordered Product
  • Fulfilled Order
  • Cancelled Order
  • Refunded Order

We also have libraries in several server-side languages. Generally, the API requires making an HTTP GET request with a base64 encoded JSON payload. With the Track API, data can be sent to Klaviyo with an HTTP POST request. More information can be found in our Track and Identify API reference.

For ongoing data, you can send order data to Klaviyo in one of two ways: 

  • Real-time
    Make requests as soon as an order is placed
  • Batch
    Write some code that will run (for example) at least every 30 minutes (e.g., on a cron) to send all order events that occurred in that past 30 minutes

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. You can send this data to Klaviyo 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. Read more about backfilling historical data in our custom integration FAQ on the topic

Placed Order

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

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.
  • 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:

  • Replace PUBLIC_API_KEY with your public API key.
  • The $event_id should be a unique identifier for the order (e.g., Order ID).
  • If the same combination of event and $event_id are sent more than once, Klaviyo 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 numeric (not a string), monetary value of the event it’s associated with.
  • The "Items" array should contain one JSON block/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": "[email protected]",
     "$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,
     "OrderId": "1234",
     "Categories": ["Kitchen Supplies", "Cooking", "Preparation"],
     "ItemNames": ["8 quart Dutch Oven", "Turkey Baster"],
     "Brands": ["Le Creuset", "Betty Crocker"],
     "StoreId": "1212",
     "StoreLocation": "Boston",
     "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

Tracking an Ordered Product event for each line item in the order enables you to:

  • Incorporate in-store products into Klaviyo’s personalized recommendations
  • Segment profiles, and trigger or filter flows based on specific attributes of single in-store items

📘

Leveraging the top-level properties of in-store items for segmentation or flow triggering/filtering can also be achieved using the Placed Order event. To do this, your Placed Order event must have an Items array including each line item, as recommended in the Placed Order section above.

Tracking the Ordered Product event from your brick-and-mortar store is therefore optional; if you do not want to leverage personalized recommendations based on in-store orders (if you are already doing this with online orders, for example) and are using Placed Order for segmentation or flow triggering/filtering, you might consider it unnecessary. 

To set up an Ordered Product-style event for each line item, make a Track request payload similar to the following example:

{
   "token": "PUBLIC_API_KEY",
   "event": "Ordered Product",
   "customer_properties": {
     "$email": "[email protected]",,
     "MostRecentStoreLocation": "Boston",
     "MostRecentStoreId": "1212"
   },
   "properties": {
     "$event_id": "1234_LC-8QT",
     "$value": 330.00,
     "OrderId": "1234",
     "StoreId": "1212",
     "StoreLocation": "Boston",
     "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

For Fulfilled Order, the only update needed is the metric name and the time at which the fulfillment took place. You can also track additional details about the fulfillment itself (e.g., tracking number, shipping method).

{
   "token": "PUBLIC_API_KEY",
   "event": "Fulfilled Order",
   "customer_properties": {
     "$email": "[email protected]",
     "$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,
     "OrderId": "1234",
     "Categories": ["Kitchen Supplies", "Cooking", "Preparation"],
     "ItemNames": ["8 quart Dutch Oven", "Turkey Baster"],
     "Brands": ["Le Creuset", "Betty Crocker"],
     "StoreId": "1212",
     "StoreLocation": "Boston",
     "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
}

Cancelled Order and Refunded Order

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

For these events, update the metric name and timestamp, and add an additional property for the cancellation or refund reason. You can also include which items were and weren’t cancelled in the event payload, in case the order is only partially cancelled or refunded. 

📘

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

Cancelled Order example

{
   "token": "PUBLIC_API_KEY",
   "event": "Cancelled Order",
   "customer_properties": {
     "$email": "[email protected]",
     "$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,
     "OrderId": "1234",
     "Reason": "No longer needed",
     "Categories": ["Kitchen Supplies", "Cooking", "Preparation"],
     "ItemNames": ["8 quart Dutch Oven", "Turkey Baster"],
     "Brands": ["Le Creuset", "Betty Crocker"],
     "StoreId": "1212",
     "StoreLocation": "Boston",
     "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 example

{
   "token": "PUBLIC_API_KEY",
   "event": "Refunded Order",
   "customer_properties": {
     "$email": "[email protected]",
     "$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,
     "OrderId": "1234",
     "Reason": "No longer needed",
     "Categories": ["Kitchen Supplies", "Cooking", "Preparation"],
     "ItemNames": ["8 quart Dutch Oven", "Turkey Baster"],
     "Brands": ["Le Creuset", "Betty Crocker"],
     "StoreId": "1212",
     "StoreLocation": "Boston",
     "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
}

Server-side profile property: Recent Stores

Specific store IDs and locations are tracked on the event payloads described above. However, it can also be helpful to track this information as a profile property — for example, if you want to use it in a profile-property based segment.

By adding a profile property for Recent Stores, you’ll be able to:

  • Create segments that target campaigns or forms to people who have only visited or purchased at specific store locations (e.g., exclusive offers or upsells meant for certain regions).
  • Create branching flow or conditional email logic to send different email content based on whether or not someone visited a given store location.

Here is an example server-side Identify API request payload for recent store properties:

{
   "token": "PUBLIC_API_KEY",
   "properties": {
     "$email": "[email protected]",
     "MostRecentStoreId": "1212",
     "MostRecentStoreLocation": "Boston"
   }
 }

Catalog feed integration

Integrating your catalog will allow you to utilize product blocks in emails. In order to set up a custom catalog integration, please follow the process outlined in Sync a custom catalog feed to Klaviyo.

Product catalog feed and store location feed

You can use the custom catalog feature to set up a feed of the items in your store, a feed of your store locations, or both. These will require separate feeds. 

Klaviyo’s catalog feed feature requires specific fields. Some fields may be unavailable; for example, the product URL must be included, but there may not be an online location to link to the product. For fields that are unavailable, pass "unavailable" or "n/a" Passing an empty string will cause Klaviyo’s validation to ignore that product.

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

Store Items Item FieldStore Locations Item FieldKlaviyo Field to Map ToExpected Data TypeKlaviyo Data Type to Map To
A unique ID (could be SKU)A unique ID$idStringString
Product title Store name$titleStringString
Product URLStore URL$linkString URL
Product descriptionStore description$descriptionStringString
Product priceN/APriceNumber (float)Number
Image URLImage URL$image_linkStringURL
CategoriesCategoriesCategoriesArray/list of stringsCategories
Comma-separated list of store location IDs ($id from store location feed mapping)N/Astore_idsStringString
N/AComma-separated list of product IDs ($id from Product Catalog Feed mapping)product_idsStringString

📘

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. To hide items (or stores) from product feeds, such as those that only appear at in-store locations, make sure to include this attribute as one of the categories of that item (e.g., a category of "in-store-only").

Additional resources