> ## Documentation Index
> Fetch the complete documentation index at: https://docs.northbeam.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Purchase Events

> Send completed purchases from your website to Northbeam so orders can be attributed.

A purchase event tells Northbeam that an order was completed in the visitor's browser. Northbeam matches it to the order you send from your backend (through Shopify or the [Orders API](/reference/post_orders)) and uses it to attribute that order.

Every checkout flow on your site needs a purchase event. If one is missing, orders from that flow show as unattributed.

<span id="tracking-purchases-on-shopify-stores" style={{ display: "block", scrollMarginTop: "7rem" }} />

<span id="tracking-purchases-on-non-shopify-stores" style={{ display: "block", scrollMarginTop: "7rem" }} />

## Which setup you need

| Your store | How purchases are tracked |
| :- | :- |
| **Shopify** | Automatically, by the Northbeam Custom Pixel. Set it up in [Shopify, step 2](/docs/shopify-installation#2-set-up-the-northbeam-custom-pixel). You don't need anything on this page. |
| **Custom** | Add `fireSlimPurchaseEvent` to your order confirmation page. **Follow this page.** |
| **Third-party checkout** that can't pass the order ID | Use `identify` instead. See [Identify Events](/docs/third-party-checkouts). |

## Choose a function

**Use `fireSlimPurchaseEvent`.** It sends only the order ID, which is all Northbeam needs.

Northbeam uses the order ID to match the purchase to your backend order. Revenue, tax, shipping, and line items always come from your backend order data (through Shopify or the [Orders API](/reference/post_orders)), never from the browser.

| Function | What it sends | Use it? |
| :- | :- | :- |
| `fireSlimPurchaseEvent` | The order ID | **Yes. Recommended for every store.** |
| `firePurchaseEvent` | The order ID plus order details | Still supported, but only the order ID is used. No need to switch if you already have it working. |

Fire the function on the **Order Confirmation** or **Thank You** page, after the order is complete. You can add it to your site's code or through Google Tag Manager.

<span id="global-requirements-both-fireslimpurchaseevent-and-firepurchaseevent" style={{ display: "block", scrollMarginTop: "7rem" }} />

## Requirements

Whichever function you use, it must:

* Fire **after** the [Northbeam Pixel](/docs/add-pixel) has loaded.
* Fire **only after the order is final** and payment has been processed. This keeps cancelled transactions out.
* Fire **once per order**. See [Fire once per order](#critical-fire-once-per-order).
* Use the **exact order ID** your backend sends to Northbeam. See [Match the order ID exactly](#match-the-order-id-exactly).

<Warning>
  Purchase data can't be recovered later. If the event is set up incorrectly, those orders will be missing attribution. We strongly recommend that someone with basic JavaScript experience sets this up.
</Warning>

<span id="critical-id-=-order_id" style={{ display: "block", scrollMarginTop: "7rem" }} />

## Match the order ID exactly

Northbeam uses the `id` value to match the purchase event to your backend order (the `order_id` you send through the [Orders API](/reference/post_orders)).

The two values must match exactly, including capitalization. If they don't, the purchase won't attribute.

<span id="critical-fire-once-per-order" style={{ display: "block", scrollMarginTop: "7rem" }} />

## Fire once per order

Your purchase event must fire **exactly once** for each order. If it fires more than once for the same order, Northbeam may not match the purchase to your backend order, and the order can show as unattributed.

<Warning>
  Sending the same order more than once does **not** improve tracking. Duplicate purchase events can cause the order to lose its attribution.
</Warning>

Common causes of duplicate fires:

* The customer refreshes or revisits the **Order Confirmation** page (for example, from a bookmark or a link in the confirmation email).
* The script is installed twice (for example, hard-coded on the site **and** added in Google Tag Manager).
* A Google Tag Manager trigger fires on more than one event (for example, both Page View and DOM Ready).
* A single-page checkout re-renders the confirmation step.

To prevent this, make sure your checkout only fires the purchase event the first time the confirmation page loads for an order. Later page loads for the same order should not fire it again.

***

<span id="window-northbeam-fireslimpurchaseevent" style={{ display: "block", scrollMarginTop: "7rem" }} />

<span id="-windownorthbeamfireslimpurchaseevent" style={{ display: "block", scrollMarginTop: "7rem" }} />

## `fireSlimPurchaseEvent`

Sends only the order ID. This is the recommended function for every store.

```javascript theme={null}
<script type="application/javascript">
  window.Northbeam.fireSlimPurchaseEvent("A123" || "Missing - unmatched order")
</script>
```

Replace `"A123"` with the variable that holds your order ID. Pass the value directly. You don't need to wrap it in an object.

| Value | Type | Description | Example |
| :- | :- | :- | :- |
| `id` | *String* | The order number. Must exactly match the `order_id` in the [Orders API](/reference/post_orders). At least 1 character. Case-sensitive. | "A123" |

<Note>
  You don't need to send revenue or other order details. Northbeam gets those from your backend order data.
</Note>

***

<span id="window-northbeam-firepurchaseevent" style={{ display: "block", scrollMarginTop: "7rem" }} />

## `firePurchaseEvent`

The original purchase function. It sends the order ID plus order details like revenue, tax, shipping, and line items.

**Northbeam only uses the order ID from this event.** The other fields aren't used for reporting, because order details come from your backend. If you're setting up purchase tracking now, use [`fireSlimPurchaseEvent`](#window-northbeam-fireslimpurchaseevent) instead. If you already use `firePurchaseEvent` and it works, you don't need to change it.

```javascript theme={null}
<script type="application/javascript">
  window.Northbeam.firePurchaseEvent({
    id: "ORD98" || "Missing - unmatched order",
    totalPrice: 100 || 0,
    shippingPrice: 10 || 0,
    taxPrice: 5.5 || 0,
    coupons: "SALE20, FAMILY10" || "",
    currency: "USD" || "USD",
    customerId: "customer-93" || "UNKNOWN VISITOR",
    lineItems: [
      {
        productId: "SKU-0001" || "UNKNOWN PRODUCT",
        variantId: "0001-A" || "",
        productName: "Pants" || "UNKNOWN VARIANT",
        variantName: "Blue" || "",
        price: 10 || 0,
        quantity: 1 || 0,
      }
    ]
  })
</script>
```

`id` is the only required field. It must exactly match the `order_id` in the [Orders API](/reference/post_orders). For other fields, an empty string (`""`) or `0` is fine when a value doesn't apply. Avoid `null`.

<Accordion title="Field reference">
  **Order fields**

  | Field | Type | Description | Example |
  | :- | :- | :- | :- |
  | `id` | *String* | The order number. Must exactly match the `order_id` in the [Orders API](/reference/post_orders). At least 1 character. Case-sensitive. | "ORD98" |
  | `totalPrice` | *Number* | The final amount paid, *after* taxes, shipping, and discounts. Must be a positive number. | 12 |
  | `shippingPrice` | *Number* | Shipping charges. Must be a number, zero or more. | 6 |
  | `taxPrice` | *Number* | Tax charges. Must be a number, zero or more. | 7 |
  | `coupons` | *String* | Coupons applied to the order. Use one string; separate multiple coupons with commas (for example, `"coupon 1, coupon 2"`). | "PROMO 4" |
  | `currency` | *String* | The 3-letter [ISO 4217](https://www.iso.org/iso-4217-currency-codes.html) currency code. | "USD" |
  | `customerId` | *String* | The customer's ID. At least 1 character. | "VICTOR12" |
  | `lineItems` | *Array of objects* | One array, with one object per product. Each object needs every line item field below. | See below |

  **Line item fields**

  These fields go inside each object in `lineItems`. They can't be used outside it.

  | Field | Type | Description | Example |
  | :- | :- | :- | :- |
  | `productId` | *String* | The parent product's ID. At least 1 character. | "SKU-01" |
  | `variantId` | *String* | The variant's ID, if the product has variants. | "VAR-01" |
  | `productName` | *String* | The product name. At least 1 character. | "Hat" |
  | `variantName` | *String* | The variant name, if the product has variants. | "Green Hat" |
  | `price` | *Number* | The item's gross price. Must be a positive number. | 12 |
  | `quantity` | *Number* | How many of this item were purchased. Must be a positive number. | 2 |
</Accordion>

***

## Validation and testing

Open your browser's **Network** tab *before* you complete a test order. The requests can't be reviewed after the fact.

When the event fires correctly, you'll see two **nb-collector** requests together:

1. One with a payload containing `data: [{e: "ue",…}]`
2. One with a payload containing `data: [{e: "se",…}]`. Its **se\_la** value is the order number Northbeam received.

You only see these two requests together when the event is valid.

To confirm the event fires only once, refresh the confirmation page with the Network tab open. You should **not** see a second **nb-collector** request for the same order number.

<span id="sequencing" style={{ display: "block", scrollMarginTop: "7rem" }} />

<span id="publishing" style={{ display: "block", scrollMarginTop: "7rem" }} />

## Google Tag Manager

If you add the purchase event through Google Tag Manager:

* **Sequence your tags.** The Northbeam Pixel tag must fire before the purchase event tag. See Google's guide to [tag sequencing](https://support.google.com/tagmanager/answer/6238868?hl=en).
* **Use one trigger.** Make sure the tag fires on a single event, so it doesn't fire twice.
* **Publish the container.** Unpublished changes don't track anything.

## Final checklist

Before going live, confirm:

* The purchase event fires on the confirmation page
* It fires exactly once per order, and refreshing the confirmation page does not fire it again
* The order ID matches your backend order data exactly
* The Northbeam Pixel fires first
* Your Google Tag Manager container is published (if you use it)

## Best practices

* **Log success and failure.** Wrap the purchase function in code that logs whether it ran. This makes testing much easier.
* **Wait for the pixel.** Check that `window.Northbeam` is ready before calling the function, and retry if it isn't. This prevents load-order problems.

### Example with logging and retry

```javascript theme={null}
<script type="application/javascript">
(function northbeamPurchaseRetry() {

    function attemptPurchaseEvent() {
        if (
            window.Northbeam &&
            typeof window.Northbeam.fireSlimPurchaseEvent === 'function'
        ) {
            console.log('Northbeam fireSlimPurchaseEvent fired');

            window.Northbeam.fireSlimPurchaseEvent(
                "A123" || "Missing - unmatched order"
            );

            console.log('Northbeam fireSlimPurchaseEvent successful');
        } else {
            console.log('Northbeam fireSlimPurchaseEvent not ready, retrying in 3 seconds...');

            setTimeout(attemptPurchaseEvent, 3000);
        }
    }

    attemptPurchaseEvent();

})();
</script>
```

## Related

* [Custom](/docs/non-shopify-installation): full setup for custom stores
* [Identify Events](/docs/third-party-checkouts): when your checkout can't pass the order ID
* [Purchases or Orders Are Missing](/docs/purchases-or-orders-are-missing): troubleshooting unattributed orders
