Enable product configuration signals in an iOS app

The product configuration signal captures user interactions with a product that suggest evaluation and preparation to purchase - for example, selecting a colorway in retail, choosing a seat on a ticket booking platform or choosing a loan term in a banking app. In Connect, the signal enables segmentation by configuration type, product and product category.

Availability: Premium and Ultimate

Languages: Swift and Objective-C


Implementation considerations

Relationship to the product view signal

The product view and product configuration signals work together:

  • productView - fires when the product detail screen loads
  • productConfiguration - fires each time the user interacts with a product element

Use the same productId and productName values in both signals to ensure consistent tracking.

When to fire the signal

Fire the signal each time a user interacts with a product element on the product detail screen. Common interactions in iOS apps:

  • Selecting a colorway or variant from a segmented control or collection
  • Adjusting quantity via a stepper
  • Tapping to view additional images
  • Expanding product details or reviews with an accordion

For interactions that fire rapidly in sequence - such as a quantity stepper - consider debouncing to avoid sending a signal on every tap. Use DispatchWorkItem with DispatchQueue.main.asyncAfter to cancel and reschedule on each tap.

Configuration type labels

Use the configurationType field to identify what the user interacted with. You can label by product parameter, UI element or action:

ApproachExamples
Product parameter"color", "size", "quantity", "room type"
UI element"Colorway segment", "Quantity stepper", "Image carousel"
Action"Select color", "Adjust quantity", "View image"

Choose a consistent approach across your implementation for better analytics.

Contact mapping

Include the audience field to attribute this signal to a contact in Connect. See How behavior signals update Connect data for identifier formats and contact resolution details.


Configuration

Before adding behavior signals, integrate the Connect SDK into your app. See the guide for your development language: Swift or Objective-C.

Method

func logSignal(_ data: [String: Any]?) -> Bool

Sends the signal to the Acoustic Connect endpoint. Initialize the Connect SDK before calling this method using ConnectSDK.shared.enable(with:).

Pass all fields as a flat [String: Any] dictionary. The SDK handles the signal structure automatically - you do not need to construct a nested object.

Signal fields

Required

FieldTypeDescription
productIdStringUnique product identifier. Use the same value as in the corresponding productView signal to keep the catalog consistent.
productNameStringName of the product
signalTypeStringSignal type. Value: "productConfiguration".

Optional

FieldTypeDescription
audience[String: Any]Key-value pairs for contact mapping. Keys must match contact attribute names exactly as they appear in Connect, including capitalization and spacing.
categoryStringSignal category. Value: "Behavior".
configurationTypeStringLabel for the interaction that occurred (for example, "color", "Select colorway")
currencyStringISO 4217 currency code (for example, "USD", "EUR"). Defaults to the currency set for your Connect subscription.
descriptionStringA description of the signal
discountNumberDiscount amount applied to the product
effectStringDescribes the effect of the signal on engagement. Use "positive" for product configuration signals.
imageUrls[String]URLs of product images
inventoryQuantityNumberNumber of units available
nameStringA label to differentiate this signal from others (for example, "colorway selection"). Max 256 characters
productCategoryStringProduct category from your catalog
productDescriptionStringDescription of the product
productUrls[String]URLs of product pages
promotionIdStringID from a marketing campaign that influenced this interaction
shoppingCartUrlStringURL or deep link to the shopping cart
unitPriceNumberUnit price of the product
virtualCategoryStringCategory based on navigation path (for example, "New arrivals", "Sale")

Things to know

  • Number fields accept native Swift numeric types (Int, Double, NSDecimalNumber).

  • Pass imageUrls and productUrls as [String] arrays.

  • If a required field is missing or invalid, the entire signal is discarded. For fields that don't apply to your business, use a placeholder value.

Basic example

import Connect

ConnectCustomEvent.sharedInstance().logSignal([
    "signalType": "productConfiguration",
    "productId": "SKU-12345",
    "productName": "Wireless Headphones",
    "configurationType": "color"
])
#import <Connect/Connect.h>

[[ConnectCustomEvent sharedInstance] logSignal:@{
    @"signalType": @"productConfiguration",
    @"productId": @"SKU-12345",
    @"productName": @"Wireless Headphones",
    @"configurationType": @"color"
}];

Complete example

