Enable order signals in the Connect web library
The order signal captures when a user completes a transaction on your website. This signal is essential for tracking conversions, calculating revenue, and triggering post-purchase campaigns.
Typical use cases across business domains:
- Retail: Order confirmation
- Travel: Booking confirmation
- Banking/Insurance: Application confirmation
Availability: Pro, Premium and Ultimate
Implementation considerations
Order confirmation challenge
The order signal is one of the most complex to implement because order details are rarely all available on the order confirmation page. Key data points are often only accessible earlier in the checkout process, but you cannot fire the signal until an order ID is generated.
Recommended approach: Store partial order data in the browser session storage during checkout, then complete and send the signal once the order is confirmed. Delete the signal from the storage after that.
Data collection strategy
Plan your implementation around these stages:
- Checkout/cart page - Collect product details, quantities, prices.
- Order placement - Store collected data in session storage.
- Confirmation page - Retrieve stored data, add order ID, send signal.
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. We suggest sendingpositivefor all order signals.name: String - Name to differentiate this signal from others (for example, "Order from website"). Max length - 256 chars.signalType(required): String - Valid value -order.
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 as it appears in Connect (case sensitive). 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 an order signal comes without a value, the system will fall back on the default currency set for your Connect subscription.orderDiscount: Number - Total discount applied to the orderorderId(required): String - Unique order identifier (usually scraped from URL or page content)orderedItems: Array of objects - List of ordered items (see structure below). You need a separate object for each product, but not each instance of the same product - three identical items would result in one object, with aquantityof 3. ⚠️ IforderedItemsfails validation, the entire signal will be discarded.orderShippingHandling: Number - Shipping and handling costorderSubtotal: Number - Subtotal amount (net of discount)orderTax: Number - Tax amountorderValue: Number - Total order value (including tax and shipping)signalCustomAttributes: Array of objects - Additional custom attributes. Each object hasnameandvaluestring fields.
Ordered items object
discount: Number - Discount amount per unitimageUrls: Array of strings - URLs of product imagesitemQuantity: Number - Quantity of this product purchasedproductCategory: String - Product category from catalogproductCategoryId: String - Product category identifierproductDescription: String - Description of the productproductId(required): String - Unique product identifier (may match SKU)productName(required): String - Name of the productproductUrls: Array of strings - URLs of product pagespromotionId: String - ID from marketing campaigns that influenced the purchaseshoppingCartUrl: String - URL of the shopping cartunitPrice(required): Number - Price the customer paid for one unitvirtualCategory: String - Category based on navigation path (e.g., "New arrivals", "Sale")
Note:
- If any required field is missing or invalid, the entire signal will be discarded.
- Optional fields enhance the signal. They won't prevent processing if omitted or invalid. Exception -
orderedItems.
Basic example
// Initialize the Connect library
if (window.TLT) {
TLT.initPremium({
appKey: "APP_KEY",
postUrl: "COLLECTOR_URL",
callback: function() {
const signal = {
// Required order-level fields
signalType: "order",
name: "order from website",
category: "Behavior",
orderId: "ORD-123456",
effect: "positive",
// Contact mapping and profile enrichment
audience: [
{
name: "Customer ID",
value: "AAUN-1417508"
},
{
name: "Location",
value: "10001"
}
],
// Optional order-level fields
currency: "USD",
orderSubtotal: 299.99,
orderTax: 24.00,
orderShippingHandling: 10.00,
orderDiscount: 30.00,
orderValue: 303.99,
// Ordered items array
orderedItems: [{
// Required item-level fields
productId: "SKU-12345",
productName: "Wireless Headphones",
itemQuantity: 1,
unitPrice: 299.99,
// Optional item-level fields
discount: 30.00,
productCategory: "Electronics"
}]
};
// Send the signal
TLT.logSignal(signal);
}
});
}
Complete implementation example
This example demonstrates collecting order data during checkout and sending the complete signal on the confirmation page. The audience identifier is captured from the login API response and stored in session storage alongside the order data. A newsletter sign-up checkbox on the confirmation page is used to update email consent.
Be cautious when updating consent
The
consentobject in the example below is used to update the opt-in status of a contact. When using this feature, make sure you involve all stakeholders to fully understand the ways a contact can opt out and how that status is maintained in the audience so that an opt-out status cannot be overwritten unintentionally, for example when audience data is batch loaded from another system.You should also ensure that your code accounts for corner cases such as the customer checking and then unchecking the consent checkbox before submitting the form.
// Utility function to write data to session storage
function store(key, value) {
const storeKey = "aco-store";
if (!sessionStorage) {
console.warn("Session storage unavailable");
return false;
}
let acoStore = sessionStorage.getItem(storeKey) || "{}";
acoStore = JSON.parse(acoStore);
// If a signal is being passed in
if (typeof key === "object" && key.signalType && value === undefined) {
// If a key for this signalType exists
if (acoStore[key.signalType]) {
acoStore[key.signalType].push(key);
} else {
acoStore[key.signalType] = [key];
}
}
if (key && value !== undefined) {
acoStore[key] = value;
}
sessionStorage.setItem(storeKey, JSON.stringify(acoStore));
return true;
}
// Utility function to read data from session storage
function retrieve(key, del) {
const storeKey = "aco-store";
if (!sessionStorage) {
console.warn("Session storage unavailable");
return false;
}
let acoStore = sessionStorage.getItem(storeKey) || "{}";
acoStore = JSON.parse(acoStore);
const value = acoStore[key] || "";
if (del && del === "delete") {
delete acoStore[key];
if (Object.keys(acoStore).length === 0) {
sessionStorage.removeItem(storeKey);
} else {
sessionStorage.setItem(storeKey, JSON.stringify(acoStore));
}
}
return value;
}
// Capture the customer ID from the login API response and store it
function monitorLoginApi(customerId) {
if (customerId) {
store("customerId", customerId);
}
}
// Initialize the Connect library
if (window.TLT) {
TLT.initPremium({
appKey: "APP_KEY",
postUrl: "COLLECTOR_URL",
callback: function() {
const signal = {
signalType: "order",
name: "order generated by website",
category: "Behavior",
orderId: "",
orderSubtotal: 0,
orderValue: 0,
currency: "USD",
orderShippingHandling: 0,
orderTax: 0,
orderDiscount: 0,
orderedItems: [],
effect: "positive"
};
const href = window.location.href;
// On the "Place Order" screen, before order confirmation
if (href.includes("/place-order")) {
const rows = document.querySelectorAll("tr.lineItem");
// Iterate over the table of products and populate orderedItems array
rows.forEach((row) => {
const orderedItem = {
productId: "",
productName: "",
productDescription: "",
itemQuantity: 0,
unitPrice: 0,
discount: 0,
productCategory: "",
productUrls: [],
imageUrls: [],
shoppingCartUrl: "https://" + window.location.host + "/shopping-cart",
promotionId: "",
virtualCategory: ""
};
orderedItem.productId = row.querySelector(".lineItem_sku")?.innerText || "";
orderedItem.productName = row.querySelector(".lineItem_name")?.innerText || "";
orderedItem.itemQuantity = Number(row.querySelector(".lineItem_qty")?.innerText) || 0;
const productUrl = row.querySelector(".lineItem_nameLink")?.href || "";
if (productUrl) {
orderedItem.productUrls.push(productUrl);
}
const imageUrl = row.querySelector(".lineItem_image > img")?.src.split("?")[0] || "";
if (imageUrl) {
orderedItem.imageUrls.push(imageUrl);
}
const discountedPrice = Number(row.querySelector(".price_sale")?.innerText) || 0;
const regularPrice = Number(row.querySelector(".price_default")?.innerText) || 0;
if (discountedPrice) {
orderedItem.unitPrice = discountedPrice;
orderedItem.discount = Math.round((regularPrice - discountedPrice) * 100) / 100;
} else {
orderedItem.unitPrice = regularPrice;
}
signal.orderedItems.push(orderedItem);
});
// Scrape the total prices from order summary
signal.orderSubtotal = Number(document.querySelector("div.orderSummary_subtotal")?.innerText) || 0;
signal.orderValue = Number(document.querySelector("div.orderSummary_total")?.innerText) || 0;
signal.orderShippingHandling = Number(document.querySelector("div.orderSummary_shipping")?.innerText) || 0;
// Store the signal until we have the order confirmation number
store(signal);
}
// On the "Order Confirmation" screen
if (href.includes("OrderConfirmation/?OrderNumber=")) {
const orderSignal = retrieve("order", "delete");
if (orderSignal) {
orderSignal.orderId = href.split("OrderConfirmation/?OrderNumber=")[1];
// Add contact mapping and profile enrichment
const customerId = retrieve("customerId");
const zipCode = document.querySelector(".shippingAddress_zip")?.innerText || "";
orderSignal.audience = [
{
name: "Customer ID",
value: customerId
},
{
name: "Location",
value: zipCode
}
];
// Add consent update if the newsletter sign-up checkbox is checked
const newsletterOptIn = document.querySelector("#newsletterSignUp")?.checked;
if (newsletterOptIn) {
orderSignal.consent = {
enableOverrideExistingOptOut: false,
email: [
{
status: "OPT_IN"
}
]
};
}
// If we have an orderId and ordered items, send the signal
if (orderSignal.orderId && orderSignal.orderedItems?.length) {
console.log("order signal:", JSON.stringify(orderSignal, undefined, 2));
TLT.logSignal(orderSignal);
}
}
}
}
});
}
Troubleshooting
Signal not capturing all products?
- Verify your selector matches all product rows in the checkout table
- Check that the DOM structure is consistent across all product types
- Ensure the loop properly iterates over all items
Missing order details on confirmation page?
- Confirm data is being stored to session storage on the checkout page
- Verify session storage persists between page loads (check browser settings)
- Test that the retrieve function is finding the stored data
Order ID not extracted correctly?
- Check the URL pattern on your confirmation page
- Verify your string splitting logic matches the actual URL format
- Consider using DOM selectors as alternatives
Signal sent multiple times?
- Ensure you're deleting the signal from storage after sending
- Check that page refresh doesn't re-trigger the signal logic
- Consider adding a flag to prevent duplicate sends
Related pages
Updated 3 days ago
