Developers · API v1.0.0

Integration for Shopify Store Apps

This guide is for teams that run a mobile app for their Shopify store. The app may be built with Expo, React Native, Flutter or natively, using the Storefront API, the Customer Account API or Checkout Kit. For the general API reference, see the Overview.

On top of the platform setup, this guide covers the Shopify-specific steps: matching customers to devices, e-commerce attributes, backend (webhook) updates and campaign setups.

Supported versions: All supported versions of the Shopify Storefront API and Customer Account API (the examples are compatible with 2025-01 and later) · For the mobile side, see the versions listed in the relevant platform guide.

If your app was built with a no-code app builder such as Tapcart, Shopney or Plobal, you need to be able to add custom code to the app to integrate NotiPilot. Check with your provider whether their platform supports custom code or SDKs.

1. Platform integration first

Set up device registration based on the technology your mobile app uses:

App technology Guide
Expo expo.md
React Native react-native.md
Flutter flutter.md
Ionic / Capacitor ionic.md
Android (Kotlin / Java) kotlin.md · android.md
iOS (Swift / Objective-C) swift.md · ios.md

Once this step is complete, every device should be registered in NotiPilot and receiving "All users" sends.

2. Matching customers to devices

When a customer signs in to the app, call identify-device. Use the Shopify customer ID with a prefix as the external_id:

gid://shopify/Customer/7234567890123  →  external_id: "shopify:7234567890123"

The prefix (shopify:) prevents collisions with user IDs that may come from other sources in the future.

Fetching customer data with the Storefront API

GraphQL
query Customer($token: String!) {
  customer(customerAccessToken: $token) {
    id
    acceptsMarketing
    defaultAddress { city countryCodeV2 }
  }
}

If you use the new Customer Account API: customer { id defaultAddress { city territoryCode } emailAddress { marketingState } }

Calling identify-device (TypeScript example)

TypeScript
async function identifyShopifyCustomer(customer: {
  id: string;
  acceptsMarketing: boolean;
  defaultAddress?: { city?: string | null; countryCodeV2?: string | null } | null;
}) {
  const numericId = customer.id.split('/').pop(); // "gid://shopify/Customer/123" → "123"

  await fetch('https://app.notipilot.com/api/v1/identify-device', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      app_id: 'YOUR-EXPO-PROJECT-ID',
      device_uid: await getDeviceUid(),          // function from the platform guide
      external_id: `shopify:${numericId}`,
      consent_marketing: customer.acceptsMarketing,
      attributes: {
        customer_type: 'registered',
        country: customer.defaultAddress?.countryCodeV2 ?? null, // "TR"
        city: customer.defaultAddress?.city ?? null,             // "Istanbul"
      },
    }),
  });
}

When the customer signs out, send the register-device request again with "external_id": null and "attributes": { "customer_type": "guest" }.

Important for location segments: The city and countryCodeV2 from the customer's default shipping address are the most reliable source for location segments in NotiPilot. For signed-out users, at least send country based on the device region.

The NotiPilot dashboard automatically creates a segment for every attribute value. That's why you should use fields with a limited number of distinct values and group numbers into ranges.

Attribute Example values Source
customer_type guest, registered, vip App / customer tags
country TR, DE defaultAddress.countryCodeV2
city Istanbul, Izmir defaultAddress.city
locale tr, en Device language
orders_bucket 0, 1, 2-5, 6+ Backend (webhook)
spend_bucket 0-500, 500-2000, 2000+ Backend (webhook)
cart_status empty, active, abandoned App / backend
last_order_month 2026-09 Backend (webhook)

❌ Don't send values like orders_count: 17, total_spent: 1234.56, email addresses or phone numbers: they either create lots of meaningless segments or are personal data.

You can use the tags field for short labels derived from customer tags (e.g. ["vip", "wholesale"]). When tags is sent, it replaces the existing list.

4. Updating from your backend (Shopify webhooks)

The NotiPilot API is a plain HTTP API, so besides the mobile app you can also call it from your own backend. To do this, store the device_uid alongside the customer in your backend when they sign in. A customer can have more than one device.

Example: when an orders/create webhook arrives, update all of the customer's devices (Node.js):

JavaScript
app.post('/webhooks/orders-create', verifyShopifyHmac, async (req, res) => {
  const order = req.body;
  const customerId = order.customer?.id;
  if (!customerId) return res.sendStatus(200);

  const ordersCount = order.customer.orders_count ?? 1;
  const bucket = ordersCount >= 6 ? '6+' : ordersCount >= 2 ? '2-5' : String(ordersCount);
  const devices = await db.devicesForCustomer(customerId); // [{ device_uid }, ...]

  await Promise.all(devices.map((d) =>
    fetch('https://app.notipilot.com/api/v1/identify-device', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        app_id: process.env.NOTIPILOT_APP_ID,
        device_uid: d.device_uid,
        external_id: `shopify:${customerId}`,
        attributes: {
          orders_bucket: bucket,
          last_order_month: new Date().toISOString().slice(0, 7),
          cart_status: 'empty',
        },
      }),
    })
  ));

  res.sendStatus(200);
});

Useful webhook topics:

Webhook Attributes to update
orders/create orders_bucket, spend_bucket, last_order_month, cart_status: empty
customers/update city, country, customer_type (based on tags), consent_marketing
checkouts/update cart_status: active

The rate limit is 300 requests per minute per IP. Queue your requests for bulk updates, and on a 429 response wait for the number of seconds given in retry_after.

5. Notification content and deep linking

When sending from the dashboard, put routing information your app understands into the Data field:

JSON
{ "screen": "product", "handle": "red-dress" }
{ "screen": "collection", "handle": "new-season" }
{ "screen": "cart" }

In the app, read these fields in the notification tap handler and navigate to the matching screen (see the "notification tapped" examples in the platform guides). You can fetch the product page from the Storefront API by its handle: product(handle: "red-dress") { ... }.

6. Campaign examples

Campaign Segment
City-specific shipping promotion city: Istanbul
Announcement for international customers country: DE, country: NL …
First-order incentive orders_bucket: 0
Loyal customer discount orders_bucket: 6+ or customer_type: vip
Cart reminder cart_status: abandoned
  • For marketing notifications, pass Shopify's marketing consent (acceptsMarketing / marketingState) to the consent_marketing field.
  • Don't put personal data (email, phone, address lines) into attributes; use only external_id for matching.
  • When a customer submits a deletion request under KVKK / GDPR, send external_id: null for their devices and clear the related attributes by setting them to null.

Checklist

  • Platform integration completed (devices are registered)
  • identify-device is called on sign-in with external_id: "shopify:<id>"
  • country and city are sent from the default address
  • consent_marketing is populated from Shopify marketing consent
  • Numeric values are grouped into ranges (buckets)
  • (Optional) device_uid is stored in the backend and updated via webhooks