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.
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 Create Event endpoint and custom catalog feature. 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 server-side Event APIs and custom catalogs
Klaviyo is optimized for using email as a profile’s primary identifier, so we highly recommend using email addresses when tracking data. This guide features examples that use email addresses.
Use this as a checklist when setting up your integration:
-
Use our Events 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. - Canceled Order
When a customer cancels their order. - Refunded Order
When a customer’s order is refunded.
- Placed Order
-
Use our custom catalog feed feature, or our Catalogs API, 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.
- Catalog feed
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.
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 Events 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
Klaviyo also has SDKs in several languages.
With the Events API, data can be sent to Klaviyo with an HTTP POST request.
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
Along with your ongoing data, it is best practice to send your historical order data, which will enhance your ability to segment off past data and improve historical accuracy in revenue tracking and predictive analytics. Historical data can be sent to Klaviyo by iterating through your historical orders and generating POST Create Event API requests for each server-side event as needed. The special time property for these events should be in ISO 8601 datetime
(i.e., 2022-11-08T00:00:00
) of when that order occurred.
Placed Order
After an order is placed, make a POST request to the Create Event API to create a Placed Order event. Send order data to Klaviyo in one of two ways: real-time or batch.
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 the entire order.
- This includes a
- One event for each line item named Ordered Product (see below).
- 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.
- This includes a
Key things to be aware of when tracking server-side events:
- Make sure to replace
PRIVATE_API_KEY
with a private key from your Klaviyo account; this key must have write permissions to create events. - The
unique_id
should be a unique identifier for the order (e.g., Order ID). - If the same combination of event and
unique_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 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 an ISO 8601datetime
(i.e.2022-11-08T00:00:00Z
) for the order date and time.
Here’s an example POST Create Event request for Placed Order:
curl --request POST \
--url https://a.klaviyo.com/api/events/ \
--header 'Authorization: Klaviyo-API-Key your-private-api-key' \
--header 'accept: application/json' \
--header 'content-type: application/json' \
--header 'revision: 2023-12-15' \
--data '
{
"data": {
"type": "event",
"attributes": {
"properties": {
"Store ID": "123",
"Store Location": "555 Home Street, Boston, MA 02110",
"OrderId": "1234567890",
"Categories": ["Fiction", "Classics", "Children"],
"ItemNames": ["Winnie the Pooh", "A Tale of Two Cities"],
"Brands": ["Kids Books", "Harcourt Classics"],
"DiscountCode": "Free Shipping",
"DiscountValue": 5,
"Total Tax": 1.25,
"Subtotal": 29.98,
"Items": [{
"ProductID": "1111",
"SKU": "WINNIEPOOH",
"ProductName": "Winnie the Pooh",
"Quantity": 1,
"ItemPrice": 9.99,
"RowTotal": 9.99,
"ProductURL": "http://www.example.com/path/to/product",
"ImageURL": "http://www.example.com/path/to/product/image.png",
"Categories": ["Fiction", "Children"],
"Brand": "Kids Books"
},
{
"ProductID": "1112",
"SKU": "TALEOFTWO",
"ProductName": "A Tale of Two Cities",
"Quantity": 1,
"ItemPrice": 19.99,
"RowTotal": 19.99,
"ProductURL": "http://www.example.com/path/to/product2",
"ImageURL": "http://www.example.com/path/to/product/image2.png",
"Categories": ["Fiction", "Classics"],
"Brand": "Harcourt Classics"
}
]
},
"time": "2022-11-08T00:00:00",
"value": 9.99,
"metric": {
"data": {
"type": "metric",
"attributes": {
"name": "Placed Order"
}
}
},
"profile": {
"data": {
"type": "profile",
"attributes": {
"email": "[email protected]",
"phone_number": "+15005550006",
"first_name": "Sarah",
"last_name": "Mason",
"image": "https://images.pexels.com/photos/3760854/pexels-photo-3760854.jpeg",
"location": {
"address1": "89 E 42nd St",
"address2": "1st floor",
"city": "New York",
"country": "United States",
"region": "NY",
"zip": "10017",
"timezone": "America/New_York"
},
"properties": {
"Most Recent Store ID": "123",
"Most Recent Store Location": "555 Home Street, Boston, MA 02110"
}
}
},
"unique_id":"9619ed63-6b9d-4b7c-ac3d-975f3115f9ec"
}
}
}
}
'
Creating an event requires at least one profile identifier. For example, the Placed Order event from the POST Create Event call above uses
Ordered Product
Ordered Product event. This metric is useful if you plan to create any filters or triggers based on product-specific information (as opposed to the order as a whole) that isn't top-level for the Placed Order metric. This metric is also used in conjunction with your catalog feed in order to enable personalized recommendations and in the benchmarks feature to calculate average item value and average cart size.
The remainder of the POST Create Event calls in this guide will use the same headers as the Placed Order call.
curl --request POST \
--url https://a.klaviyo.com/api/events/ \
--header 'Authorization: Klaviyo-API-Key your-private-api-key' \
--header 'accept: application/json' \
--header 'content-type: application/json' \
--header 'revision: 2023-12-15' \
--data '
{
"data": {
"type": "event",
"attributes": {
"properties": {
"Store ID": "123",
"Store Location": "555 Home Street, Boston, MA 02110",
"OrderId": "1234567890",
"ProductID": "1111",
"SKU": "WINNIEPOOH",
"ProductName": "Winnie the Pooh",
"Quantity": 1,
"ProductURL": "http://www.example.com/path/to/product",
"ImageURL": "http://www.example.com/path/to/product/image.png",
"Categories": [
"Fiction",
"Children"
],
"ProductBrand": "Kids Books"
},
"time": "2024-02-03T00:00:00",
"value": 9.99,
"metric": {
"data": {
"type": "metric",
"attributes": {
"name": "Ordered Product"
}
}
},
"profile": {
"data": {
"type": "profile",
"attributes": {
"email": "[email protected]",
"phone_number": "+15005550006"
}
}
}
},
"unique_id": "a25e4c18-a8e0-4239-b140-f2afbf6de0d1"
}
}
'
Fulfilled Order, Cancelled Order, 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 Cancelled Order and Refunded Order to be included in CLV calculations, they must have
unique_ids
that correspond to a previously tracked Placed Order event.
Fulfilled Order example
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).
curl --request POST \
--url https://a.klaviyo.com/api/events/ \
--header 'Authorization: Klaviyo-API-Key your-private-api-key' \
--header 'accept: application/json' \
--header 'content-type: application/json' \
--header 'revision: 2023-12-15' \
--data '
{
"data": {
"type": "event",
"attributes": {
"properties": {
"Store ID": "123",
"Store Address": "555 Home Street, Boston, MA 02110",
"OrderId": "1234",
"Categories": [
"Fiction",
"Classics",
"Children"
],
"ItemNames": [
"Winnie the Pooh",
"A Tale of Two Cities"
],
"Brands": [
"Kids Books",
"Harcourt Classics"
],
"Items": [{
"ProductID": "1111",
"SKU": "WINNIEPOOH",
"ProductName": "Winnie the Pooh",
"Quantity": 1,
"ItemPrice": 9.99,
"RowTotal": 9.99,
"ProductURL": "http://www.example.com/path/to/product",
"ImageURL": "http://www.example.com/path/to/product/image.png",
"Categories": ["Fiction", "Children"],
"Brand": "Kids Books"
},
{
"ProductID": "1112",
"SKU": "TALEOFTWO",
"ProductName": "A Tale of Two Cities",
"Quantity": 1,
"ItemPrice": 19.99,
"RowTotal": 19.99,
"ProductURL": "http://www.example.com/path/to/product2",
"ImageURL": "http://www.example.com/path/to/product/image2.png",
"Categories": ["Fiction", "Classics"],
"Brand": "Harcourt Classics"
}
],
"ShippingAddress": {
"FirstName": "Sarah",
"LastName": "Mason",
"Address1": "123 abc street",
"Address2": "apt 1"
}
},
"time": "2023-12-07T10:00:00Z",
"value": 29.98,
"metric": {
"data": {
"type": "metric",
"attributes": {
"name": "Fulfilled Order"
}
}
},
"profile": {
"data": {
"type": "profile",
"attributes": {
"email": "[email protected]",
"phone_number": "+15005550006"
}
}
}
},
"unique_id": "1081ca61-23b5-4e08-b2a9-c4d3d41e4259"
}
}
'
Cancelled Order example
For Cancelled Order, update the metric name and timestamp, and add an additional property for the cancellation reason. You can also include which items were and weren’t cancelled in the event payload, in case the order is only partially cancelled.
curl --request POST \
--url https://a.klaviyo.com/api/events/ \
--header 'Authorization: Klaviyo-API-Key your-private-api-key' \
--header 'accept: application/json' \
--header 'content-type: application/json' \
--header 'revision: 2023-12-15' \
--data '
{
"data": {
"type": "event",
"attributes": {
"properties": {
"Store ID": "123",
"Store Location": "555 Home Street, Boston, MA 02110",
"OrderId": "1234567890",
"Reason": "No longer needed",
"Categories": [
"Fiction",
"Classics",
"Children"
],
"ItemNames": [
"Winnie the Pooh",
"A Tale of Two Cities"
],
"Brands": [
"Kids Books",
"Harcourt Classics"
],
"Items": [{
"ProductID": "1111",
"SKU": "WINNIEPOOH",
"ProductName": "Winnie the Pooh",
"Quantity": 1,
"ItemPrice": 9.99,
"RowTotal": 9.99,
"ProductURL": "http://www.example.com/path/to/product",
"ImageURL": "http://www.example.com/path/to/product/image.png",
"Categories": ["Fiction", "Children"],
"Brand": "Kids Books"
},
{
"ProductID": "1112",
"SKU": "TALEOFTWO",
"ProductName": "A Tale of Two Cities",
"Quantity": 1,
"ItemPrice": 19.99,
"RowTotal": 19.99,
"ProductURL": "http://www.example.com/path/to/product2",
"ImageURL": "http://www.example.com/path/to/product/image2.png",
"Categories": ["Fiction", "Classics"],
"Brand": "Harcourt Classics"
}
],
"ShippingAddress": {
"FirstName": "Sarah",
"LastName": "Mason",
"Address1": "123 abc street",
"Address2": "apt 1"
}
},
"time": "2023-12-07T11:00:00Z",
"value": 26.23,
"metric": {
"data": {
"type": "metric",
"attributes": {
"name": "Cancelled Order"
}
}
},
"profile": {
"data": {
"type": "profile",
"attributes": {
"email": "[email protected]",
"phone_number": "+15005550006"
}
}
}
},
"unique_id": "9619ed63-6b9d-4b7c-ac3d-975f3115f9ec"
}
}
'
Refunded Order example
For Refunded Order, update the metric name and timestamp, and add an additional property for the refund reason. You can also include which items were and weren’t refunded in the event payload, in case the order is only partially refunded.
curl --request POST \
--url https://a.klaviyo.com/api/events/ \
--header 'Authorization: Klaviyo-API-Key your-private-api-key' \
--header 'accept: application/json' \
--header 'content-type: application/json' \
--header 'revision: 2023-12-15' \
--data '
{
"data": {
"type": "event",
"attributes": {
"properties": {
"OrderId": "1234",
"Reason": "No longer needed",
"Categories": [
"Fiction",
"Classics",
"Children"
],
"ItemNames": [
"Winnie the Pooh",
"A Tale of Two Cities"
],
"Brands": [
"Kids Books",
"Harcourt Classics"
],
"DiscountCode": "Free Shipping",
"DiscountValue": 5,
"Items": [
{
"ProductID": "1111",
"SKU": "WINNIEPOOH",
"ProductName": "Winnie the Pooh",
"Quantity": 1,
"ItemPrice": 9.99,
"RowTotal": 9.99,
"ProductURL": "http://www.example.com/path/to/product",
"ImageURL": "http://www.example.com/path/to/product/image.png",
"Categories": [
"Fiction",
"Children"
],
"Brand": "Kids Books"
},
{
"ProductID": "1112",
"SKU": "TALEOFTWO",
"ProductName": "A Tale of Two Cities",
"Quantity": 1,
"ItemPrice": 19.99,
"RowTotal": 19.99,
"ProductURL": "http://www.example.com/path/to/product2",
"ImageURL": "http://www.example.com/path/to/product/image2.png",
"Categories": [
"Fiction",
"Classics"
],
"Brand": "Harcourt Classics"
}
],
"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"
},
"ShippingAddress": {
"FirstName": "John",
"LastName": "Smith",
"Company": "",
"Address1": "123 abc street",
"Address2": "apt 1",
"City": "Boston",
"Region": "Massachusetts",
"Region_code": "MA",
"Country": "United States",
"Country_code": "US",
"Zip": "02110",
"Phone": "5551234567"
}
},
"time": "2022-11-08T00:00:00",
"value": 9.99,
"metric": {
"data": {
"type": "metric",
"attributes": {
"name": "Refunded Order"
}
}
},
"profile": {
"data": {
"type": "profile",
"attributes": {
"email": "[email protected]",
"phone_number": "+15005550006",
"external_id": "63f64a2b-c6bf-40c7-b81f-bed08162edbe",
"first_name": "John",
"last_name": "Smith"
}
}
}
},
"unique_id": "9619ed63-6b9d-4b7c-ac3d-975f3115f9ec"
}
}
'
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 profile that includes Recent Store properties:
"profile": {
"data": {
"type": "profile",
"id": "01GDDKASAP8TKDDA2GRZDSVP4H",
"attributes": {
"email": "[email protected]",
"phone_number": "+15005550006"
},
"properties": {
"Most Recent Store ID": "123",
"MostRecentStoreLocation": "555 Home Street, Boston, MA 02110"
}
}
}
Sync your catalog feed
Syncing your catalog feed will allow you to utilize product blocks in emails. In order to set up a custom catalog feed, please follow the process outlined in Sync a custom catalog feed to Klaviyo. You can also choose to sync your catalog via our Catalogs API.
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 Field | Store Locations Item Field | Klaviyo Field to Map To | Expected Data Type | Klaviyo Data Type to Map To |
---|---|---|---|---|
A unique ID (could be SKU) | A unique ID | $id | String | String |
Product title | Store name | $title | String | String |
Product URL | Store URL | $link | String URL | |
Product description | Store description | $description | String | String |
Product price | N/A | Price | Number (float) | Number |
Image URL | Image URL | $image_link | String | URL |
Categories | Categories | Categories | Array/list of strings | Categories |
Comma-separated list of store location IDs ($id from store location feed mapping) | N/A | store_ids | String | String |
N/A | Comma-separated list of product IDs ($id from Product Catalog Feed mapping) | product_ids | String | String |
Hiding in-store-only products from product feeds: Klaviyo's product feed filters can be based on 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 for "in-store-only").
Additional resources
Updated about 1 month ago