MenuShopify

Revenue attribution

Shopify

Connect Shopify with a custom app token and signed webhooks so ProductLense records paid orders and refunds and attributes each buyer.

ProductLense reads your Shopify customers, paid orders and refunds and attributes each buyer to the channel that first brought them. Shopify orders are one-off purchases, so there are no subscriptions. ProductLense only reads: it never writes to your store, never refunds, cancels, fulfils or changes anything.

What you need

FieldSecretWhere to find it
Shop domainNoYour myshopify.com domain, for example acme.myshopify.com.
Admin API access tokenYesThe shpat_... token of a custom app with the read_orders and read_customers scopes.
Webhook signing secretYesThe key Shopify signs webhooks with: shown in Settings → Notifications → Webhooks when you create webhooks there, or the custom app's client secret when the app registers them through the Admin API.

Every plan includes one billing connection per site, Free too; Growth and Scale allow any number. See plans and limits.

Permissions

Give the custom app these Admin API scopes, both read-only:

  • read_orders
  • read_customers

ProductLense calls the Admin API (version 2025-01): GET shop.json, customers.json and orders.json (paid orders, any status).

Set up

  1. In Shopify admin, open Settings → Apps and sales channels → Develop apps → Create an app.
  2. Under Configuration, give it the Admin API scopes above, install it, and copy the Admin API access token.
  3. In ProductLense, open your site's Settings → Billing connections → Shopify, enter the shop domain and token, check the Processor fees estimate, and click Connect and import history.
  4. Copy the webhook URL from the connection card.
  5. In Shopify admin, open Settings → Notifications → Webhooks and create a JSON webhook for each topic below with that URL. Copy the signing key shown on that page into the connection in ProductLense.
  6. Add the ProductLense script to your theme as in the Shopify install guide.

Webhook

URL: https://api.productlense.app/api/webhooks/shopify/<connection-id> (ProductLense shows the exact URL after you save).

Topics ProductLense uses:

  • orders/paid, orders/updated
  • refunds/create
  • customers/create, customers/update

Verification: the X-Shopify-Hmac-Sha256 header, a base64 HMAC-SHA256 of the raw body. The X-Shopify-Shop-Domain header must also equal the connection's shop domain, so one connection never accepts another shop's data. A repeated delivery of the same order or refund is processed once.

What ProductLense reads

  • Customers: email and name.
  • Payments: paid orders, with their note attributes, landing site, referring site, source and tags.
  • Refunds: order refunds.
  • Subscriptions: none.
  • History: the last 12 months on first connect (limited to 60 days without read_all_orders), then a nightly reconcile at 03:00 UTC from the last sync minus 48 hours.

How fees are derived

Shopify Payments fees are exposed only by the Payouts and Balance Transactions API, which ProductLense does not read, so every order arrives with a fee of 0.

A fee estimate can be set under Processor fees on the connection card (percent, fixed amount per payment, and its currency), for example your Shopify Payments rate. It starts at 0 and is applied only to charges that arrive with a zero fee. Estimated fees are flagged, and the dashboard shows the share of fees in a period that is estimated. An estimate is only as accurate as the rate you enter; card type, currency and your Shopify plan change the real fee.

Match payments to visitors

Theme scripts do not run on Shopify's checkout, so the link is made before checkout. ProductLense reads the user id from the order's note attributes (cart attributes): pl_user_id, user_id, userId or uid. A ProductLense visitor id in pl_visitor_id or visitor_id is also accepted. Without either, ProductLense falls back to the customer email, matched to the email trait sent with identify, or to a user id that is that email.

layout/theme.liquid, for logged-in customers
{% if customer %}
  <script>
    fetch('/cart/update.js', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ attributes: { pl_user_id: '{{ customer.id }}' } }),
    });
  </script>
{% endif %}

Call identify with the same customer.id (see the Shopify install guide), so the cart attribute and the visitor share one id. See how payments are attributed.

    ↑ ↓ to move, Enter to open