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:

  1. Checkout/cart page - Collect product details, quantities, prices.
  2. Order placement - Store collected data in session storage.
  3. 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, sms or whatsapp.
  • description: String - Description of the signal
  • effect: 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 sending positive for 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 - When true, current opt-out statuses will be overridden by new statuses. When false, 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 order
  • orderId (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 a quantity of 3. ⚠️ If orderedItems fails validation, the entire signal will be discarded.
  • orderShippingHandling: Number - Shipping and handling cost
  • orderSubtotal: Number - Subtotal amount (net of discount)
  • orderTax: Number - Tax amount
  • orderValue: Number - Total order value (including tax and shipping)
  • signalCustomAttributes: Array of objects - Additional custom attributes. Each object has name and value string fields.

Ordered items object

  • discount: Number - Discount amount per unit
  • imageUrls: Array of strings - URLs of product images
  • itemQuantity: Number - Quantity of this product purchased
  • productCategory: String - Product category from 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 - URLs of product pages
  • promotionId: String - ID from marketing campaigns that influenced the purchase
  • shoppingCartUrl: String - URL of the shopping cart
  • unitPrice (required): Number - Price the customer paid for one unit
  • virtualCategory: 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 consent object 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

How behavior signals are processed in Connect