Verify Android-Kotlin SDK events

An event moves through four stages between your track() call and Amplitude's servers. Verify each stage in order. The first stage where the event looks wrong is where the problem is.

For problems common to every Amplitude SDK, go to SDK troubleshooting and debugging. If behavior changed after an SDK upgrade, check the Android-Kotlin SDK release notes.

Turn on debug logging

The SDK logs to Logcat with the tag Amplitude. The default level is INFO. Set it to DEBUG while you verify:

import com.amplitude.common.Logger

amplitude.logger.logMode = Logger.LogMode.DEBUG

The levels are DEBUG, INFO, WARN, ERROR, and OFF. In Android Studio, open Logcat and filter on tag:Amplitude. Return the level to INFO or higher before you ship.

Send logs to your own logger

To route SDK logs somewhere other than Logcat, such as your crash reporter, implement LoggerProvider and set loggerProvider:

kotlin
import com.amplitude.common.Logger
import com.amplitude.core.LoggerProvider

class MyLogger : Logger {
    override var logMode: Logger.LogMode = Logger.LogMode.WARN
    override fun debug(message: String) { /* ... */ }
    override fun info(message: String) { /* ... */ }
    override fun warn(message: String) { /* ... */ }
    override fun error(message: String) { /* ... */ }
}

val amplitude = Amplitude("YOUR_API_KEY", applicationContext) {
    loggerProvider = object : LoggerProvider {
        override fun getLogger(amplitude: com.amplitude.core.Amplitude): Logger = MyLogger()
    }
}

Check what the SDK captured

With debug logging on, the SDK logs one line for each event it accepts:

text
D/Amplitude: Logged event with type: Sign Up Clicked

If that line doesn't appear, look for one of these instead:

At this stage you can also read the state the SDK attaches to every event:

kotlin
amplitude.isBuilt.await() // inside a coroutine
amplitude.getDeviceId() // "3f6c1e2a-...R"
amplitude.getUserId() // "user-12345" or null
amplitude.sessionId // 1761480000000, or -1 before the first session
amplitude.optOut // false

Check how plugins transformed the event

After capture, each event passes through the plugin pipeline in a fixed order:

  1. Before plugins (Plugin.Type.Before): The built-in context plugin adds device_id, user_id, session_id, time, insert_id, library, platform, device and OS fields, and ip, subject to trackingOptions.
  2. Enrichment plugins (Plugin.Type.Enrichment): Your own enrichment plugins run here, in the order you add them.
  3. Destination plugins (Plugin.Type.Destination): The built-in Amplitude destination writes the event to the upload queue.

A before or enrichment plugin that returns null drops the event. To see the exact event the SDK sends, add a destination plugin that records each event:

kotlin
import com.amplitude.core.events.BaseEvent
import com.amplitude.core.platform.DestinationPlugin

class InspectEvents : DestinationPlugin() {
    override fun track(payload: BaseEvent): BaseEvent {
        amplitude.logger.debug("Final event: ${payload.eventType} ${payload.eventProperties} device=${payload.deviceId} session=${payload.sessionId}")
        return payload
    }
}

val inspector = InspectEvents()
amplitude.add(inspector)

Destination plugins receive the event after every before and enrichment plugin has run. Identify events arrive in identify(), and revenue events in revenue(). Remove the plugin with amplitude.remove(inspector) when you're done.

Check what the SDK sent

The SDK uploads events with a POST request to the endpoint for your configuration:

The SDK uploads when 30 events (flushQueueSize) are waiting, 30 seconds (flushIntervalMillis) after an event is queued, when the app goes to the background, and when the device reconnects. To send right away, call amplitude.flush().

The request body has this shape:

json
{
  "api_key": "YOUR_API_KEY",
  "client_upload_time": "2026-10-26T12:00:12.400Z",
  "events": [
    {
      "event_type": "Sign Up Clicked",
      "event_properties": { "plan": "starter" },
      "device_id": "3f6c1e2a-...R",
      "session_id": 1761480000000,
      "time": 1761480012345,
      "insert_id": "9c0e...",
      "platform": "Android",
      "os_name": "android",
      "ip": "$remote",
      "library": "amplitude-analytics-android/1.37.0"
    }
  ]
}

"ip": "$remote" tells Amplitude to use the request's IP address. To omit it, call trackingOptions.disableIpAddress().

Compressed request bodies

For Amplitude's own endpoints, the SDK gzip-compresses every request body and sends Content-Encoding: gzip. For a custom serverUrl, it sends plain JSON unless you set enableRequestBodyCompression = true. Decompress the body before you read it in a proxy.

The debug log doesn't print request bodies. To inspect request bodies, use one of these methods:

  • A local proxy: Set serverUrl to a proxy you control, such as one running on your development machine, and read the body there. The proxy receives plain JSON unless you turn on compression.
  • A custom HTTP client: Set httpClient to an HttpClientInterface that logs the events string before it uploads. Go to Use a custom HTTP client.

If no request appears:

Check what Amplitude received

Read the upload result

Pass a callback to track() to get the result for one event, or set callback in the configuration to get every result:

kotlin
import com.amplitude.core.events.BaseEvent

amplitude.track(BaseEvent().apply { eventType = "Sign Up Clicked" }) { event, status, message ->
    Log.d("MyApp", "${event.eventType}: $status $message")
}

The SDK calls the callback only when it finishes with an event, either because Amplitude accepted it or because the SDK dropped it. For errors that the SDK retries, it keeps the event and doesn't call the callback until a later attempt finishes.

A successful response body looks like this:

json
{
  "code": 200,
  "events_ingested": 1,
  "payload_size_bytes": 412,
  "server_upload_time": 1761480012612
}

A 200 response means Amplitude's ingestion API accepted the event. Amplitude can still exclude an accepted event from analysis later, for example when a block filter matches it.

Find the event in Amplitude

  1. Open User Lookup and search for the event's user_id, or its device_id if the user is anonymous.
  2. Select the Activity tab and turn on Live event updates.
  3. Open the event and compare its properties with what your inspection plugin logged.

For events from many users, use the Ingestion Debugger to review accepted and rejected requests for the project. Charts can take a few minutes longer to include new events than User Lookup does.

Was this helpful?

For AI agents: a documentation index is available at /docs/llms.txt. Append .md to any page URL for markdown, or send Accept: text/markdown.

On this page