# Revenue tracking

URL: https://pieowl.com/docs/revenue-tracking





[Revenue attribution](/features/revenue-attribution) shows you which sources, campaigns, pages and countries bring in money. This page walks through checkout flows step by step. For how purchases are credited to visits and where to see revenue in the dashboard, see [Revenue attribution](/docs/revenue-attribution).

First, a few basics about how Pieowl and your payment provider work, and how to link a payment to a visitor.

Payment providers [#payment-providers]

Usually, you create your checkout from your backend, which then returns a payment link that your visitor will be redirected to. When creating the checkout link, you usually add additional fields such as metadata, customer information, or order details. We'll add the device ID information in this metadata field to be able to link your payment to a visitor.

Pieowl [#pieowl]

Pieowl doesn't use cookies. It identifies visitors with a `device_id`. To link a payment to a visitor, you need to capture their `device_id` before they complete checkout. This `device_id` will be stored in your payment provider's metadata, and when the payment webhook arrives, you'll use it to associate the revenue with the correct visitor.

Some typical flows [#some-typical-flows]

* [Revenue tracking from your backend (not identified)](#revenue-tracking-from-your-backend-webhook)
* [Revenue tracking from your backend (identified)](#revenue-tracking-from-your-backend-webhook---identified-users)
* [Revenue tracking from your frontend](#revenue-tracking-from-your-frontend)
* [Revenue tracking without linking it to a identity or device](#revenue-tracking-without-linking-it-to-an-identity-or-device)

In the backend examples, `pieowl` is a server-side instance created with your client secret (see the [JavaScript SDK](/docs/sdks/javascript)). In the browser examples, it's your [Web SDK](/docs/sdks/web) instance or `window.pieowl`.

Revenue tracking from your backend (webhook) [#revenue-tracking-from-your-backend-webhook]

This is the most common flow and most secure one. Your backend receives webhooks from your payment provider, and here is the best opportunity to do revenue tracking.

<Callout type="info">
  Pieowl credits the purchase to the visit of that `deviceId`: the visit going on when the webhook arrives or, if it has already ended, the visitor's most recent visit that started up to 7 days before the purchase. If there is no such visit, the purchase is still saved as a `revenue` event, but it doesn't show up in the revenue of sources, pages or countries.
</Callout>

<FlowStep step="1" actor="Visitor" description="Visits your website" icon="visitor" />

<FlowStep step="2" actor="Visitor" description="Makes a purchase" icon="visitor" />

<FlowStep step="3" actor="Your website" description="Does a POST request to get the checkout URL" icon="website">
  When you create the checkout, first call `pieowl.getDeviceId()`. It returns the visitor's current `deviceId`. Pass it to your checkout endpoint.

  ```javascript
  fetch('https://domain.com/api/checkout', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      deviceId: pieowl.getDeviceId(), // keep the visitor's deviceId with the payment
      // ... other checkout data
    }),
  })
    .then(response => response.json())
    .then(data => {
      // Handle checkout response, e.g., redirect to payment link
      window.location.href = data.paymentUrl;
    })
  ```
</FlowStep>

<FlowStep step="4" actor="Your backend" description="Will generate and return the checkout URL" icon="backend">
  ```javascript
  import Stripe from 'stripe';

  const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);

  export async function POST(req: Request) {
    const { deviceId, amount, currency } = await req.json();
    
    const session = await stripe.checkout.sessions.create({
      payment_method_types: ['card'],
      line_items: [
        {
          price_data: {
            currency: currency,
            product_data: { name: 'Product Name' },
            unit_amount: amount * 100, // Convert to cents
          },
          quantity: 1,
        },
      ],
      mode: 'payment',
      metadata: {
        deviceId: deviceId, // keep the deviceId with the payment
      },
      success_url: 'https://domain.com/success',
      cancel_url: 'https://domain.com/cancel',
    });
    
    return Response.json({
      paymentUrl: session.url,
    });
  } 
  ```
</FlowStep>

<FlowStep step="5" actor="Visitor" description="Gets redirected to payment link" icon="visitor" />

<FlowStep step="6" actor="Visitor" description="Pays on your payment provider" icon="payment" />

<FlowStep step="7" actor="Your backend" description="Receives a webhook for a successful payment" icon="backend">
  ```javascript
  export async function POST(req: Request) {
    const event = await req.json();
    
    // Stripe sends events with type and data.object structure
    if (event.type === 'checkout.session.completed') {
      const session = event.data.object;

      pieowl.purchase({
        id: session.id, // a resent webhook counts once
        amount: session.amount_total / 100, // Stripe sends cents
        currency: session.currency.toUpperCase(),
        deviceId: session.metadata.deviceId, // credited to this visitor's visit
      });
    }
    
    return Response.json({ received: true });
  }
  ```

  Stripe sends amounts in the smallest currency unit. For currencies without decimals, like JPY, send `session.amount_total` as it is. If your payment provider gives you the amount with decimals, send that.
</FlowStep>

<FlowStep step="8" actor="Visitor" description="Redirected to your website with payment confirmation" icon="success" />

***

Revenue tracking from your backend (webhook) - Identified users [#revenue-tracking-from-your-backend-webhook---identified-users]

If your visitors are identified (meaning you have called `identify` with a `profileId`), this process gets a bit easier. You don't need to pass the `deviceId` when creating your checkout, and you only need to provide the `profileId` (in backend) to the `purchase()` call.

Pieowl credits the purchase to the visitor's current visit, so its source, entry page and country get the revenue. If the visit has already ended (a visit ends after 30 minutes without activity), the purchase goes to the visitor's most recent visit that started up to 7 days before it. If you also have the visitor's `deviceId`, send it too: Pieowl tries it first. See [Revenue attribution](/docs/revenue-attribution#from-your-server).

<FlowStep step="1" actor="Visitor" description="Visits your website" icon="visitor" />

<FlowStep step="2" actor="Your website" description="Identifies the visitor" icon="website">
  When a visitor logs in or is identified, call `pieowl.identify()` with their unique `profileId`.

  ```javascript
  pieowl.identify({
    profileId: 'user-123', // Unique identifier for this user
    email: 'user@example.com',
    firstName: 'John',
    lastName: 'Doe',
  });
  ```
</FlowStep>

<FlowStep step="3" actor="Visitor" description="Makes a purchase" icon="visitor" />

<FlowStep step="4" actor="Your website" description="Does a POST request to get the checkout URL" icon="website">
  Since the visitor is already identified, you don't need to fetch or pass the `deviceId`. Just send the checkout data.

  ```javascript
  fetch('https://domain.com/api/checkout', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      // No deviceId needed - user is already identified
      // ... other checkout data
    }),
  })
    .then(response => response.json())
    .then(data => {
      // Handle checkout response, e.g., redirect to payment link
      window.location.href = data.paymentUrl;
    })
  ```
</FlowStep>

<FlowStep step="5" actor="Your backend" description="Will generate and return the checkout URL" icon="backend">
  Since the user is authenticated, you can get their `profileId` from the session and store it in metadata for easy retrieval in the webhook.

  ```javascript
  import Stripe from 'stripe';

  const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);

  export async function POST(req: Request) {
    const { amount, currency } = await req.json();
    
    // Get profileId from authenticated session
    const profileId = req.session.userId; // or however you get the user ID
    
    const session = await stripe.checkout.sessions.create({
      payment_method_types: ['card'],
      line_items: [
        {
          price_data: {
            currency: currency,
            product_data: { name: 'Product Name' },
            unit_amount: amount * 100, // Convert to cents
          },
          quantity: 1,
        },
      ],
      mode: 'payment',
      metadata: {
        profileId: profileId, // Store profileId instead of deviceId
      },
      success_url: 'https://domain.com/success',
      cancel_url: 'https://domain.com/cancel',
    });
    
    return Response.json({
      paymentUrl: session.url,
    });
  } 
  ```
</FlowStep>

<FlowStep step="6" actor="Visitor" description="Gets redirected to payment link" icon="visitor" />

<FlowStep step="7" actor="Visitor" description="Pays on your payment provider" icon="payment" />

<FlowStep step="8" actor="Your backend" description="Receives a webhook for a successful payment" icon="backend">
  In the webhook handler, retrieve the `profileId` from the session metadata.

  ```javascript
  export async function POST(req: Request) {
    const event = await req.json();
    
    // Stripe sends events with type and data.object structure
    if (event.type === 'checkout.session.completed') {
      const session = event.data.object;

      pieowl.purchase({
        id: session.id,
        amount: session.amount_total / 100, // Stripe sends cents
        currency: session.currency.toUpperCase(),
        profileId: session.metadata.profileId, // Use profileId instead of deviceId
      });
    }
    
    return Response.json({ received: true });
  }
  ```
</FlowStep>

<FlowStep step="9" actor="Visitor" description="Redirected to your website with payment confirmation" icon="success" />

***

Revenue tracking from your frontend [#revenue-tracking-from-your-frontend]

This flow sends the purchase from your frontend. Call `pieowl.purchase()` on your success page, or where your code learns that the payment went through. If your success page doesn't know the order, look it up on your server, for example with the checkout session ID your payment provider adds to the success address. Pieowl stores nothing in the browser between checkout and the success page.

<FlowStep step="1" actor="Visitor" description="Visits your website" icon="visitor" />

<FlowStep step="2" actor="Visitor" description="Clicks to purchase" icon="visitor" />

<FlowStep step="3" actor="Your website" description="Sends the visitor to checkout" icon="website">
  Create the checkout with a success address that tells your page which order was paid. With Stripe Checkout, add `session_id={CHECKOUT_SESSION_ID}` to `success_url`.
</FlowStep>

<FlowStep step="4" actor="Visitor" description="Pays on your payment provider" icon="payment" />

<FlowStep step="5" actor="Visitor" description="Redirected back to your success page" icon="visitor" />

<FlowStep step="6" actor="Your website" description="Sends the purchase on the success page" icon="website">
  Look up the paid order and send it with `purchase()`. The same order sent twice counts once.

  ```javascript
  const sessionId = new URLSearchParams(location.search).get('session_id');
  const order = await fetch(`/api/orders/${sessionId}`).then((res) => res.json());

  pieowl.purchase({
    id: order.id,
    amount: order.total, // in normal units, like 49.99
    currency: order.currency,
  });
  ```
</FlowStep>

Pros: [#pros]

* Quick way to get going
* No webhook required

Cons: [#cons]

* Missed if the visitor closes the tab before the success page loads
* Less "secure": anyone could post revenue data
* Needs **Accept revenue sent from the browser** turned on in Settings → Site → Revenue
* Revenue sent from the browser is conversion measurement. In some countries it needs your visitors' consent. See [Do you need a cookie banner?](/docs/consent-and-cookie-banners)

***

Revenue tracking without linking it to an identity or device [#revenue-tracking-without-linking-it-to-an-identity-or-device]

If you simply want to track revenue totals without linking payments to specific visitors or devices, you can call `pieowl.purchase()` directly from your backend without providing a `deviceId` or `profileId`. This is the simplest approach and works well when you only need aggregate revenue data.

<FlowStep step="1" actor="Visitor" description="Makes a purchase" icon="visitor" />

<FlowStep step="2" actor="Visitor" description="Pays on your payment provider" icon="payment" />

<FlowStep step="3" actor="Your backend" description="Receives a webhook for a successful payment" icon="backend">
  Call `pieowl.purchase()` with the order number and amount. No `deviceId` or `profileId` is needed.

  ```javascript
  export async function POST(req: Request) {
    const event = await req.json();
    
    // Stripe sends events with type and data.object structure
    if (event.type === 'checkout.session.completed') {
      const session = event.data.object;

      // Simple revenue tracking without linking to a visitor
      pieowl.purchase({
        id: session.id,
        amount: session.amount_total / 100,
        currency: session.currency.toUpperCase(),
      });
    }
    
    return Response.json({ received: true });
  }
  ```
</FlowStep>

Pros: [#pros-1]

* Simplest implementation
* No need to capture or pass device IDs
* Works well for aggregate revenue tracking

Cons: [#cons-1]

* **You can't dive deeper into where this revenue came from.** For instance, you won't be able to see which source generates the best revenue, which campaigns are most profitable, or which visitors are your highest-value customers.
* Revenue events won't be linked to specific user journeys or sessions
* The overview's revenue only counts purchases credited to a visit, so these purchases don't show up there. You can still chart the `revenue` event in reports.

Available methods [#available-methods]

Purchase [#purchase]

Sends a paid order. For security, Pieowl rejects purchases that aren't sent with your client secret. To send them from the browser, turn on **Accept revenue sent from the browser** in Settings → Site → Revenue.

* `id`: your order number. The same order sent twice counts once.
* `amount`: in normal units, like `49.99`, not cents.
* `currency`: an ISO 4217 code, like `EUR`. Optional, your site currency by default. See [Currency](/docs/revenue-attribution#currency).
* `deviceId`: from a server, the visitor's device ID from `pieowl.getDeviceId()`.
* Any other properties, like `plan`.

```javascript
pieowl.purchase({ id: string, amount: number, currency?: string, deviceId?: string, ...properties }): Promise<void>
```

Refund [#refund]

Subtracts an order from your revenue. Without `amount`, the whole order is refunded. Pass `amount` (and `currency`) for a partial refund, and a `refundId` to count several partial refunds of one order. The visit the order was paid in loses the amount. Browser refunds need the same setting as purchases.

```javascript
pieowl.refund({ id: string, amount?: number, currency?: string, deviceId?: string }): Promise<void>
```

Revenue (older) [#revenue-older]

The older way to send a purchase. The amount is a whole number in the smallest unit of your site currency, like cents (`4900` for 49.00). It has no order number, so a resent purchase counts twice. Use `purchase()` for new code.

```javascript
pieowl.revenue(amount: number, properties?: Record<string, unknown>): Promise<void>
```

Fetch your current users device id [#fetch-your-current-users-device-id]

```javascript
pieowl.getDeviceId(): string
```
