Tracking ObservabilityInspector Snowplow SDK integration

Send data from Snowplow to Avo Inspector

If you are using Snowplow SDKs to track events in your application, you can connect Avo Inspector to monitor and validate your tracking implementation. This integration requires adding a small amount of code to your existing Snowplow setup.

After connecting Inspector to your Snowplow implementation, all your Snowplow self describing events will be automatically analyzed by Avo Inspector. You’ll be able to monitor the quality of your tracking implementation and catch schema issues before they impact your data pipeline.

💡

Inspector only receives your event schema, no actual data will be sent to Avo.

ℹ️

Remember that production data takes up to 2 hours to appear in Inspector dashboard, while development data is available without any delay.

Guide to connect Snowplow and Avo Inspector

Step 1. Set up your Avo source

An Avo Source is the first thing you need to connect your Snowplow data to Avo Inspector.

Open Sources in the Avo sidebar or Manage Sources in Inspector tab

Navigation to Inspector sources screen in Avo

If you already have the source, open it in the dashboard. If you don’t have the source corresponding to your app, add a new one.

In the source details, go to “Inspector Setup” and there you’ll see options to integrate Inspector. Copy the API key, you will need to use it in your Snowplow integration.

Copy Inspector API key

Step 2. Install and initialize the Avo Inspector SDK

Before integrating with Snowplow, you’ll need to install the Avo Inspector SDK for your platform:

Step 3. Integrate Avo Inspector with your Snowplow implementation

The best integration approach depends on how your tracking implementation is organized. We’ll cover both scenarios to help you choose the right method.

Web Integration

Recommended: Add Inspector to your analytics wrapper

If you have an analytics wrapper or service that centralizes your tracking calls (where trackSelfDescribingEvent is only called from one place), this is the cleanest integration approach. Simply add Avo Inspector to your existing wrapper function.

import { trackSelfDescribingEvent } from '@snowplow/browser-tracker';
import * as Inspector from "avo-inspector";
 
// Initialize Avo Inspector with your API key
const inspector = new Inspector.AvoInspector({
  apiKey: 'YOUR_API_KEY',
  env: 'dev', // Use "staging" or "prod" for other environments
  version: '1.2.3', // Your app version
});
 
function trackEvent(event, context) {
  // Extract event name from schema URI 
  // (e.g., "iglu:com.example/button_clicked/jsonschema/1-0-0" -> "button_clicked")
  // This is a common approach, but it may need to be adjusted based on how you structure your
  // Snowplow schemas and how you've organized your events in your Avo tracking plan.
  // Ensure the extracted event name matches the event names defined in Avo.
  const eventName = event.schema.split('/')[1] || event.schema;
 
  // If you have context objects (e.g., Snowplow contexts) you've documented as
  // event properties in Avo, here you'd want to add them to the eventProperties object.
  // For example, if you have a userContext object with the schema "iglu:com.example/user/jsonschema/1-0-0",
  // you'd want to add the user to the eventProperties object.
  // eventProperties['user'] = userContext.data;
  const eventProperties = event.data;
 
  // Send to Avo Inspector
  inspector.trackSchemaFromEvent(eventName, eventProperties);
 
  // Send to Snowplow
  trackSelfDescribingEvent({ event, context });
}
 
// Usage throughout your app
trackEvent(
  {
    schema: 'iglu:com.example/button_clicked/jsonschema/1-0-0',
    data: {
      button_name: 'sign_up',
      page: 'homepage',
    },
  },
  [],
);

Alternative: Create a wrapper function for decentralized tracking

If your codebase has tracking calls scattered throughout (multiple places calling trackSelfDescribingEvent directly), create a wrapper function that you can use instead.

⚠️

This approach requires you to replace all existing trackSelfDescribingEvent calls with your wrapper function throughout your codebase.

import { trackSelfDescribingEvent } from '@snowplow/browser-tracker';
import * as Inspector from "avo-inspector";
 
const inspector = new Inspector.AvoInspector({
  apiKey: 'YOUR_API_KEY',
  env: 'dev',
  version: '1.2.3', // Your app version
});
 
// Create a wrapper function to intercept all tracking calls
function trackingWrapper(args) {
  // Extract event name from schema URI
  const eventName = args.event.schema.split('/')[1] || args.event.schema;
  const eventProperties = args.event.data;
 
  // Send to Avo Inspector
  inspector.trackSchemaFromEvent(eventName, eventProperties);
  
  // Send to Snowplow
  return trackSelfDescribingEvent(args);
}
 
// Use trackingWrapper instead of trackSelfDescribingEvent throughout your app
trackingWrapper({
  event: {
    schema: 'iglu:com.example/button_clicked/jsonschema/1-0-0',
    data: { button_name: 'sign_up', page: 'homepage' }
  },
  context: []
});

Mobile Integration

Recommended: Add Inspector to your analytics wrapper

Most mobile apps have an analytics service or wrapper class that centralizes tracking calls. If you’re calling Snowplow’s tracking methods from a single location, add Avo Inspector there.

iOS (Swift)

