Shopify
Products become catalog items, orders become outcomes, and no customer field is ever read.
Shopify gives Patterna two things: your products as catalog items of kind product, and orders as order_placed outcomes. There are two ways in. Without any token, the storefront sync reads your shop's public product feed and an orders export gives co-purchase pairs (next section). With a read-only Admin API token, Patterna reads products and orders on a schedule, and webhooks for changes can go straight to Patterna or through your backend (sections 1 and 2). Neither way selects a customer field.
Without a token: the storefront feed and an orders export#
Products. Every Shopify store publishes https://<your-shop>/products.json. The storefront sync reads it page by page and upserts each product: the Shopify product id as external_id (the same key the Admin connector uses, so switching later creates no duplicates), the product type as collection, the shop's tags, the description as text for tagging, and the attributes price_cents, currency, compare_at_cents (on sale), available (any variant in stock), image_url, url, handle, product_type and format: "product". A product that left the feed is archived, not deleted. The sync runs on a schedule once the connector is configured for your tenant, and you can start one at any time from Connectors → Shopify storefront → Sync now (the panel queues it; the worker picks it up within a minute and shows the result: products read, products archived).
The schedule is a tenant setting we set for you: settings.connectors.shopify_storefront = {"enabled": true, "shop_url": "https://your-shop.example", "every_minutes": 60}. The worker runs the feed every every_minutes (hourly by default) and writes each run under its own source, shopify_storefront, so Connectors shows the storefront's last run separately from the webhook's. A failed run raises a connector_failure alert (email or Slack, from Settings → Alerts) and appears in the notification centre. Without the setting the feed is read only when you press Sync now or when we run it by hand:
python -m patterna_jobs shopify-storefront --tenant your-tenant --shop-url https://your-shop.exampleThe feed is capped at 10,000 products; a larger shop uses the Admin token.
Order history. Export your orders as CSV from Shopify admin (Orders → Export) and hand us the file. The reader keeps three columns (order name, line-item name, SKU), matches line items to products by SKU and then by title, and writes product pairs bought together with their lift, for pairs in at least two orders. Names, emails and addresses in the export are never read, and the file is not copied. The pairs feed each product's neighbours ("bought together"); they say nothing about any one customer.
python -m patterna_jobs orders-csv --tenant your-tenant --csv orders_export.csvIt prints the order count, the multi-product orders, the pairs and the line items it could not match. New orders after the export still need order_placed events (section 2) to count in purchase_rate and the repurchase window.
Create an Admin API token#
In Shopify admin, open Settings → Apps and sales channels → Develop apps and create an app named
Patterna.Under Configuration → Admin API integration, select exactly these scopes:
read_products read_ordersInstall the app and copy the Admin API access token. Shopify shows it once.
Hand it to us through your secret channel, together with your shop domain (
your-shop.myshopify.com). Today the token lives in the jobs worker's environment, named in the connector's config (token_env); it is not entered in the panel and never stored in the database.
The jobs use the token to read products and orders through the Admin GraphQL API. The order query selects no customer, address, email or note fields.
Add the webhooks#
The endpoint for the two webhooks, both in JSON:
https://api.patterna.nl/v1/webhooks/shopify/your-tenant| Topic | What Patterna does |
|---|---|
orders/create | writes an order_placed event |
products/update | upserts the product as an item of kind product |
You can create them under Settings → Notifications → Webhooks, or from the app. Shopify signs each delivery with an X-Shopify-Hmac-Sha256 header.
The tenant is in the path; there is no Bearer key. Each delivery is authenticated by its HMAC alone, under the shopify_webhook_secret an owner sets with PATCH /settings. Use the secret Shopify shows for the webhooks you created (admin-made and app-made webhooks have different secrets).
- No secret set: every delivery is refused with
401 webhook_not_configured. - Wrong HMAC:
401 bad_signature.
You can also receive orders/create in your own backend instead: verify Shopify's HMAC there, map the Shopify customer to your random Patterna id (or leave the user out), and send order_placed with POST /events and your ingest key. That path keeps the customer mapping on your side. Product updates also come in through the scheduled product read, or your own catalog sync with PUT /catalog/items.
What becomes what#
Products → items. Kind product, with the editorial title, status and attributes such as price_cents, currency, categories[], image_url and url. Products are tagged into your taxonomy like any other item.
Orders → order_placed.
| Field | Value |
|---|---|
order_id_hash | a hash of the Shopify order id, never the id itself |
product_ids[] | the products in the order |
value_cents | order value in cents |
currency | ISO currency code |
An order is linked to a user or session only through the patterna_uid or patterna_sid cart attribute, or by your backend when it forwards the order. Without one, the order is stored without a user: it still feeds the co-purchase pairs and the popularity prior, not any one person's profile. The ledger computes no revenue figure today; its product metric is purchase_rate (an order containing a shown product within 7 days of a tap). See Proof and the ledger.
The shop link parameter#
When your app links from a recommendation to a product page in the shop, add one tracking parameter to the link, ref=<request_id>: the request id of the slate, so the visit becomes a product_viewed event with product_id and request_id. It carries nothing else.
Never put a user id in the link: not the Patterna id, not an email, an account id or a Shopify customer id. A shop URL is public (shared, logged, cached), and a pseudonymous id in it would travel with it.
Linking an order to a person happens on your side, not in the link: your backend forwards the order with the Patterna id or session id, or your storefront writes the patterna_uid or patterna_sid cart attribute server-side from its own session, never from a URL parameter. The order reader looks only at those cart attributes. Until one of the two is in place, orders arrive without a user link, and forwarding orders from your backend is the simpler path. See Identity across connectors.
What is never read#
- Customers, emails, names, phone numbers, addresses.
- Order notes and any free text.
- The raw Shopify order id (only its hash is stored).
Check it works#
Connectors → Shopify shows the last run, rows in, rows mapped, fields dropped and errors; Shopify storefront shows the feed's own runs and the pending Sync now request. Place a test order and confirm one order_placed row appears.