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
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)
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
cityandcountryCodeV2from the customer's default shipping address are the most reliable source for location segments in NotiPilot. For signed-out users, at least sendcountrybased on the device region.
3. Recommended e-commerce attributes
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):
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
429response wait for the number of seconds given inretry_after.
5. Notification content and deep linking
When sending from the dashboard, put routing information your app understands into the Data field:
{ "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 |
7. Legal notes
- For marketing notifications, pass Shopify's marketing consent (
acceptsMarketing/marketingState) to theconsent_marketingfield. - Don't put personal data (email, phone, address lines) into attributes; use only
external_idfor matching. - When a customer submits a deletion request under KVKK / GDPR, send
external_id: nullfor their devices and clear the related attributes by setting them tonull.
Checklist
- Platform integration completed (devices are registered)
-
identify-deviceis called on sign-in withexternal_id: "shopify:<id>" -
countryandcityare sent from the default address -
consent_marketingis populated from Shopify marketing consent - Numeric values are grouped into ranges (buckets)
- (Optional)
device_uidis stored in the backend and updated via webhooks