---
title: Using App Events
description: >-
  Build an end-to-end custom event tracking flow to monitor merchant onboarding,
  feature usage, and errors in the Dev Dashboard.
source_url:
  html: 'https://shopify.dev/docs/apps/build/app-events/send-events'
  md: 'https://shopify.dev/docs/apps/build/app-events/send-events.md'
---

# Using App Events

With the [App Events API](https://shopify.dev/docs/api/app-events), you can send events from your app to Shopify. All events are shown in the [Dev Dashboard](https://shopify.dev/docs/apps/build/dev-dashboard/monitoring-and-logs), and events with handles that match a [Shopify App Pricing](https://shopify.dev/docs/apps/launch/billing/shopify-app-pricing) billing meter are automatically processed as billable usage. For a step-by-step guide to setting up billing events, see [Build a billing event](https://shopify.dev/docs/apps/launch/billing/shopify-app-pricing/subscription-billing/build-billing-event).

In this tutorial, you'll build an end-to-end tracking flow using **onboarding** as the example. The same pattern applies to any custom event — define your event handles, structure your attributes, send them through the API, and analyze the data in the Dev Dashboard.

***

## What you'll learn

In this tutorial, you'll learn how to do the following tasks:

* Authenticate with the App Events API
* Define event handles and design attribute structures for your use case
* Send custom events to the App Events API
* Include structured attribute data to track meaningful metrics
* Use the Dev Dashboard to monitor trends and troubleshoot issues

***

## Requirements

* A [Partner account](https://www.shopify.com/partners)
* A [development store](https://shopify.dev/docs/apps/tools/development-stores#create-a-development-store-to-test-your-app)
* An [app](https://shopify.dev/docs/apps/getting-started/create)
* An [API key](https://dev.shopify.com/dashboard/)

***

## Step 1: Authenticate with the App Events API

The App Events API uses JWT tokens for authentication. Generate a token using your app's client credentials from the Dev Dashboard.

Use your app's client ID and client secret to request an access token:

POST

## https://api.shopify.com/auth/access\_token

```javascript
const response = await fetch("https://api.shopify.com/auth/access_token", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    client_id: "{your_client_id}",
    client_secret: "{your_client_secret}",
    grant_type: "client_credentials",
  }),
});


const data = await response.json();
```

The response includes an `access_token`, the granted `scope`, and an `expires_in` value in seconds:

## {} Response

```json
{
  "access_token": "f8563253df0bf277ec9ac6f649fc3f17",
  "scope": "write_global_api_app_events",
  "expires_in": 3599
}
```

Store this token securely in your app. Tokens expire after 60 minutes — refresh them before they expire.

***

## Step 2: Plan your events

Before sending events, define what you want to track and how you'll structure the data. A well-planned event schema makes it easier to filter and analyze data in the Dev Dashboard.

### Define the event handles

Pick handles that clearly describe the event. For the onboarding example, you'll send two events that bookend the flow:

| Event handle | When to send | Purpose |
| - | - | - |
| `onboarding_started` | The merchant opens your app's setup flow for the first time | Track when merchants begin onboarding and which version they see |
| `onboarding_completed` | The merchant finishes all setup steps | Track completion rates, time-to-complete, and which steps merchants finish |

Together, these events let you calculate onboarding completion rates, identify where merchants drop off, and measure how long onboarding takes.

The same pattern works for other use cases. For example, if you're tracking errors, you might define a `server_error` handle that fires whenever your app returns a 500 to a merchant, with attribute fields like `error_code`, `endpoint`, and `stack_trace`. If you're tracking feature usage, you might define `report_generated` with fields like `report_type` and `row_count`.

### Design the attributes structure

Custom event attributes are flexible JSON objects. Include data that helps you analyze behavior and troubleshoot issues.

**Caution:**

Don't include any data that, alone or in combination with other data, could identify an individual. This includes any merchant or buyer information, such as name, email address, phone number, and other identifiable data points. Use anonymized identifiers and aggregated metrics instead.

Here's the attributes structure for the onboarding example:

**`onboarding_started` attributes:**

| Field | Type | Description |
| - | - | - |
| `onboarding_version` | integer | The version of your onboarding flow, so you can compare changes over time |
| `total_steps` | integer | The total number of steps in the onboarding flow |
| `source` | string | Where the merchant entered onboarding from (for example, `app_install`, `settings_page`, `welcome_link`) |

**`onboarding_completed` attributes:**

| Field | Type | Description |
| - | - | - |
| `onboarding_version` | integer | Matches the version from `onboarding_started` |
| `steps_completed` | integer | How many steps the merchant actually finished |
| `total_steps` | integer | The total number of steps available |
| `time_to_complete_seconds` | integer | Seconds between `onboarding_started` and `onboarding_completed` |

**Info:**

Use a consistent naming convention for your `event_handle` values. Sticking with `snake_case` makes them easier to filter and search in the Dev Dashboard.

***

## Step 3: Send the first event

When a merchant opens your onboarding flow for the first time, send an `onboarding_started` event to record it.

### Create the reporting function

```javascript
async function reportOnboardingStarted(shopId, accessToken) {
  const response = await fetch(
    "https://api.shopify.com/app/unstable/events",
    {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        Authorization: `Bearer ${accessToken}`,
      },
      body: JSON.stringify({
        shop_id: shopId,
        event_handle: "onboarding_started",
        timestamp: new Date().toISOString(),
        idempotency_key: `onboard_start_${shopId}_v3`,
        attributes: {
          onboarding_version: 3,
          total_steps: 5,
          source: "app_install",
        },
      }),
    }
  );


  if (!response.ok) {
    console.error(
      "Failed to report onboarding started:",
      response.status
    );
  }
}
```

### Integrate into your onboarding flow

Call the reporting function when the merchant first lands on your onboarding page. Use the `idempotency_key` to prevent duplicates if the merchant refreshes the page:

## app/routes/onboarding.jsx

```javascript
export const loader = async ({ request }) => {
  const { authenticate } = await import("../shopify.server");
  const { session } = await authenticate.admin(request);
  const shopId = session.shop_id;


  const accessToken = await getAppEventsToken();


  await reportOnboardingStarted(shopId, accessToken);


  return { shopId };
};
```

The idempotency key `onboard_start_{shopId}_v3` ensures that if the merchant visits the onboarding page multiple times, only the first visit is recorded. Update the version suffix when you change your onboarding flow.

***

## Step 4: Send a follow-up event

When a merchant finishes all the steps in your onboarding flow, send an `onboarding_completed` event with metrics about their experience. Pairing related events like this — a start and an end event — lets you measure conversion rates and time-to-complete for any flow.

### Create the reporting function

```javascript
async function reportOnboardingCompleted(
  shopId,
  accessToken,
  startTime,
  stepsCompleted
) {
  const timeToComplete = Math.round((Date.now() - startTime) / 1000);


  const response = await fetch(
    "https://api.shopify.com/app/unstable/events",
    {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        Authorization: `Bearer ${accessToken}`,
      },
      body: JSON.stringify({
        shop_id: shopId,
        event_handle: "onboarding_completed",
        timestamp: new Date().toISOString(),
        idempotency_key: `onboard_done_${shopId}_v3`,
        attributes: {
          onboarding_version: 3,
          steps_completed: stepsCompleted,
          total_steps: 5,
          time_to_complete_seconds: timeToComplete,
        },
      }),
    }
  );


  if (!response.ok) {
    console.error(
      "Failed to report onboarding completed:",
      response.status
    );
  }
}
```

### Integrate into your onboarding completion handler

Call the reporting function when the merchant completes the final step. Track the start time so you can calculate `time_to_complete_seconds`.

The code below reads `startTime` and `stepsCompleted` from the form submission. You'll need to store these values when the merchant begins onboarding — for example, save `startTime` (as a Unix timestamp from `Date.now()`) in a hidden form field or in the merchant's session when the onboarding page first loads, and increment `stepsCompleted` as the merchant progresses through each step.

## app/routes/onboarding.complete.jsx

```javascript
export const action = async ({ request }) => {
  const { authenticate } = await import("../shopify.server");
  const { session } = await authenticate.admin(request);
  const shopId = session.shop_id;


  const formData = await request.formData();
  const stepsCompleted = Number(formData.get("stepsCompleted"));
  const startTime = Number(formData.get("startTime"));


  const accessToken = await getAppEventsToken();


  await reportOnboardingCompleted(
    shopId,
    accessToken,
    startTime,
    stepsCompleted
  );


  return redirect("/app");
};
```

The `time_to_complete_seconds` field in the attributes lets you measure how long merchants take to finish onboarding. Compare this metric across onboarding versions to see whether your changes are making setup faster or slower.

***

## Step 5: Verify events in the Dev Dashboard

After sending both events, use the Dev Dashboard to confirm they're being recorded and inspect the data.

### Find your events

1. Go to the [Dev Dashboard](https://dev.shopify.com) and select your app.
2. Click **Logs** in the sidebar.
3. Set the **Type** filter to **App Event** to show only app events.

You should see your custom events in the list. In this example, you'd see both `onboarding_started` and `onboarding_completed`. Custom events are labeled as **Non-billable** since they don't match a billing meter.

### Inspect event details

Click any event to see its full details:

* **Event handle**: The handle you defined (for example, `onboarding_started`)
* **Shop**: The merchant's shop domain
* **Timestamp**: When the event occurred
* **Attributes**: The full JSON attributes with all the fields you included

Verify that the attribute fields contain the values you expect. If any field is missing or incorrect, check your app's reporting logic.

### Filter by event type

Use the search and filter options to focus on specific events:

* Filter by **Shop** to see a specific merchant's event history
* Search by event handle to find all instances of a specific event
* Compare timestamps between related events (like `onboarding_started` and `onboarding_completed`) for the same shop to verify calculated fields like `time_to_complete_seconds`

***

## Step 6: Analyze event trends

With events flowing into the Dev Dashboard, you can analyze behavior across your merchant base.

### Monitor event volume

Switch to the **Monitoring** tab to see aggregated event data. The event graph shows the volume of events over time, which helps you:

* **Track adoption**: See how many merchants trigger a given event each day or week
* **Measure conversion**: Compare the volume of paired events (like `onboarding_started` vs. `onboarding_completed`) to calculate completion rates
* **Spot issues**: A sudden spike in `server_error` events or a drop in `onboarding_completed` while starts remain steady might indicate a bug

### Spot-check attribute data

You can click into individual events in the Dev Dashboard to inspect their attribute fields. This is useful for debugging and verifying that your app is sending the right data for specific merchant journeys. For the onboarding example, you might check:

* Whether `steps_completed` is less than `total_steps`, indicating the merchant abandoned onboarding before finishing
* Whether a merchant has an `onboarding_started` event but no corresponding `onboarding_completed`
* Whether `time_to_complete_seconds` seems unusually high for a particular merchant, suggesting a confusing step

Since the Dev Dashboard shows attribute data on individual event records, this approach works best for spot-checking and investigating specific merchants rather than analyzing aggregate trends. For broader analysis across your merchant base, you'd need to export or process the event data externally — for example, by querying the API programmatically and aggregating the results in your own analytics system.

### Iterate using version fields

When you make changes to a flow, include a version field in your event attributes (like the `onboarding_version` in this example). This lets you compare metrics across versions in the Dev Dashboard:

* Filter by attribute values to compare version 3 against version 4
* Track whether your changes improved completion rates or reduced time-to-complete
* Identify regressions early by monitoring error patterns after a deploy

Learn more about [monitoring in the Dev Dashboard](https://shopify.dev/docs/apps/build/app-events#view-events-in-the-dev-dashboard).

***

## Next steps

* Apply this pattern to other use cases — track feature usage, errors, support interactions, or any event that's meaningful to your app.
* Set up [billing events](https://shopify.dev/docs/apps/launch/billing/shopify-app-pricing/subscription-billing/build-billing-event) to charge merchants based on usage.
* Review the [App Events API reference](https://shopify.dev/docs/api/app-events) for the complete API specification.
* [Monitor your events](https://shopify.dev/docs/apps/build/app-events#view-events-in-the-dev-dashboard) in the Dev Dashboard.

***
