Enable error signals in an Android 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 or logging tools.

Availability: Premium and Ultimate

Languages: Kotlin and Java


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 errors

When to fire the signal

Fire the signal immediately after the error is presented to the user. In Android apps, this is typically:

  • After form validation runs and an error message is shown
  • In the catch block of a network or API call 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

Use the audience field to map the signal to a contact in Connect. Commonly used identifiers are email address, phone number, and customer ID.

📘

Note

A customer ID mapped to the Contact key attribute in Connect is the most reliable identifier for known contacts. However, if no matching contact exists and the only attribute is a contact key, the signal is discarded — contact keys alone cannot create new contacts.

Sources of the identifier in an Android app:

  • In-memory session object (for example, UserSession.email)
  • SharedPreferences for persisted login state
  • Room database or other local store
  • Form fields collected during the current flow — for example, the email address entered on a checkout screen

The value must be a JSONObject where each key is a contact attribute name as it appears in Connect (including capitalization and spacing) and each value is the attribute value. For phone number attributes, use the E.164 format: +[country code][area code][phone number] — no spaces, dashes, or special characters. If the + symbol is omitted, Connect adds it automatically.

📘

Note

We recommend attaching identifiers to as many signals as possible. If the user is not authenticated, omit audience — Connect will attempt to identify the visitor using other signals from the same session.


Configuration

Method

Connect.logSignal(data: HashMap<String?, Any?>?): Boolean

Sends the signal to the Acoustic Connect endpoint. The Connect SDK must be initialized via Connect.enable() before calling this method.

Pass all fields as a flat HashMap. The SDK handles the signal structure automatically — you do not need to construct a nested object.

Signal fields

Required

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

Optional

FieldSchema typeDescription
audienceJSONObjectKey-value pairs for contact mapping. Must be a flat JSONObject — not a HashMap. Keys must match contact attribute names exactly as they appear in Connect.
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.
nameStringA label to differentiate this signal from others (for example, "checkout error"). Max 256 characters

Things to know

  • Pass audience as a JSONObject, not a HashMap. A HashMap value is silently dropped by the SDK serializer and the contact will not be mapped.

  • If a required field is missing or invalid, the entire signal is discarded.

Required import

import org.json.JSONObject

Basic example

val data = hashMapOf<String?, Any?>(
    "signalType" to "error",
    "errorType" to "USER",
    "errorText" to "Invalid promo code",
    "errorIdentifier" to "promoCode",
    "effect" to "negative"
)
Connect.logSignal(data)

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 focuses 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 org.json.JSONObject
import com.acoustic.connect.android.connectmod.Connect

private fun validate(): Boolean {
    var valid = true
    var firstError: View? = null

    val requiredFields = listOf(
        binding.nameField       to "name",
        binding.streetField     to "street",
        binding.cityField       to "city",
        binding.postalField     to "postalCode",
        binding.cardNumberField to "cardNumber",
        binding.expiryField     to "expiry",
        binding.cvcField        to "cvc"
    )
    for ((field, identifier) in requiredFields) {
        if (field.text.isNullOrBlank()) {
            val errorText = getString(R.string.checkout_validate_required)
            field.error = errorText
            sendErrorSignal(errorText, identifier)
            if (firstError == null) firstError = field
            valid = false
        }
    }

    if ((binding.cardNumberField.text?.toString().orEmpty()).length < 12) {
        val errorText = getString(R.string.checkout_validate_card)
        binding.cardNumberField.error = errorText
        sendErrorSignal(errorText, "payment")
        if (firstError == null) firstError = binding.cardNumberField
        valid = false
    }

    firstError?.requestFocus()
    return valid
}

private fun sendErrorSignal(errorText: String, errorIdentifier: String) {
    val data = hashMapOf<String?, Any?>(
        "signalType"      to "error",
        "errorType"       to "USER",
        "errorText"       to errorText,
        "errorIdentifier" to errorIdentifier,
        "effect"          to "negative"
    )

    // audience must be JSONObject — HashMap values are silently dropped
    UserSession.email?.let { email ->
        data["audience"] = JSONObject().put("Email Address", email)
    }

    Connect.logSignal(data)
}

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 org.json.JSONObject
import com.acoustic.connect.android.connectmod.Connect

private fun applyPromo(code: String) {
    val normalized = code.trim().uppercase()
    if (normalized.isEmpty()) return

    if (normalized == "HRN10") {
        // valid — apply discount
    } else {
        val errorText = getString(R.string.cart_promo_unknown)
        binding.totalsCard.promoStatusLabel.text = errorText
        sendErrorSignal(errorText, "promoCode")
    }
}

private fun sendErrorSignal(errorText: String, errorIdentifier: String) {
    val data = hashMapOf<String?, Any?>(
        "signalType"      to "error",
        "errorType"       to "USER",
        "errorText"       to errorText,
        "errorIdentifier" to errorIdentifier,
        "effect"          to "negative"
    )

    // audience must be JSONObject — HashMap values are silently dropped
    UserSession.email?.let { email ->
        data["audience"] = JSONObject().put("Email Address", email)
    }

    Connect.logSignal(data)
}

Troubleshooting

Signal not appearing in Connect?

  • Confirm Connect.enable() is called before logSignal.
  • Capture the return value and check that it is true: val accepted = Connect.logSignal(data). A false return indicates the SDK rejected the call — no additional configuration is required to use the return value.
  • Verify the appKey and postMessageUrl 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 will 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. If you want to suppress duplicates within a session, track which errors have already been reported using a local flag.

Contact not created or updated?

  • Confirm audience is a JSONObject, not a HashMap.
  • Verify attribute key names match exactly how they appear in Connect — including capitalization and spacing.

Related pages