--- title: Build a Billing Event description: >- Use Shopify App Pricing and the App Events API together to implement usage-based billing. Configure pricing plans, send billing events, and charge merchants based on SMS usage. source_url: html: >- https://shopify.dev/docs/apps/launch/billing/shopify-app-pricing/subscription-billing/build-billing-event md: >- https://shopify.dev/docs/apps/launch/billing/shopify-app-pricing/subscription-billing/build-billing-event.md --- # Build a Billing Event Usage-based billing for Shopify apps relies on two systems working together: * [**Shopify App Pricing**](https://shopify.dev/docs/apps/launch/billing/shopify-app-pricing): Defines your pricing plans, billing meters, and pricing structures in the Partner Dashboard. Shopify App Pricing handles plan selection, merchant approval, and automatic charge calculation. * [**App Events API**](https://shopify.dev/docs/api/app-events): Reports billing events from your app to Shopify. When an event's handle matches a billing meter defined in Shopify App Pricing, Shopify automatically counts it toward the merchant's usage charges. In this tutorial, you'll build an SMS messaging app that charges merchants per message sent. You'll use Shopify App Pricing to configure a plan with a usage meter, then use the App Events API to report each SMS as a billing event. Shopify handles everything in between — plan selection, charge approval, usage tracking, and invoicing. *** ## What you'll learn In this tutorial, you'll learn how to do the following tasks: * Use Shopify App Pricing to create a plan with usage-based billing meters * Configure a welcome link and redirect merchants to the plan selection page * Authenticate with the App Events API * Send billing events that match your Shopify App Pricing meters * Verify that Shopify App Pricing correctly processes your billing events in the Dev Dashboard *** ## 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) with [Shopify App Pricing](https://shopify.dev/docs/apps/launch/billing/shopify-app-pricing) enabled * An [API key](https://dev.shopify.com/dashboard/) with the `write_global_api_app_events` [scope](https://shopify.dev/docs/api/usage/access-scopes) *** ## Step 1: Configure usage-based pricing with Shopify App Pricing Before you can send billing events through the App Events API, you need to use Shopify App Pricing to create a pricing plan and define a billing meter. The meter's handle is what connects your API requests to the pricing configuration — when an event's `event_handle` matches a meter handle, Shopify knows to bill for it. ### Create a plan with usage-based pricing 1. From the [Partner Dashboard](https://partners.shopify.com), click **Apps > All Apps** and click your app's name. 2. Click **Distribution**. 3. Beside **Shopify App Store listing**, click **Manage listing**. 4. Under **Published languages**, click **Edit** for the locale you want to update. 5. Under **Pricing content**, click **Manage** to open the Pricing index page. 6. Under **Public plans**, click **Add** to open the plan editor. 7. Under **Billing**, select **Monthly** as the billing period. 8. Under **Monthly charge**, enter **9.99** as the base subscription fee. The base fee gives merchants access to your app. Usage-based charges for SMS messages are added on top of this fee. ### Add a usage meter for SMS Usage meters track specific types of usage in your app. Each meter has a handle that you'll reference when sending billing events through the API. 1. In your plan editor, click **Add usage meter**. 2. Configure the meter with the following values: | Field | Value | | - | - | | **Name** | SMS messages sent | | **Handle** | `sms_sent` | | **Pricing structure** | Fixed | | **Unit price** | $0.01 | | **Included units** | 100 | In this configuration, each merchant gets 100 free SMS messages per month. After that, each additional message costs $0.01. Shopify App Pricing automatically handles the calculation and adds the charges to the merchant's monthly bill. **Warning:** The `event_handle` you send through the App Events API must exactly match the meter handle you define in Shopify App Pricing. Handles are case-sensitive. A mismatched handle causes the event to be marked as non-billable in the Dev Dashboard. ### Configure the welcome link The welcome link is where merchants are redirected after they approve your plan charge. Use it to guide merchants into your app's onboarding experience. 1. In the plan editor, find the **Welcome link** field. 2. Enter a redirect path or URL: * **Apps with a landing page in [App Home](https://shopify.dev/docs/api/app-home)**: Specify a relative path to your app root, such as `/welcome`. Your `plan_handle` URL parameter is appended to all redirect URLs which is a plan that merchant is subscribed to. * **Apps without a landing page in App Home**: Redirect to a valid URL (including the `http` or `https` protocol). URL parameters for the `plan_handle` and the merchant shop domain are appended to redirect URLs. 3. Click **Save** to save the plan. **Info:** After a merchant is redirected to your welcome link, query the [Active Subscription API](https://shopify.dev/docs/api/partner/active-subscription) on the Partner API to verify the subscription status before granting access to paid features. ### Add plan descriptions Plans need a display name and feature list for each language your app listing supports. A plan only displays to merchants if it has a description for their language. 1. From the Partner Dashboard, navigate back to your app's **Pricing content**. 2. Find the plan you created. 3. Under **Display name**, enter a name like **SMS Pro**. 4. Under **Top features**, list the features included in the plan. For example: * Send SMS messages to customers * 100 free messages per month * $0.01 per additional message 5. Click **Save**. 6. Repeat for each published language in your app listing. *** ## Step 2: Redirect merchants to the Shopify App Pricing plan selection page Shopify App Pricing hosts the plan selection page for your app. When merchants install your app or need to select a plan, redirect them to this page. It displays the plans you configured in Step 1 and handles the approval flow. ### Build the redirect in your app Your app's plan selection page URL follows this pattern: ```text https://admin.shopify.com/store/:store_handle/charges/:app_handle/pricing_plans ``` In your app's root route loader, check whether the merchant has an active subscription using the [Active Subscription API](https://shopify.dev/docs/api/partner/active-subscription) on the Partner API. If not, redirect them to the plan selection page: ## app/routes/app.jsx ```javascript export const loader = async ({ request }) => { const appHandle = "YOUR_APP_HANDLE"; const { authenticate } = await import("../shopify.server"); const { admin, redirect, session } = await authenticate.admin(request); // Get the store handle from the session and the shop GID from the Admin API const storeHandle = session.shop.replace(".myshopify.com", ""); const shopResponse = await admin.graphql(`{ shop { id } }`); const { data: { shop: { id: shopId } } } = await shopResponse.json(); // Check subscription status via the Partner API const subscription = await fetchActiveSubscription(shopId); if (!subscription) { return redirect( `https://admin.shopify.com/store/${storeHandle}/charges/${appHandle}/pricing_plans`, { target: "_top" } ); } return { apiKey: process.env.SHOPIFY_API_KEY || "" }; }; async function fetchActiveSubscription(shopId) { // The Partner API uses a static access token created in the Partners Dashboard // under Settings > Partner API clients. Store it as SHOPIFY_PARTNER_API_ACCESS_TOKEN. const res = await fetch( `https://partners.shopify.com/${process.env.SHOPIFY_PARTNER_ORG_ID}/api/unstable/graphql.json`, { method: "POST", headers: { "Content-Type": "application/json", "X-Shopify-Access-Token": process.env.SHOPIFY_PARTNER_API_ACCESS_TOKEN, }, body: JSON.stringify({ query: `query ($appId: ID!, $shopId: ID!) { activeSubscription(appId: $appId, shopId: $shopId) { billingPeriod } }`, variables: { appId: process.env.SHOPIFY_APP_GID, shopId, }, }), } ); const { data } = await res.json(); return data?.activeSubscription ?? null; } ``` Replace `YOUR_APP_HANDLE` with the `app_handle` value from your `shopify.app.toml` file. The `target: "_top"` option is required for apps with a landing page in [App Home](https://shopify.dev/docs/api/app-home) because the plan selection page is outside the app's iframe. ### Test the plan approval flow 1. Install your app on your [development store](https://shopify.dev/docs/apps/tools/development-stores). 2. Confirm that your app redirects to the plan selection page. 3. Select the **SMS Pro** plan and approve the charge. 4. Verify that you're redirected to your welcome link with the `plan_handle` parameter. **Info:** On development stores, merchants aren't actually charged. You can test the full billing flow without any real payments. If you don't want to publish a public plan yet, Shopify App Pricing includes a [$0 private test plan](https://shopify.dev/docs/apps/launch/billing/shopify-app-pricing#test-plan) that you can use to configure and test your meters during development. *** ## Step 3: 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. When you create an API key in the Dev Dashboard, you automatically receive permissions to write to App Events. No additional configuration is required. Use your app's client ID and client secret to request an access token: POST ## /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. You'll include it as an `Authorization: Bearer` header on every App Events API request. Tokens expire after 60 minutes — refresh them before they expire. *** ## Step 4: Send a billing event through the App Events API After your app sends an SMS message for a merchant, use the App Events API to report the usage to Shopify. The `event_handle` must match the `sms_sent` meter handle you configured in Shopify App Pricing in [Step 1](#step-1-configure-usage-based-pricing-with-shopify-app-pricing). This is how the two systems connect — the App Events API reports the event, and Shopify App Pricing uses the matching meter to calculate the charge. To learn more about using App Events, see [About App Events](https://shopify.dev/docs/apps/build/app-events). ### Send a single SMS event ```javascript async function reportSmsSent(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: "sms_sent", timestamp: new Date().toISOString(), idempotency_key: `sms_${shopId}_${Date.now()}`, attributes: { value: 1, }, }), } ); if (!response.ok) { console.error("Failed to report SMS event:", response.status); } } ``` Report usage promptly to ensure accurate billing data. You can queue events in your app and send them sequentially to stay within [rate limits](https://shopify.dev/docs/api/app-events/unstable/creating-events#rate-limits). **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. ### Understand the request fields | Field | Value | Description | | - | - | - | | `shop_id` | `"gid://shopify/Shop/23423423"` | The Shopify shop ID of the merchant the SMS was sent for. Accepts a GID (shown) or a numeric shop ID as a string. | | `event_handle` | `sms_sent` | Must exactly match the billing meter handle from your Shopify App Pricing configuration. | | `timestamp` | `2026-01-27T14:30:00Z` | The ISO 8601 time when the SMS was actually sent, not when you reported it. Must fall within the merchant's current billing cycle and can't be more than 5 minutes in the future. | | `idempotency_key` | `sms_23423423_1706365800` | A unique key that prevents duplicate charges if you retry the request. Maximum 64 characters. Billing idempotency keys are enforced permanently. | | `attributes.value` | `1` | The number of units to add to the meter. Must be greater than 0. Use `1` for a single SMS. | ### Report batch usage If your app sends multiple messages in a campaign, you can report the total in a single event: ```javascript const response = await fetch("https://api.shopify.com/app/unstable/events", { method: "POST", headers: { "Content-Type": "application/json", Authorization: "Bearer {access_token}", }, body: JSON.stringify({ shop_id: "gid://shopify/Shop/23423423", event_handle: "sms_sent", timestamp: "2026-01-27T15:00:00Z", idempotency_key: "sms_campaign_abc123", attributes: { value: 5, }, }), }); const data = await response.json(); ``` Setting `value: 5` adds five units to the merchant's `sms_sent` meter for the billing period. **Info:** The App Events API always returns a `202` response when it receives your request, even if the event fails billing validation. There is no synchronous billing error response and no webhooks for billing validation failures. Always verify your events in the Dev Dashboard under **Logs** with the **App Billing Event** type filter. *** ## Step 5: Verify billing events in the Dev Dashboard After sending billing events through the App Events API, use the Dev Dashboard to confirm that Shopify App Pricing is processing them correctly. ### View your billing 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. 4. Look for your `sms_sent` events in the list. Each event displays: * **Status**: Whether the event was successfully processed (`200`) or failed (`4xx`, `5xx`). * **Event handle**: The meter handle from your request (`sms_sent`). * **Shop**: The merchant's shop domain. * **Timestamp**: When the event was sent. ### Confirm the event is billable In the logs, billing events are labeled to distinguish them from custom events: * **Billable**: The App Events API matched the event to the `sms_sent` billing meter in Shopify App Pricing. The usage counts toward the merchant's charges. * **Non-billable**: The event was received by the App Events API but didn't match any billing meter in Shopify App Pricing. If an event shows as non-billable, verify that the `event_handle` in your App Events API request exactly matches the meter handle in your Shopify App Pricing configuration. Handles are case-sensitive. ### Troubleshoot billing errors The following errors can appear in the Dev Dashboard for billing events: | Error | Cause | Solution | | - | - | - | | `NO_SUBSCRIPTION` | The merchant doesn't have an active Shopify App Pricing subscription | Confirm the merchant installed your app and approved the pricing plan | | `ACCOUNT_FROZEN` | The partner account is frozen and can't process billable events | Contact Shopify Partner Support to resolve your account status | | `SUBSCRIPTION_NOT_METERED` | The Shopify App Pricing subscription doesn't include usage-based pricing | Verify the merchant is on a plan that includes usage meters | | `INVALID_TIMESTAMP` | The timestamp is missing, malformed, more than 5 minutes in the future, or falls outside the merchant's current billing cycle | Use an ISO 8601 timestamp within the merchant's current billing cycle that isn't more than 5 minutes in the future | | `IDEMPOTENCY_KEY_ERROR` | The key is missing, already used, or invalid | Use a unique idempotency key for each distinct billable action | | `INVALID_VALUE` | The `value` is zero, negative, or non-numeric | Send a value greater than 0 for the `value` field | | `MISSING_VALUE_KEY` | The `value` key is missing from the attributes | Include `"value"` in the `attributes` object | | `PERIOD_CLOSED` | The billing period for this timestamp has already closed. This also occurs more than 24 hours after a merchant uninstalls your app | Send events within the current billing cycle. After a merchant uninstalls, you have 24 hours to submit remaining usage | | `INVALID_ACCOUNT` | The app or partner account isn't authorized | Verify your app credentials and partner account status | ### Monitor billing health Use the event graph in the Logs view to monitor billing event volume over time: * **Consistent volume**: Billing events should correlate with your app's SMS sending patterns. * **Error spikes**: Sudden increases in failed events might indicate a configuration issue or expired credentials. * **Missing events**: Gaps in expected billing events could mean your app isn't reporting usage correctly. Check the Dev Dashboard regularly, especially after deploying changes to your billing integration. *** ## Next steps * Explore [Shopify App Pricing](https://shopify.dev/docs/apps/launch/billing/shopify-app-pricing) for an overview of all pricing options available to your app. * Learn about [graduated and volume pricing structures](https://shopify.dev/docs/apps/launch/billing/shopify-app-pricing/subscription-billing/setup-usage-charges) in Shopify App Pricing for more advanced billing models. * [Combine subscriptions with usage](https://shopify.dev/docs/apps/launch/billing/shopify-app-pricing/subscription-billing/combined-subscription-and-usage) for hybrid pricing plans. * Set up [custom events](https://shopify.dev/docs/apps/build/app-events/send-events) to track non-billable merchant behavior through the App Events API. * 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. ***