Enable error signals in an iOS app
The error signal captures when users encounter errors during critical flows in your app - for example, payment failures, invalid promo codes or form validation issues. It enables targeted outreach and recovery campaigns for users who hit obstacles on the path to conversion. In Connect, the signal enables segmentation by error type and error ID.
NoteThe error signal is for marketing segmentation, not error monitoring. Use it to identify users who experienced issues so your team can reach out with recovery messaging - not as a replacement for crash reporting tools.
Availability: Premium and Ultimate
Languages: Swift and Objective-C
Implementation considerations
Error types
Two error types are supported:
USER- caused by invalid user input: form validation failures, incorrect payment details, invalid promo codes, missing required fieldsAPPLICATION- generated by the app itself: network failures, server errors, API timeouts
When to fire the signal
Fire the signal immediately after the error is presented to the user. In iOS apps, this is typically:
- After form validation runs and an inline error or shake animation is shown
- In a
catchblock or failure closure when the error is surfaced in the UI - After receiving an error response from a payment or checkout service
Only fire the signal when the error is visible to the user - not for silent background failures.
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.
- (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.
Signal fields
Required
| Field | Type | Description |
|---|---|---|
errorText | String | The error message shown to the user |
errorType | String | Type of error. Value: "USER" or "APPLICATION". |
signalType | String | Signal type. Value: "error". |
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". |
description | String | A description of the signal |
effect | String | Describes the effect of the signal on engagement. Use "negative" for error signals. |
errorIdentifier | String | A short identifier for the error type (for example, "payment", "promoCode", "registration"). Used for segmentation by Error ID in Connect. |
name | String | A label to differentiate this signal from others (for example, "checkout error"). Max 256 characters |
Things to know
errorTypemust be exactly"USER"or"APPLICATION". Any other value causes the signal to be discarded.If a required field is missing or invalid, the entire signal is discarded.
Basic example
import Connect
ConnectCustomEvent.sharedInstance().logSignal([
"signalType": "error",
"errorType": "USER",
"errorText": "Invalid promo code",
"errorIdentifier": "promoCode",
"effect": "negative"
])#import <Connect/Connect.h>
[[ConnectCustomEvent sharedInstance] logSignal:@{
@"signalType": @"error",
@"errorType": @"USER",
@"errorText": @"Invalid promo code",
@"errorIdentifier": @"promoCode",
@"effect": @"negative"
}];Complete examples
Checkout form validation
This example fires an error signal when checkout form validation fails. It validates all fields in one pass, fires a signal for each invalid field and shakes the first one. If the user corrects the errors and places the order, the subsequent order signal in Connect captures the recovery - giving your team visibility into the full path from error to conversion. Users who experienced errors but did not complete the order are available for segmentation as a cart abandonment audience.
import Connect
private func sendErrorSignal(errorText: String, errorIdentifier: String) {
var signal: [String: Any] = [
"signalType": "error",
"errorType": "USER",
"errorText": errorText,
"errorIdentifier": errorIdentifier,
"effect": "negative"
]
if UserSession.shared.isAuthenticated, let email = UserSession.shared.email {
signal["audience"] = ["Email Address": email]
}
let accepted = ConnectCustomEvent.sharedInstance().logSignal(signal)
NSLog("[Connect] error signal accepted: \(accepted)")
}
private func validateForm() -> Bool {
let fields: [(UITextField, String)] = [
(nameField, "name"),
(streetField, "street"),
(cityField, "city"),
(postalField, "postalCode"),
(cardNumberField, "cardNumber"),
(cardExpiryField, "expiry"),
(cardCVCField, "cvc")
]
var firstInvalid: UITextField?
for (field, identifier) in fields where (field.text ?? "").isEmpty {
sendErrorSignal(errorText: "This field is required", errorIdentifier: identifier)
if firstInvalid == nil { firstInvalid = field }
}
if (cardNumberField.text ?? "").count < 12 {
sendErrorSignal(errorText: "Enter a valid card number", errorIdentifier: "payment")
if firstInvalid == nil { firstInvalid = cardNumberField }
}
if let field = firstInvalid {
shake(field)
return false
}
return true
}#import <Connect/Connect.h>
- (void)sendErrorSignalWithErrorText:(NSString *)errorText
errorIdentifier:(NSString *)errorIdentifier {
NSMutableDictionary *signal = [@{
@"signalType": @"error",
@"errorType": @"USER",
@"errorText": errorText,
@"errorIdentifier": errorIdentifier,
@"effect": @"negative"
} mutableCopy];
if ([[UserSession shared] isAuthenticated] && [[UserSession shared] email]) {
signal[@"audience"] = @{ @"Email Address": [[UserSession shared] email] };
}
BOOL accepted = [[ConnectCustomEvent sharedInstance] logSignal:signal];
NSLog(@"[Connect] error signal accepted: %@", accepted ? @"true" : @"false");
}
- (BOOL)validateForm {
NSArray *fields = @[
@[self.nameField, @"name"],
@[self.streetField, @"street"],
@[self.cityField, @"city"],
@[self.postalField, @"postalCode"],
@[self.cardNumberField, @"cardNumber"],
@[self.cardExpiryField, @"expiry"],
@[self.cardCVCField, @"cvc"]
];
UITextField *firstInvalid = nil;
for (NSArray *pair in fields) {
UITextField *field = pair[0];
NSString *identifier = pair[1];
if (field.text.length == 0) {
[self sendErrorSignalWithErrorText:@"This field is required"
errorIdentifier:identifier];
if (!firstInvalid) firstInvalid = field;
}
}
if (self.cardNumberField.text.length < 12) {
[self sendErrorSignalWithErrorText:@"Enter a valid card number"
errorIdentifier:@"payment"];
if (!firstInvalid) firstInvalid = self.cardNumberField;
}
if (firstInvalid) {
[self shake:firstInvalid];
return NO;
}
return YES;
}Invalid promo code
This example fires an error signal when a user enters an unrecognised promo code on the cart screen. It is a strong candidate for a recovery campaign - your team knows the user attempted a discount and can follow up with a working code if the user leaves without placing the order.
import Connect
private func applyPromo(_ code: String) {
let normalized = code.trimmingCharacters(in: .whitespaces).uppercased()
guard !normalized.isEmpty else { return }
if normalized == "HRN10" {
// valid - apply discount
} else {
let errorText = "We don't recognise that code."
totals.promoStatusLabel.text = errorText
totals.promoStatusLabel.textColor = Theme.accent
var signal: [String: Any] = [
"signalType": "error",
"errorType": "USER",
"errorText": errorText,
"errorIdentifier": "promoCode",
"effect": "negative"
]
if UserSession.shared.isAuthenticated, let email = UserSession.shared.email {
signal["audience"] = ["Email Address": email]
}
ConnectCustomEvent.sharedInstance().logSignal(signal)
}
}#import <Connect/Connect.h>
- (void)applyPromo:(NSString *)code {
NSString *normalized = [[code stringByTrimmingCharactersInSet:
NSCharacterSet.whitespaceCharacterSet] uppercaseString];
if (normalized.length == 0) return;
if ([normalized isEqualToString:@"HRN10"]) {
// valid - apply discount
} else {
NSString *errorText = @"We don't recognise that code.";
self.totals.promoStatusLabel.text = errorText;
self.totals.promoStatusLabel.textColor = Theme.accent;
NSMutableDictionary *signal = [@{
@"signalType": @"error",
@"errorType": @"USER",
@"errorText": errorText,
@"errorIdentifier": @"promoCode",
@"effect": @"negative"
} mutableCopy];
if ([[UserSession shared] isAuthenticated] && [[UserSession shared] email]) {
signal[@"audience"] = @{ @"Email Address": [[UserSession shared] email] };
}
[[ConnectCustomEvent sharedInstance] logSignal:signal];
}
}Verification
After submitting a form with a required field empty or an invalid value, check the Xcode console for:
[Connect] error 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,errorType,errorText. - Verify
errorTypeis exactly"USER"or"APPLICATION"- other values cause the signal to be discarded.
Duplicate signals on retry?
- If the user retries and triggers the same validation error, a new signal fires. This is expected behavior. To suppress duplicates within a session, track which errors have already been reported using a local flag or
Set<String>.
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
