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.

⚠️

Note

The 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 fields
  • APPLICATION - 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 catch block 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]?) -> 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.

Signal fields

Required

FieldTypeDescription
errorTextStringThe error message shown to the user
errorTypeStringType of error. Value: "USER" or "APPLICATION".
signalTypeStringSignal type. Value: "error".

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".
descriptionStringA description of the signal
effectStringDescribes the effect of the signal on engagement. Use "negative" for error signals.
errorIdentifierStringA short identifier for the error type (for example, "payment", "promoCode", "registration"). Used for segmentation by Error ID in Connect.
nameStringA label to differentiate this signal from others (for example, "checkout error"). Max 256 characters

Things to know

  • errorType must 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). 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, errorType, errorText.
  • Verify errorType is 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 audience match exactly how they appear in Connect - including capitalization and spacing.

Related pages


Did this page help you?