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 loadsproductConfiguration- 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:
| Approach | Examples |
|---|---|
| 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]?) -> BoolSends 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.
- (BOOL)logSignal:(NSDictionary *)values;Sends the signal to the Acoustic Connect endpoint. Initialize the Connect SDK before calling this method using [[ConnectApplicationHelper sharedInstance] enableFramework:withPostMessageUrl:].
Pass all fields as an NSDictionary. The SDK handles the signal structure automatically - you do not need to construct a nested object.
Signal fields
Required
| Field | Type | Description |
|---|---|---|
productId | String | Unique product identifier. Use the same value as in the corresponding productView signal to keep the catalog consistent. |
productName | String | Name of the product |
signalType | String | Signal type. Value: "productConfiguration". |
Optional
| Field | Type | Description |
|---|---|---|
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. |
category | String | Signal category. Value: "Behavior". |
configurationType | String | Label for the interaction that occurred (for example, "color", "Select colorway") |
currency | String | ISO 4217 currency code (for example, "USD", "EUR"). Defaults to the currency set for your Connect subscription. |
description | String | A description of the signal |
discount | Number | Discount amount applied to the product |
effect | String | Describes the effect of the signal on engagement. Use "positive" for product configuration signals. |
imageUrls | [String] | URLs of product images |
inventoryQuantity | Number | Number of units available |
name | String | A label to differentiate this signal from others (for example, "colorway selection"). Max 256 characters |
productCategory | String | Product category from your catalog |
productDescription | String | Description of the product |
productUrls | [String] | URLs of product pages |
promotionId | String | ID from a marketing campaign that influenced this interaction |
shoppingCartUrl | String | URL or deep link to the shopping cart |
unitPrice | Number | Unit price of the product |
virtualCategory | String | Category based on navigation path (for example, "New arrivals", "Sale") |
Things to know
Number fields accept native Swift numeric types (
Int,Double,NSDecimalNumber).Pass
imageUrlsandproductUrlsas[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). Afalsereturn indicates the SDK rejected the call. - Verify the
appKeyandpostURLmatch 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
DispatchWorkItemas shown in the complete example.
Contact not created or updated?
- Verify attribute key names in
audiencematch exactly how they appear in Connect - including capitalization and spacing.
Related pages
Updated about 1 hour ago
