Helion

Revenue tracking

Learn how to easily track your revenue with Helion and how to get it shown directly in your dashboard.

Revenue tracking gives you a clear view of your top revenue sources. This page covers the fundamentals and the available integration patterns.

Before we start, we need to understand how Helion and your payment provider work together to link a payment to a specific visitor.

Payment providers

Checkout sessions are typically created server-side, which returns a payment link that the visitor is redirected to. When creating the checkout link, you can include additional fields such as metadata, customer information, or order details. We use this metadata field to carry the visitor's device_id so the payment can be linked back to them.

Helion

Helion is a cookieless analytics tool that identifies visitors using a device_id. To link a payment to a visitor, capture their device_id before they complete checkout. Store it in your payment provider's metadata, and when the payment webhook arrives, use it to attribute the revenue to the correct visitor.

Some typical flows

Revenue tracking from your backend (webhook)

This is the most common and most secure flow. Your backend receives webhooks from your payment provider — this is the ideal place to record revenue.

1
Visitor: Visits your website
2
Visitor: Makes a purchase
3
Your website: Does a POST request to get the checkout URL

When creating the checkout, call hl.getDeviceId() to retrieve the visitor's current deviceId. Pass it to your checkout endpoint.

fetch('https://domain.com/api/checkout', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    deviceId: hl.getDeviceId(), // ✅ links the payment to this visitor
    // ... other checkout data
  }),
})
  .then(response => response.json())
  .then(data => {
    window.location.href = data.paymentUrl;
  })
4
Your backend: Will generate and return the checkout URL
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, // ✅ stored for webhook attribution
    },
    success_url: 'https://domain.com/success',
    cancel_url: 'https://domain.com/cancel',
  });
  
  return Response.json({
    paymentUrl: session.url,
  });
} 
5
Visitor: Gets redirected to payment link
6
Visitor: Pays on your payment provider
7
Your backend: Receives a webhook for a successful payment
export async function POST(req: Request) {
  const event = await req.json();
  
  if (event.type === 'checkout.session.completed') {
    const session = event.data.object;
    const deviceId = session.metadata.deviceId;
    const amount = session.amount_total;
    
    hl.revenue(amount, { deviceId }); // ✅ attributes revenue to the correct visitor
  }
  
  return Response.json({ received: true });
}
8
Visitor: Redirected to your website with payment confirmation

Revenue tracking from your backend (webhook) - Identified users

If your visitors are identified (i.e., you have called identify with a profileId), the flow is simpler. You do not need to pass the deviceId when creating the checkout; provide the profileId in the webhook handler instead.

1
Visitor: Visits your website
2
Your website: Identifies the visitor

When a visitor logs in, call hl.identify() with their unique profileId.

hl.identify({
  profileId: 'user-123',
  email: 'user@example.com',
  firstName: 'John',
  lastName: 'Doe',
});
3
Visitor: Makes a purchase
4
Your website: Does a POST request to get the checkout URL

Since the visitor is already identified, no deviceId is required.

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 => {
    window.location.href = data.paymentUrl;
  })
5
Your backend: Will generate and return the checkout URL

Retrieve the profileId from the authenticated session and store it in the checkout metadata.

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();
  
  const profileId = req.session.userId;
  
  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,
        },
        quantity: 1,
      },
    ],
    mode: 'payment',
    metadata: {
      profileId: profileId, // ✅ stored for webhook attribution
    },
    success_url: 'https://domain.com/success',
    cancel_url: 'https://domain.com/cancel',
  });
  
  return Response.json({
    paymentUrl: session.url,
  });
} 
6
Visitor: Gets redirected to payment link
7
Visitor: Pays on your payment provider
8
Your backend: Receives a webhook for a successful payment
export async function POST(req: Request) {
  const event = await req.json();
  
  if (event.type === 'checkout.session.completed') {
    const session = event.data.object;
    const profileId = session.metadata.profileId;
    const amount = session.amount_total;
    
    hl.revenue(amount, { profileId }); // ✅ attributes revenue to the identified user
  }
  
  return Response.json({ received: true });
}
9
Visitor: Redirected to your website with payment confirmation

Revenue tracking from your frontend

This flow tracks revenue directly from the browser. Because the success page does not have access to the payment amount, revenue is recorded when checkout is initiated and confirmed on the success page.

1
Visitor: Visits your website
2
Visitor: Clicks to purchase
3
Your website: Track revenue when checkout is initiated

When the visitor clicks the checkout button, record a pending revenue event.

async function handleCheckout() {
  const amount = 2000; // Amount in cents

  // Store in sessionStorage — not sent until flushRevenue() is called
  hl.pendingRevenue(amount, {
    productId: '123',
    // ... other properties
  });
   
  window.location.href = 'https://checkout.stripe.com/...';
}
4
Visitor: Gets redirected to payment link
5
Visitor: Pays on your payment provider
6
Visitor: Redirected back to your success page
7
Your website: Confirm/flush the revenue on success page

On your success page, flush all pending revenue events. This sends every pending revenue recorded during checkout and clears them from sessionStorage.

// Send all pending revenues
await hl.flushRevenue();

// Or discard without sending (e.g., payment was cancelled)
hl.clearRevenue();

Pros:

  • Quick to implement
  • No backend required
  • Revenue is recorded when checkout starts

Cons:

  • Less accurate — the visitor may not complete payment
  • Less secure — anyone can post revenue data

Revenue tracking without linking it to an identity or device

If you only need aggregate revenue totals and do not need to link payments to specific visitors, call hl.revenue() from your backend without a deviceId or profileId. This is the simplest approach.

1
Visitor: Makes a purchase
2
Visitor: Pays on your payment provider
3
Your backend: Receives a webhook for a successful payment
export async function POST(req: Request) {
  const event = await req.json();
  
  if (event.type === 'checkout.session.completed') {
    const session = event.data.object;
    const amount = session.amount_total;
    
    hl.revenue(amount); // ✅ simple aggregate revenue tracking
  }
  
  return Response.json({ received: true });
}

Pros:

  • Simplest implementation
  • No device ID or profile ID required
  • Suitable for aggregate revenue reporting

Cons:

  • No source attribution. You cannot determine which campaigns, channels, or visitors generated the revenue.
  • Revenue events are not linked to specific user journeys or sessions.

Available methods

Revenue

Records a revenue event immediately. Requires a client secret — frontend revenue tracking must be explicitly enabled in your project settings for security reasons.

hl.revenue(amount: number, properties: Record<string, unknown>): Promise<void>

Add a pending revenue

Creates a pending revenue entry stored in sessionStorage. Nothing is sent until flushRevenue() is called. Pending entries are automatically restored from sessionStorage when the SDK initialises.

hl.pendingRevenue(amount: number, properties?: Record<string, unknown>): void

Send all pending revenues

Sends all pending revenue entries to Helion, then clears them from sessionStorage. Returns a Promise that resolves when all revenues have been sent.

await hl.flushRevenue(): Promise<void>

Clear any pending revenue

Discards all pending revenue entries from memory and sessionStorage without sending them. Use this when a payment is cancelled or you need to reset pending state.

hl.clearRevenue(): void

Fetch the current visitor's device ID

hl.getDeviceId(): string

On this page