class AnalyticsService {
    func trackEvent(event: SelfDescribing, context: [Context]) {
        // Extract event name from schema URI
        let eventName = event.schema.components(separatedBy: "/").count > 1 ? event.schema.components(separatedBy: "/")[1] : event.schema
 
        // If you have context objects (e.g., Snowplow contexts) you've documented as
        // event properties in Avo, here you'd want to add them to the eventParams object.
        // For example, if you have a userContext object with the schema "iglu:com.example/user/jsonschema/1-0-0",
        // you'd want to add the user to the eventParams object.
        // eventParams["user"] = userContext.data
        var eventParams = event.data
 
        // Send to Avo Inspector
        avoInspector.trackSchema(fromEvent: eventName, eventParams: eventParams)
 
        // Send to Snowplow
        let event = SelfDescribing(schema: schema, payload: data)
        tracker.track(event)
    }
}
 
// Usage throughout your app
let event = SelfDescribing(
    schema: "iglu:com.example/button_clicked/jsonschema/1-0-0", 
    payload: ["button_name": "sign_up", "page": "homepage"]
)
analyticsService.trackEvent(event: event, context: [])

Android (Kotlin)

class AnalyticsService {
    fun trackEvent(event: SelfDescribing, context: List<Context>) {
        // Extract event name from schema URI
        val eventName = event.schema.split("/").getOrElse(1) { event.schema }
        val eventProperties = event.data
 
        // Send to Avo Inspector
        avoInspector.trackSchemaFromEvent(eventName, eventProperties)
 
        // Send to Snowplow
        val event = SelfDescribing(schema, data)
        tracker.track(event)
    }
}
 
// Usage throughout your app
val event = SelfDescribing(
    "iglu:com.example/button_clicked/jsonschema/1-0-0",
    mapOf("button_name" to "sign_up", "page" to "homepage")
)
analyticsService.trackEvent(event)

React Native

React Native uses the same approach as web since the Snowplow React Native tracker is implemented in JavaScript/TypeScript. The Avo Inspector React Native SDK provides the same API as the web version.

import { newTracker } from '@snowplow/react-native-tracker';
import * as Inspector from 'avo-inspector';
 
// Initialize Avo Inspector
const inspector = new Inspector.AvoInspector({
  apiKey: 'YOUR_API_KEY',
  env: 'dev',
  version: '1.2.3',
});
 
// Initialize Snowplow tracker
const tracker = newTracker({
  namespace: 'appTracker',
  endpoint: 'https://YOUR_COLLECTOR',
});
 
class AnalyticsService {
  trackEvent(event, context) {
    // Extract event name from schema URI
    const eventName = event.schema.split('/')[1] || event.schema;
 
    // Send to Avo Inspector
    inspector.trackSchemaFromEvent(eventName, event.data);
 
    // Send to Snowplow
    tracker.trackSelfDescribingEvent(event, context);
  }
}
 
// Usage throughout your app
analyticsService.trackEvent(
  'iglu:com.example/button_clicked/jsonschema/1-0-0',
  { button_name: 'sign_up', page: 'homepage' }
);
ℹ️

Event name extraction: The above examples extract the event name by splitting the Snowplow schema URI on / and taking the second part. This approach may need to be adjusted based on how you structure your Snowplow schemas and how you’ve organized your events in your Avo tracking plan. Ensure the extracted event name matches the event names defined in Avo.

Alternative: Use SDK plugins for decentralized tracking

If your mobile app has tracking calls scattered throughout the codebase, use Snowplow’s plugin system to automatically capture all events. Snowplow mobile SDKs (v5+) provide a plugin architecture with several extension points, including an afterTrack callback that executes after an event has been processed but before it’s sent to the collector.

The afterTrack callback is perfect for Avo Inspector integration because it provides access to the complete event object, including all context entities, without interfering with your existing tracking implementation. Learn more about Snowplow tracker plugins.

iOS Plugin Approach

let plugin = PluginConfiguration(identifier: "avoInspectorPlugin")
 
plugin.afterTrack { event in
  // Extract event name from schema URI
  let eventName = event.schema.components(separatedBy: "/").count > 1 ? 
    event.schema.components(separatedBy: "/")[1] : event.schema
  
  // Send to Avo Inspector
  avoInspector.trackSchemaFromEvent(eventName: eventName, eventParams: event.payload)
}
 
let tracker = Snowplow.createTracker(
  namespace: "namespace",
  network: networkConfig,
  configurations: [plugin]
)

Android Plugin Approach

val plugin = PluginConfiguration("myPlugin")
 
plugin.afterTrack {
  // Extract event name from schema URI
  val eventName = it.schema.split("/").getOrElse(1) { it.schema }
  val params = mutableMapOf<String, Any?>()
 
  // Combine main event payload with context entities
  params.putAll(it.payload)
  it.entities.forEach { ctx ->
    params[ctx.schema] = ctx.data
  }
 
  avoInspector.trackSchemaFromEvent(eventName, params)
}
 
val tracker = Snowplow.createTracker(
  applicationContext,
  "namespace",
  networkConfig,
  plugin
)

Step 4. Test your integration

After implementing the integration, test that events are being sent to Avo Inspector by triggering some events in your application.

Navigate to Avo, pick Inspector in the sidebar and pick Development environment

Environment picker in Inspector

You’ll see your Snowplow events in the dashboard under the source you configured. This means that your Snowplow integration is working!

Next Steps