This example fires a product configuration signal from a product detail view controller when the user selects a colorway or adjusts quantity. Stepper interactions are debounced to avoid sending a signal on every tap.

import Connect

// MARK: - Signal helper

private func sendProductConfigurationSignal(configurationType: String) {
    var signal: [String: Any] = [
        "signalType": "productConfiguration",
        "productId": product.id,
        "productName": product.name,
        "configurationType": configurationType,
        "unitPrice": product.price.doubleValue,
        "productCategory": product.categoryName,
        "currency": "USD",
        "effect": "positive"
    ]

    if UserSession.shared.isAuthenticated, let email = UserSession.shared.email {
        signal["audience"] = ["Email Address": email]
    }

    let accepted = ConnectCustomEvent.sharedInstance().logSignal(signal)
    NSLog("[Connect] productConfiguration signal accepted: \(accepted)")
}

// MARK: - Colorway selection

@objc private func colorwayChanged() {
    let index = colorwaySegment.selectedSegmentIndex
    guard product.colorways.indices.contains(index) else { return }
    selectedColorway = product.colorways[index]
    sendProductConfigurationSignal(configurationType: "color")
}

// MARK: - Quantity stepper (debounced)

private var stepperDebounce: DispatchWorkItem?

@objc private func stepperChanged() {
    quantityLabel.text = "\(Int(quantityStepper.value))"

    stepperDebounce?.cancel()
    let work = DispatchWorkItem { [weak self] in
        self?.sendProductConfigurationSignal(configurationType: "quantity")
    }
    stepperDebounce = work
    DispatchQueue.main.asyncAfter(deadline: .now() + 0.5, execute: work)
}
#import <Connect/Connect.h>

// Signal helper
- (void)sendProductConfigurationSignalWithConfigurationType:(NSString *)configurationType {
    NSMutableDictionary *signal = [@{
        @"signalType": @"productConfiguration",
        @"productId": self.product.id,
        @"productName": self.product.name,
        @"configurationType": configurationType,
        @"unitPrice": @(self.product.price.doubleValue),
        @"productCategory": self.product.categoryName,
        @"currency": @"USD",
        @"effect": @"positive"
    } mutableCopy];

    if ([[UserSession shared] isAuthenticated] && [[UserSession shared] email]) {
        signal[@"audience"] = @{ @"Email Address": [[UserSession shared] email] };
    }

    BOOL accepted = [[ConnectCustomEvent sharedInstance] logSignal:signal];
    NSLog(@"[Connect] productConfiguration signal accepted: %@", accepted ? @"true" : @"false");
}

// Colorway selection
- (void)colorwayChanged {
    NSInteger index = self.colorwaySegment.selectedSegmentIndex;
    if (index < self.product.colorways.count) {
        self.selectedColorway = self.product.colorways[index];
        [self sendProductConfigurationSignalWithConfigurationType:@"color"];
    }
}

// Quantity stepper (debounced)
- (void)stepperChanged {
    self.quantityLabel.text = [NSString stringWithFormat:@"%d", (int)self.quantityStepper.value];

    [NSObject cancelPreviousPerformRequestsWithTarget:self
              selector:@selector(fireStepperSignal)
              object:nil];
    [self performSelector:@selector(fireStepperSignal) withObject:nil afterDelay:0.5];
}

- (void)fireStepperSignal {
    [self sendProductConfigurationSignalWithConfigurationType:@"quantity"];
}

Verification

After triggering a product configuration interaction in your app, check the Xcode console for:

[Connect] productConfiguration signal accepted: true

The false value means the SDK rejected the call - see Troubleshooting below. Once the console confirms acceptance, verify the signal is available in Connect.


Troubleshooting

Signal not appearing in Connect?

  • Confirm the Connect SDK is initialized before any signal calls. See Method.
  • Capture the return value and check that it is true: let accepted = ConnectCustomEvent.sharedInstance().logSignal(signal). A false return indicates the SDK rejected the call.
  • Verify the appKey and postURL match the Connect org you're checking.

Signal marked as invalid in Connect?

  • Confirm all required fields are present: signalType, productId, productName.

Too many signals firing?

  • For interactions that repeat rapidly (such as quantity steppers), add debouncing using DispatchWorkItem as shown in the complete example.

Contact not created or updated?

  • Verify attribute key names in audience match exactly how they appear in Connect - including capitalization and spacing.

Related pages


Did this page help you?