Skip to main content

Using App Events

With the App Events API, you can send events from your app to Shopify. All events are shown in the Dev Dashboard, and events with handles that match a 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.

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.


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


Anchor to Step 1: Authenticate with the App Events APIStep 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

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

{
"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.


Anchor to Step 2: Plan your eventsStep 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.

Anchor to Define the event handlesDefine the event handles

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

Event handleWhen to sendPurpose
onboarding_startedThe merchant opens your app's setup flow for the first timeTrack when merchants begin onboarding and which version they see
onboarding_completedThe merchant finishes all setup stepsTrack 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.

Anchor to Design the attributes structureDesign 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:

FieldTypeDescription
onboarding_versionintegerThe version of your onboarding flow, so you can compare changes over time
total_stepsintegerThe total number of steps in the onboarding flow
sourcestringWhere the merchant entered onboarding from (for example, app_install, settings_page, welcome_link)

onboarding_completed attributes:

FieldTypeDescription
onboarding_versionintegerMatches the version from onboarding_started
steps_completedintegerHow many steps the merchant actually finished
total_stepsintegerThe total number of steps available
time_to_complete_secondsintegerSeconds 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.


Anchor to Step 3: Send the first eventStep 3: Send the first event

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

Anchor to Create the reporting functionCreate the reporting function

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
);
}
}

Anchor to Integrate into your onboarding flowIntegrate 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

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.


Anchor to Step 4: Send a follow-up eventStep 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.

Anchor to Create the reporting functionCreate the reporting function

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
);
}
}

Anchor to Integrate into your onboarding completion handlerIntegrate 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

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.


Anchor to Step 5: Verify events in the Dev DashboardStep 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.

  1. Go to the Dev Dashboard 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.

Anchor to Inspect event detailsInspect 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.

Anchor to Filter by event typeFilter 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

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

Anchor to Monitor event volumeMonitor 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

Anchor to Spot-check attribute dataSpot-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.

Anchor to Iterate using version fieldsIterate 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.


  • 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 to charge merchants based on usage.
  • Review the App Events API reference for the complete API specification.
  • Monitor your events in the Dev Dashboard.

Was this page helpful?