# Connect Shopify

> Receive Shopify orders and refunds through signed webhooks and attribute them to visits.

Source: https://metricraven.com/docs/shopify  
Markdown: https://metricraven.com/docs/shopify.md  
Section: Revenue

Shopify sends paid and updated orders to webhooks signed with your store's webhook key. A cart attribute carries the visitor ID onto each order.

## Set up the webhooks

1. **Install the MetricRaven script in your theme**

   Follow [Install on Shopify](https://metricraven.com/docs/install-shopify).

2. **Copy the webhook URL**

   In MetricRaven, open **Settings → Integrations → Shopify** and copy the webhook URL.

3. **Create two webhooks in Shopify**

   In Shopify admin, open **Settings → Notifications → Webhooks → Create webhook**. Create one for **Order payment** and one for **Order update**, both in **JSON** format with the latest API version and this URL.

4. **Paste the signing key in MetricRaven**

   Below the webhook list Shopify shows the key your webhooks are signed with. Paste it into the Shopify card and choose **Connect Shopify**. For a custom app's webhooks, use the app's client secret instead.

## Add the visitor ID to the cart

Cart attributes are copied to the order's `note_attributes`. Add this to your theme after the MetricRaven script; it sets the `metricraven_visitor_id` attribute:

```html title="layout/theme.liquid"
<!-- Shopify admin → Online Store → Themes → Edit code → layout/theme.liquid, before </body>.
     The MetricRaven script tag must already be installed in the theme. -->
<script>
  (function () {
    function save() {
      var id = window.analytics && window.analytics.getVisitorId && window.analytics.getVisitorId();
      if (!id) return;
      // Cart attributes are copied to the order's note_attributes.
      fetch((window.Shopify && Shopify.routes ? Shopify.routes.root : "/") + "cart/update.js", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ attributes: { metricraven_visitor_id: id } }),
      });
    }
    window.addEventListener("analytics:visitor", save);
    window.addEventListener("load", save);
  })();
</script>
```

Customers' emails are also matched to emails you sent to the Identify API.

MetricRaven links each payment to a visit using the first of these that is available:

1. The `metricraven_visitor_id` passed through checkout.
2. The `metricraven_user_id` (or the provider's customer external ID), matched to a visitor you linked with the [Identify API](https://metricraven.com/docs/identify-users).
3. An earlier payment from the same provider customer, so renewals keep the original attribution.
4. The checkout email, matched to an email you sent to the Identify API.
5. The checkout email, matched to a browser that called [payment({ email })](https://metricraven.com/docs/attribute-by-email) within an hour of the payment.

> **Verify it works**
>
> After a delivery, the Shopify card in **Settings → Integrations** shows **Connected**, the number of verified events and the last event type. A live payment appears in **Revenue** in its own currency; with a visitor ID it is credited to that visit's source in **Traffic**.
>
> If the card says a delivery was rejected, the signing secret doesn't match. Paste it again or generate a replacement.

## Details

- Revenue is the order total in your shop currency (including tax and shipping). Refunds come from the order's current total, so they need the **Order update** webhook.
- Test orders are acknowledged but not counted. Unpaid and pending orders are not revenue.
- Signature: `X-Shopify-Hmac-Sha256` is the base64 HMAC-SHA256 of the raw request body with the signing key.
- Orders from subscription apps marked with source `subscription_contract` count as renewals.
- Each delivery is verified with an HMAC signature over the exact request body, compared in constant time. Unsigned or altered requests are rejected with `401` and never change revenue.
- Redelivered events are recognized and ignored, so provider retries never add a payment twice. Refunds reduce the original payment and never exceed it.
- The signing secret is stored encrypted, bound to this site, and never shown in reports, share links or logs. Disconnecting deletes it; imported revenue stays in your reports.
- Free trials and zero-amount orders are not revenue. A subscription cancellation is not a refund: revenue already paid stays.
- Webhooks deliver new events only. Payments from before you connected are not imported, and live MRR is currently available for Stripe only.
- Use one revenue path per purchase: don't also send the same orders through the Payments API.
