Enable removed-from-cart signals in the Connect web library
The removed-from-cart signal fires when a user removes a product, service or offering from their shopping cart. This signal is essential for understanding customer purchase intent, identifying friction points in the buying journey and enabling targeted re-engagement campaigns.
This signal tracks detailed information about products that customers have removed from their cart, including product details, pricing, quantities and promotional information. By capturing this data, you can:
- Identify products with high abandonment rates
- Trigger personalized win-back campaigns for abandoned items
- Analyze pricing sensitivity and discount effectiveness
- Optimize the checkout experience by understanding removal patterns
- Create targeted segments based on cart abandonment behavior
Availability: Pro, Premium and Ultimate
Implementation considerations
Removal locations
Users can remove items from different locations on your website, depending on the design:
- Shopping cart page - The main cart view where users review their selections
- Mini-cart or cart dropdown - Quick cart access from the header or sidebar
- Checkout page - Last-minute removals during the checkout process
- Mobile cart drawer - Slide-out cart panels on mobile devices
Each location may have different HTML structures and require different selectors.
Triggering the signal
Decide when to send the signal based on your site's behavior:
- Immediate (on click) - Fire the signal as soon as the user clicks the Remove button. Best for tracking intent, even if the action fails.
- Delayed (on cart update) - Fire the signal after confirming the item was removed from the cart. Ensures accuracy but may miss failed attempts.
Removal interactions
Different removal patterns may require different handling:
- Remove button - Explicit "Remove" or "Delete" buttons
- Quantity to zero - Users changing quantity to 0
- Quantity down - Users decreasing quantity using down arrow or manually entering a smaller number
- Drag to remove - Swipe or drag gestures on mobile
- Clear cart - Removing all items at once (may need to send individual signals for each item)
Data availability
Consider how product data is available when an item is removed:
- From DOM - Product information is visible in the cart UI (easiest approach)
- From cart state - Your application maintains a cart data object
- From data layer - Cart removal events push data to a data layer (for example, Google Tag Manager)
Contact mapping
Any signal can be mapped to a contact. Use the audience object to provide a customer ID (contact key) or an addressable attribute (email or phone number). You can provide several identification records in the same signal — for example, a contact key and an email address. For details on how Connect processes identification records, see How behavior signals are processed in Connect.
Several sources of identification are available:
- User identification stored in browser cookies
- Temporary session-based storage
- Persistent client-side storage
- Data layer (for example, Google Tag Manager
dataLayer) - URL parameters passed during navigation
- Login API monitoring (capture identifier during successful authentication)
If none of these options are available, work with your development team to make the identifier accessible to the Connect library.
Important
We recommend attaching identifiers to as many signals as possible. However, if an identifier is missing, the system falls back on other signals from the same user session in order to identify the website visitor.
Configuration
Method
TLT.logSignal(signalObject)
Sends the signal to the Acoustic Connect endpoint as a JSON object. The Connect library must be initialized before calling this method.
Signal structure
Top-level fields
audience: Array of objects - Create an object for contact mapping and any number of additional objects to update contact attributes.category: String - Valid value -Behavior.consent: Object - Consent preferences to update for the contact. At least one of the following channel fields is required:email,smsorwhatsapp.description: String - Description of the signaleffect: String - Describes the effect of the signal on engagement. It is intended to be used for engagement index scoring. Valid values:negative,positive. Usenegativefor all removed-from-cart signals.name: String - Name to differentiate this signal from others (example: "removedFromCart from cart page" vs "removedFromCart from mini-cart"). Max length - 256 chars.signalType(required): String - Valid value -removedFromCart.
Audience object
Create an object with one or more identifiers for contact mapping (recommended) and any number of additional objects for other contact attributes you want to update.
name(required): String - Name of contact attribute. To get the full list of available attributes, use the Contact attributes query.value: The new value of the contact attribute. Its format depends on the attribute type (text, number, Boolean or date). If you send an empty or null value, it will be ignored and the contact attribute won't be updated.
Note
- For date attributes, use the following format:
yyyy-MM-dd'T'HH:mm:ssXXX.- For phone number attributes, use the ISO E.164 format:
+[country code][area code][phone number]— no spaces, dashes or special characters. If the+symbol is omitted, Connect adds it automatically.
- Consent object
Each channel accepts multiple entries to set different consent statuses per consent group in a single call.
enableOverrideExistingOptOut(required): Boolean - Whentrue, current opt-out statuses will be overridden by new statuses. Whenfalse, contacts who have unsubscribed from communications won't have their consent status updated.email: Array of objects - Consent entries for the email channel. The objects support the following fields:status(required): Enum - Consent status for the email channel. Valid values:OPT_IN,OPT_OUT,OPT_IN_UNVERIFIED.consentGroupIds: Array of strings - The IDs of consent groups a new status applies to. If you skip this field or send an empty array, the new consent status will be applied to all consent groups.
sms: Array of objects - Consent entries for the SMS channel. The objects support the following fields:status(required): Enum - Consent status for the SMS channel. Valid values:OPT_IN,OPT_OUT,OPT_IN_UNVERIFIED.consentGroupIds: Array of strings - The IDs of consent groups a new status applies to. If you skip this field or send an empty array, the new consent status will be applied to all consent groups.
whatsapp: Array of objects - Consent entries for the WhatsApp channel. The objects support the following fields:status(required): Enum - Consent status for the WhatsApp channel. Valid values:OPT_IN,OPT_OUT,OPT_IN_UNVERIFIED.consentGroupIds: Array of strings - The IDs of consent groups a new status applies to. If you skip this field or send an empty array, the new consent status will be applied to all consent groups.
Note
To get the IDs of consent groups, use the dataSets query.
Signal content
-
currency: String - ISO 4217 currency code (for example, "USD", "EUR", "GBP"). If there is only one currency on your website, provide a static value. If there is a selection, use dynamic values. If a removed-from-cart signal comes without a value, the system will fall back on the default currency set for your Connect subscription. -
discount: Number - Discount amount or difference between original and current price -
imageUrls: Array of strings - The URLs of product images -
itemQuantity(required): Number - Quantity of items removed from the cart (not the quantity that remain). Use integers. -
productCategory: String - Product category name based on catalog -
productCategoryId: String - Product category identifier -
productDescription: String - Description of the product -
productId(required): String - Unique product identifier (may match SKU) -
productName(required): String - Name of the product -
productUrls: Array of strings - The URLs of product pages -
promotionId: String - ID from marketing campaigns associated with this product -
shoppingCartUrl: String - URL of the shopping cart -
unitPrice(required): Number - Unit price of the product -
virtualCategory: String - Category based on navigation path (e.g., "New arrivals", "Sale")
Notes:
- If any required field is missing or invalid, the entire signal will be discarded.
- Optional fields enhance the signal but won't prevent processing if omitted or invalid.
- If a required field isn't applicable to your business, use a placeholder value.
Basic example
// Initialize the Connect library
if (window.TLT) {
TLT.initPremium({
appKey: "APP_KEY",
postUrl: "COLLECTOR_URL",
callback: function() {
const signal = {
// Required fields
signalType: "removedFromCart",
productId: "SKU-12345",
productName: "Wireless Headphones",
itemQuantity: 1,
unitPrice: 299.99,
// Optional but recommended fields
category: "Behavior",
name: "removedFromCart from cart page",
effect: "negative",
currency: "USD",
productDescription: "Premium noise-cancelling headphones",
discount: 30.00,
productCategory: "Electronics",
productUrls: ["https://example.com/products/headphones"],
imageUrls: ["https://example.com/images/headphones.jpg"],
shoppingCartUrl: "https://example.com/cart",
// Contact mapping
audience: [
{
name: "Customer ID",
value: "AAUN-1417508"
}
]
};
// Send the signal
TLT.logSignal(signal);
}
});
}
Complete implementation example
This example handles removal events from both the main cart page and a mini-cart dropdown. The audience identifier is retrieved from session storage, where it was stored during login.
// Initialize the Connect library
if (window.TLT) {
TLT.initPremium({
appKey: "APP_KEY",
postUrl: "COLLECTOR_URL",
callback: function() {
// Define the signal template
const signal = {
// Required fields
signalType: "removedFromCart",
productId: "",
productName: "",
itemQuantity: 0,
unitPrice: 0,
// Optional fields
name: "removedFromCart generated by website",
category: "Behavior",
currency: "USD",
effect: "negative",
productDescription: "",
discount: 0,
productCategory: "",
productCategoryId: "",
productUrls: [],
imageUrls: [],
shoppingCartUrl: "https://" + window.location.host + "/shopping-cart",
virtualCategory: "",
promotionId: ""
};
// Add contact mapping from session storage
const customerId = sessionStorage.getItem("customerId");
if (customerId) {
signal.audience = [
{
name: "Customer ID",
value: customerId
}
];
}
// Define selectors for different remove button types
const removeButtons = [{
selector: ".cart-item__remove",
type: "Cart page"
},
{
selector: ".mini-cart__remove-btn",
type: "Mini-cart"
}
];
// Attach click listeners to all matching elements
removeButtons.forEach((removeButton) => {
const elements = document.querySelectorAll(removeButton.selector);
if (!elements.length) return;
elements.forEach((element) => {
element.addEventListener("click", () => {
// Find the cart item container
const cartItem = element.closest(".cart-item") || element.closest(".mini-cart__item");
if (cartItem) {
// Extract product information from the cart item
signal.productId = cartItem.getAttribute("data-product-id") ||
cartItem.querySelector(".product-id")?.innerText || "";
signal.productName = cartItem.getAttribute("data-product-name") ||
cartItem.querySelector(".product-name")?.innerText || "";
signal.itemQuantity = Number(cartItem.querySelector(".quantity-input")?.value) ||
Number(cartItem.getAttribute("data-quantity")) || 1;
// Extract pricing information
const priceElement = cartItem.querySelector(".price-current") ||
cartItem.querySelector(".product-price");
signal.unitPrice = priceElement ?
parseFloat(priceElement.innerText.replace(/[^0-9.]/g, "")) : 0;
const originalPriceElement = cartItem.querySelector(".price-original");
if (originalPriceElement) {
const originalPrice = parseFloat(originalPriceElement.innerText.replace(/[^0-9.]/g, ""));
signal.discount = Math.round((originalPrice - signal.unitPrice) * 100) / 100;
}
// Extract optional information
const productLink = cartItem.querySelector("a.product-link");
if (productLink) {
signal.productUrls = [productLink.href];
}
const productImage = cartItem.querySelector(".product-image img");
if (productImage) {
signal.imageUrls = [productImage.src.split("?")[0]];
}
const categoryElement = cartItem.querySelector(".product-category");
if (categoryElement) {
signal.productCategory = categoryElement.innerText;
}
// Set removal location
signal.name = `removedFromCart from ${removeButton.type}`;
console.log("removedFromCart signal:", JSON.stringify(signal, undefined, 2));
TLT.logSignal(signal);
}
});
});
});
// Handle quantity changes to zero (alternative removal method)
const quantityInputs = document.querySelectorAll(".quantity-input");
quantityInputs.forEach((input) => {
input.addEventListener("change", (event) => {
const newQuantity = Number(event.target.value);
if (newQuantity === 0) {
const cartItem = input.closest(".cart-item");
if (cartItem) {
signal.productId = cartItem.getAttribute("data-product-id") || "";
signal.productName = cartItem.getAttribute("data-product-name") || "";
signal.itemQuantity = Number(cartItem.getAttribute("data-quantity")) || 1;
const priceElement = cartItem.querySelector(".price-current");
signal.unitPrice = priceElement ?
parseFloat(priceElement.innerText.replace(/[^0-9.]/g, "")) : 0;
signal.name = "removedFromCart by quantity change";
console.log("removedFromCart signal:", JSON.stringify(signal, undefined, 2));
TLT.logSignal(signal);
}
}
});
});
}
});
}
Related pages
Updated 3 days ago
