Guides
How-tos
Practical walkthroughs for the tasks product teams set up most often. Each guide covers the dashboard steps and, where it applies, code with the React SDK. For full reference material, see the documentation.
Create a custom event
Track a product-specific action — like a newsletter signup or demo request — so it shows up in reports, funnels, and experiment goals.
In the dashboard
- Open your property and go to Events/Funnel/Audience in the left nav.
- Stay on the Custom events tab and scroll to Add event.
- Enter a display name and choose Manual (fired from code) or On click (DOM-triggered) if a button click should record the event with no code.
- Confirm the generated token starts with
custom_(for examplecustom_newsletter_signup). Add optional metadata fields if you plan to break down the event in reports. - Click Create event. The token is live on your next page load — unregistered tokens are dropped at ingestion.
In your React app
React SDK — tracker is ready for you
Wrap your app in BadgerlyticsProvider (see Script Installation → React). The provider loads the script and bridges React to the tracker — use useBadgerlyticsTracker() in components and call trackCustomEvent, trackFunnelStep, trackProductView, and the rest on that object. You don't need window.badgerlytics in React code. For shared modules outside components, use callWhenTrackerReady() from the same package (see useful script functions in the docs).
Use useBadgerlyticsTracker() to call trackCustomEvent when the action happens:
import { useCallback } from 'react';import { useBadgerlyticsTracker } from '@badgerlytics/sdk/react';export function NewsletterSignup() {const tracker = useBadgerlyticsTracker();const onSubmit = useCallback(() => {// Token must match the dashboard registration exactlytracker?.trackCustomEvent('custom_newsletter_signup', {source: 'footer',});}, [tracker]);return (<form onSubmit={(e) => { e.preventDefault(); onSubmit(); }}>{/* … */}<button type="submit">Subscribe</button></form>);}
Not using React?
badgerlytics.trackCustomEvent() on window.badgerlytics after the script tag loads — see Custom Events in the docs.Verify and explore
- Trigger the event on your site (or use test mode to watch the browser console).
- Open Reports → Engagement → Custom Events to confirm counts are flowing.
More on registration limits, DOM triggers, and reporting: Custom Events and Tracking script API.
Create a funnel
Map the ordered steps visitors take toward a goal — for example browsing products through checkout — and measure drop-off between each step.
In the dashboard
- Open Events/Funnel/Audience and switch to the Funnels tab.
- Under Add funnel, give the funnel a name and token (for example
purchase). - Add steps in order. A typical ecommerce purchase funnel looks like:
- 1 — Product listing page (
product_listing_page) - 2 — Product display page (
product_display_page) - 3 — Add to cart (
add_to_cart) - 4 — View cart (
view_cart) - 5 — Payment and review (
payment_and_review) - 6 — Conversion (
conversion)
- 1 — Product listing page (
- Click Create funnel. Use the row's Tracking example button anytime to copy ready-made
trackFunnelStepcalls.
In your React app
React SDK — tracker is ready for you
Wrap your app in BadgerlyticsProvider (see Script Installation → React). The provider loads the script and bridges React to the tracker — use useBadgerlyticsTracker() in components and call trackCustomEvent, trackFunnelStep, trackProductView, and the rest on that object. You don't need window.badgerlytics in React code. For shared modules outside components, use callWhenTrackerReady() from the same package (see useful script functions in the docs).
Keep step numbers in a shared module. In components, call trackFunnelStep on the tracker from useBadgerlyticsTracker(); use callWhenTrackerReady() in the helper for non-component call sites:
import { callWhenTrackerReady } from '@badgerlytics/sdk/react';export const FUNNEL_NAME = 'purchase';export const FUNNEL_STEPS = Object.freeze({PRODUCT_LISTING_PAGE: { number: 1, name: 'product_listing_page' },PRODUCT_DISPLAY_PAGE: { number: 2, name: 'product_display_page' },ADD_TO_CART: { number: 3, name: 'add_to_cart' },VIEW_CART: { number: 4, name: 'view_cart' },PAYMENT_AND_REVIEW: { number: 5, name: 'payment_and_review' },CONVERSION: { number: 6, name: 'conversion' },});/** Shared helper for call sites outside React components (e.g. cart context). */export function trackFunnelStep(step) {if (!step?.number || !step?.name) return;callWhenTrackerReady((tracker) => {tracker.trackFunnelStep(FUNNEL_NAME, step.number, step.name);});}
import { useEffect, useCallback } from 'react';import { useBadgerlyticsTracker } from '@badgerlytics/sdk/react';import { FUNNEL_NAME, FUNNEL_STEPS } from '@/lib/tracking';export function ProductDetail({ product }) {const tracker = useBadgerlyticsTracker();useEffect(() => {if (!product || !tracker) return;tracker.trackFunnelStep(FUNNEL_NAME,FUNNEL_STEPS.PRODUCT_DISPLAY_PAGE.number,FUNNEL_STEPS.PRODUCT_DISPLAY_PAGE.name,);}, [product, tracker]);const handleAddToCart = useCallback(() => {// …add to cart logictracker?.trackFunnelStep(FUNNEL_NAME,FUNNEL_STEPS.ADD_TO_CART.number,FUNNEL_STEPS.ADD_TO_CART.name,);}, [tracker]);return (/* PDP UI */);}
Note
add_to_cart, not on every re-render.Read the results
- After traffic flows, open Reports → Engagement → Conversion Funnel.
- Pick your funnel and date range to see step-by-step completion and drop-off.
Full setup notes and considerations: Funnels.
Integrate a product view event
Record what visitors look at — a product detail page in ecommerce or a plan on your pricing page in SaaS — so view-to-purchase rates and top-viewed items show up in reports.
When to fire it
- Ecommerce: once per product detail page (PDP) visit.
- SaaS: once per plan card or pricing row the visitor sees (match
product_codeto your plan catalogue).
In your React app
React SDK — tracker is ready for you
Wrap your app in BadgerlyticsProvider (see Script Installation → React). The provider loads the script and bridges React to the tracker — use useBadgerlyticsTracker() in components and call trackCustomEvent, trackFunnelStep, trackProductView, and the rest on that object. You don't need window.badgerlytics in React code. For shared modules outside components, use callWhenTrackerReady() from the same package (see useful script functions in the docs).
import { useEffect } from 'react';import { useBadgerlyticsTracker } from '@badgerlytics/sdk/react';function dollarsToCents(value) {return Math.round(Number(value) * 100);}export function ProductDetail({ product }) {const tracker = useBadgerlyticsTracker();// One product_view per PDP visit powers view-to-purchase reporting.useEffect(() => {if (!product || !tracker) return;tracker.trackProductView({product_code: product.sku,name: product.name,category: product.category,price: dollarsToCents(product.price), // cents});}, [product, tracker]);return (/* PDP UI */);}
import { useEffect } from 'react';import { useBadgerlyticsTracker } from '@badgerlytics/sdk/react';export function PricingPlanCard({ plan }) {const tracker = useBadgerlyticsTracker();// Fire once per plan the visitor sees on your pricing page.useEffect(() => {if (!plan || !tracker) return;tracker.trackProductView({product_code: plan.plan_code,name: plan.plan_name,category: plan.tier_label,price: plan.price_cents,});}, [plan, tracker]);return (/* plan card UI */);}
Note
useEffect tied to the product or plan id keeps your component predictable.Verify
- Load a PDP or pricing page with test mode enabled and confirm a
product_viewevent in the console. - Open Reports after traffic flows — ecommerce teams use product performance reports; SaaS teams use plan-level conversion views (requires SaaS features on the property).
Payload reference: Ecommerce → Product view, SaaS → Product view, and Tracking script API → Commerce & funnels.
Integrate a cart update event
Snapshot the visitor's cart (or selected plan) after every change so abandoned-cart reporting and plan-level drop-off stay accurate.
When to fire it
- Ecommerce: after add to cart, remove, or quantity change — send the full cart, not just the delta.
- SaaS: after the visitor picks a plan and reaches checkout or review (treat the selected plan as a one-line cart).
In your React app
React SDK — tracker is ready for you
Wrap your app in BadgerlyticsProvider (see Script Installation → React). The provider loads the script and bridges React to the tracker — use useBadgerlyticsTracker() in components and call trackCustomEvent, trackFunnelStep, trackProductView, and the rest on that object. You don't need window.badgerlytics in React code. For shared modules outside components, use callWhenTrackerReady() from the same package (see useful script functions in the docs).
import { useCallback } from 'react';import { useBadgerlyticsTracker } from '@badgerlytics/sdk/react';function buildCartPayload(items) {const products = items.map((item) => ({product_code: item.sku,name: item.name,category: item.category,price: item.priceCents,quantity: item.quantity,}));const subtotal = products.reduce((sum, p) => sum + p.price * p.quantity, 0);if (subtotal <= 0) return null;return {subtotal,currency: 'USD',cart_id: 'session',products,};}export function useCartTracking() {const tracker = useBadgerlyticsTracker();// Call after add, remove, or quantity change — always send the full cart.return useCallback((items) => {const payload = buildCartPayload(items);if (payload && tracker) tracker.trackCartUpdate(payload);},[tracker],);}
import { useBadgerlyticsTracker } from '@badgerlytics/sdk/react';export function CheckoutReview({ plan, customerId }) {const tracker = useBadgerlyticsTracker();function onPlanConfirmed() {if (!tracker || !plan) return;tracker.trackCartUpdate({subtotal: plan.price_cents,currency: 'USD',customer_id: customerId,products: [{product_code: plan.plan_code,name: plan.plan_name,category: plan.tier_label,price: plan.price_cents,quantity: 1,billing_interval: plan.billing_interval,interval_count: plan.interval_count,},],});}return (/* review step UI that calls onPlanConfirmed */);}
Skip empty carts
Verify
- Add or change items in the cart (or confirm a plan) and watch for a
cart_updateevent in test mode. - Check Reports for abandoned-cart or checkout drop-off after the maturity window closes (see docs for the 1-day abandonment window).
More detail: Ecommerce → Cart update, SaaS → Cart update, and abandoned cart considerations.
Integrate a conversion event on your confirmation page
Fire a conversion when an order or subscription actually completes. This is the revenue event that powers conversion rate, A/B test winners, and (for SaaS) new MRR.
On the confirmation page
- Only fire after payment succeeds — typically on the order-confirmation or subscription-success page, not when the user clicks "Pay."
- Pass a stable
order_id(your order or invoice id, not a timestamp) so a page refresh does not double-count. - Include the full line-item breakdown in
products. - Guard with a
useRefor sessionStorage flag so React strict mode or remounts do not fire twice.
In your React app
React SDK — tracker is ready for you
Wrap your app in BadgerlyticsProvider (see Script Installation → React). The provider loads the script and bridges React to the tracker — use useBadgerlyticsTracker() in components and call trackCustomEvent, trackFunnelStep, trackProductView, and the rest on that object. You don't need window.badgerlytics in React code. For shared modules outside components, use callWhenTrackerReady() from the same package (see useful script functions in the docs).
import { useEffect, useRef } from 'react';import { useBadgerlyticsTracker } from '@badgerlytics/sdk/react';function buildConversionPayload(order) {return {order_id: order.id,order_total: order.totalCents,total_tax: order.taxCents,total_shipping: order.shippingCents,total_discount: order.discountCents,currency: 'USD',customer_id: order.customerId,products: order.lineItems.map((item) => ({product_code: item.sku,name: item.name,category: item.category,price: item.priceCents,quantity: item.quantity,})),};}export function OrderConfirmation({ order }) {const tracker = useBadgerlyticsTracker();const trackedRef = useRef(false);useEffect(() => {if (!order || !tracker || trackedRef.current) return;trackedRef.current = true;tracker.trackConversion(buildConversionPayload(order));}, [order, tracker]);return (/* thank-you page */);}
import { useEffect, useRef } from 'react';import { useBadgerlyticsTracker } from '@badgerlytics/sdk/react';export function SubscriptionSuccess({ plan, customerId, subscriptionId, orderId }) {const tracker = useBadgerlyticsTracker();const trackedRef = useRef(false);useEffect(() => {if (!plan || !tracker || trackedRef.current) return;trackedRef.current = true;tracker.trackConversion({order_id: orderId,order_total: plan.price_cents,currency: 'USD',customer_id: customerId,subscription_id: subscriptionId,products: [{product_code: plan.plan_code,name: plan.plan_name,price: plan.price_cents,quantity: 1,category: plan.tier_label,billing_interval: plan.billing_interval,interval_count: plan.interval_count,},],});}, [plan, customerId, subscriptionId, orderId, tracker]);return (/* success page */);}
SaaS: mark recurring revenue
billing_interval and interval_count on each product line so the conversion counts as new MRR rather than a one-time purchase. Trial starts can use order_total: 0with the plan's eventual MRR on the line — see SaaS → Conversion.Verify
- Complete a test purchase and confirm a
conversionevent in test mode. - Open Reports — revenue, conversion rate, and A/B experiment results all read from this event.
Full payloads: Ecommerce → Conversion, SaaS → Conversion, and Tracking script API.
Create an A/B test
Compare two or more variants of a page or component, measure lift vs control, and pick a winner with statistical significance.
In the dashboard
- Open Feature flags in the left nav and click New flag / experiment.
- Set a Display name — the Test token (for example
hero_test) is generated from it and is what you reference in code. - Under Type, choose Experiment (A/B/C).
- Define variations. Experiments require a
controlarm plus one or more variants (v1,v2, …) and a traffic split. - Choose a Winning metric (conversion rate, average order value, or a registered custom event) and optionally set Focus pages or an Audience segment.
- Click Create flag, then Start on the flag detail page when you are ready to bucket visitors.
In your React app
Wrap your app in BadgerlyticsProvider (see Script Installation → React), then branch on the assigned variation:
import { useVariation } from '@badgerlytics/sdk/react';export function Hero() {const variation = useVariation('hero_test', 'control');return variation === 'v1' ? <HeroVariantB /> : <HeroVariantA />;}
Next.js (server-rendered)
Pair the React provider with SSR middleware for Next.js so the first HTML already reflects the assigned variant:
// App Router — requires createBadgerlyticsMiddleware (see docs)import { headers } from 'next/headers';import { getVariation } from '@badgerlytics/sdk/nextjs';export default async function Page() {const h = await headers();const hero = getVariation(h, 'hero_test', 'control');return hero === 'v1' ? <HeroVariantB /> : <HeroVariantA />;}
Read the results
- Open Reports → A/B Testing and select your experiment.
- Compare lift, significance, and revenue side by side. When you have a winner, stop the test and ship the winning variant.
Bandit mode, iterations, focus pages, and more examples (Remix, Astro, Nuxt): Feature Flags and SSR middleware (SDK).
Create an on/off feature flag
Roll a feature out gradually or gate it behind a switch — without running a full A/B comparison or significance analysis.
In the dashboard
- Go to Feature flags and click New flag / experiment.
- Enter a Display name and note the generated Test token (for example
new_pricing_table). - Under Type, choose Feature flag (on/off). Traffic splits between
control(off) andv1(on). - Raise the
v1weight to ramp the feature up (new flags start at 100% off). - Click Create flag, then Start to begin assigning visitors.
In your React app
Use useIsFlagEnabled() for a simple boolean gate:
import { useIsFlagEnabled } from '@badgerlytics/sdk/react';export function PricingSection() {const newPricingEnabled = useIsFlagEnabled('new_pricing_table');return newPricingEnabled ? <PricingV2 /> : <PricingV1 />;}
Next.js (server-rendered)
With middleware installed, read the flag on the server before sending HTML:
import { headers } from 'next/headers';import { isFlagEnabled } from '@badgerlytics/sdk/nextjs';export default async function Page() {const h = await headers();const showNewPricing = isFlagEnabled(h, 'new_pricing_table');return showNewPricing ? <PricingV2 /> : <PricingV1 />;}
Install steps and framework-specific APIs: Feature Flags → Code: React, Code: Next.js, and SSR middleware → Next.js.
When the test is done
Create an audience trait for a test
Register a trait so you can target an A/B experiment to a slice of visitors — for example logged-in users on the pro plan.
Register the trait
- Open Events/Funnel/Audience and switch to the Audience tab.
- Under Add trait, enter a label (for example "Signed in") and pick a type: boolean, string, or number.
- Confirm the generated token (for example
signed_in) and click Add trait.
Send trait values from your app
Update traits whenever the visitor's state changes — login, logout, plan upgrade:
import { useSetTraits } from '@badgerlytics/sdk/react';export function useAuthTraits(user) {const setTraits = useSetTraits();async function syncTraits() {if (user) {await setTraits({ signed_in: true, plan: user.plan });} else {await setTraits({ signed_in: false, plan: null });}}return { syncTraits };}
Note
_bai_traits cookie on login so segment rules apply on the first render — see Audiences → Code examples and SSR middleware (SDK).Target an experiment
- Open an Experiment (A/B/C) under Feature flags (create a new one or edit a draft).
- Scroll to Audience segment and switch from All visitors to a custom rule.
- Add a condition using your trait — for example
signed_inequalstrue, orplanequalspro. - Save and Start the experiment. Only visitors who match the segment at assignment time are bucketed into the test.
Built-in traits like visitor_type and device_type work without registration. Full reference: Audiences and Feature Flags → Audiences.
Geolocate visitors to show or hide content
Read a visitor's IP-derived region from the tracker and conditionally render UI — for example a Texas-only promotion banner.
How location works
Badgerlytics resolves location from the visitor's IP when a new session starts. The result is cached for that session (about 30 minutes) and exposed through getLocation(). Wait for onSessionStartReady before reading — calling too early returns null.
In your React app
React SDK — tracker is ready for you
Wrap your app in BadgerlyticsProvider (see Script Installation → React). The provider loads the script and bridges React to the tracker — use useBadgerlyticsTracker() in components and call trackCustomEvent, trackFunnelStep, trackProductView, and the rest on that object. You don't need window.badgerlytics in React code. For shared modules outside components, use callWhenTrackerReady() from the same package (see useful script functions in the docs).
This example shows a custom component only when regionCode is TX. Call getLocation() on the tracker from useBadgerlyticsTracker():
import { useEffect, useState } from 'react';import { useBadgerlyticsTracker } from '@badgerlytics/sdk/react';export function TexasPromoBanner() {const tracker = useBadgerlyticsTracker();const [showTexasOffer, setShowTexasOffer] = useState(false);useEffect(() => {if (!tracker) return;const apply = () => {const loc = tracker.getLocation();setShowTexasOffer(loc?.regionCode === 'TX');};if (typeof tracker.onSessionStartReady === 'function') {tracker.onSessionStartReady(apply);} else {apply();}}, [tracker]);if (!showTexasOffer) return null;return <TexasOnlyPromo />;}
Other fields
getLocation() also returns country, city, region (full name), and timezone. Use whichever fits your targeting rule.Alternatives
- For experiment bucketing by geography, use an Audience segment with a context condition instead of branching in code — see Audiences → Setup.
- For static sites without React, call
badgerlytics.getLocation()onwindow.badgerlyticsinsideonSessionStartReady— see Tracking script API → Visitor location.
Integrate a subscription webhook
Forward subscription lifecycle changes from your billing system so MRR, churn, expansion, and cancellation-reason reports stay accurate after signup. SaaS properties only — ecommerce orders do not use this event.
In the dashboard
- Enable SaaS features on the property (when creating or editing it under Properties).
- Define your plan catalogue under SaaS Setup → Manage plans so plan codes in webhook payloads match.
- Open Settings → API Keys for your organization and create a secret API key.
subscription_changeis server-only — browser calls are rejected by design. - Point your billing provider's webhook (Stripe, Paddle, etc.) at a route in your backend that forwards qualifying events to Badgerlytics.
Do not forward new subscriptions
trackConversion at checkout. Only forward post-signup changes — cancellations, upgrades, downgrades, reactivations, and trial conversions — or every signup is counted twice.Node.js helper
Add a small module your webhook route can call. Each request needs a fresh unique_event_id; retries with the same id are deduplicated on our side.
import crypto from 'node:crypto';const INGEST_URL = 'https://events.badgerlytics.com/ingestion-api/events';export async function reportSubscriptionChange({propertyId,secretKey,subscriptionId,customerId,changeType,mrrDeltaCents,previousPlanCode = '',newPlanCode = '',cancelReason = '',}) {const body = {property_id: propertyId,event_type: 'subscription_change',unique_event_id: crypto.randomUUID(),session_id: 'srv_' + crypto.randomUUID(),visitor_id: 'srv_' + customerId,event_time: new Date().toISOString(),customer_id: customerId,subscription_data: {subscription_id: subscriptionId,customer_id: customerId,change_type: changeType,mrr_delta_cents: mrrDeltaCents,previous_plan_code: previousPlanCode,new_plan_code: newPlanCode,currency: 'USD',cancel_reason: cancelReason,effective_at: new Date().toISOString(),},};const res = await fetch(INGEST_URL, {method: 'POST',headers: {'Content-Type': 'application/json',Authorization: 'Bearer ' + secretKey,},body: JSON.stringify(body),});if (!res.ok) throw new Error('Badgerlytics ingest failed: ' + res.status);}
Wire it to your billing webhook
Verify your provider's signature, map the event to a change_type, and call the helper. mrr_delta_cents should be the monthly impact in cents (negative for churn, positive for expansion):
// Example: call from your billing provider's webhook handlerimport { reportSubscriptionChange } from './lib/badgerlytics.js';export async function handleBillingWebhook(req, res) {// Verify the provider signature before trusting req.body (Stripe, Paddle, etc.)const event = req.body;if (event.type === 'customer.subscription.deleted') {const sub = event.data.object;await reportSubscriptionChange({propertyId: process.env.BADGERLYTICS_PROPERTY_ID,secretKey: process.env.BADGERLYTICS_SECRET_KEY,subscriptionId: sub.id,customerId: String(sub.customer),changeType: 'cancel',mrrDeltaCents: -15900, // monthly MRR removed, in centspreviousPlanCode: sub.items?.data?.[0]?.price?.lookup_key || '',cancelReason: sub.cancellation_details?.reason || '',});}// Handle upgrade / downgrade / reactivate similarly — see docs for change types.res.sendStatus(200);}
Change types
Use cancel, upgrade, downgrade, renewal, reactivate, trial_started, or trial_converted. Other values are rejected at ingest.
Verify
- Send a test payload with curl (see docs) or trigger a cancel/upgrade in your billing sandbox.
- Open Reports → SaaS lifecycle views after events process (allow a few minutes for ingestion).
- Optional: use the SaaS Setup wizard in the app — it generates a Stripe forwarder snippet pre-filled for your property.
Endpoint, auth, curl example, and adapters for other languages: SaaS → Lifecycle events webhook and SaaS Reporting Setup.
Integrate a usage charge webhook
Forward usage-based and pack revenue from your billing system so Usage Revenue reports stay accurate. Fire a charge when the customer is actually billed — not on every meter tick. SaaS properties only; keep this separate from subscription MRR.
In the dashboard
- Enable SaaS features on the property (when creating or editing it under Properties).
- Define each billable meter, overage, or pack under SaaS Setup → Usage items (or Manage usage items). The
usage_item_codeyou create is the whitelist token your backend must send — unknown codes are rejected at ingest. - Open Settings → API Keys for your organization and create a secret API key (same keys as lifecycle events).
usage_chargeis server-only — browser calls are rejected by design. - Point your billing provider's invoice / charge webhook at a route in your backend that forwards qualifying line items to Badgerlytics.
Do not fold usage into MRR
mrr_delta_cents on subscription_change — keep committed subscription MRR clean and report recognized charges here instead.Charge when billed, not when metered
usage_charge when an invoice is finalized or paid, or when a one-time pack purchase completes. Wait until the billing system has turned usage into a line item with an amount.Node.js helper
Add a small module your webhook route can call. Required on usage_data: a whitelisted usage_item_code and amount_cents (integer). Optional: quantity (defaults to 1), plan_code (when set, Usage Revenue breaks down by plan; when omitted, charges appear under Unaffiliated), plus customer_id / subscription_id for attribution.
import crypto from 'node:crypto';const INGEST_URL = 'https://events.badgerlytics.com/ingestion-api/events';export async function reportUsageCharge({propertyId,secretKey,customerId,subscriptionId = '',usageItemCode,amountCents,quantity = 1,planCode = '',uniqueEventId = null,}) {const body = {property_id: propertyId,event_type: 'usage_charge',unique_event_id: uniqueEventId || crypto.randomUUID(),session_id: 'srv_' + crypto.randomUUID(),visitor_id: 'srv_' + customerId,event_time: new Date().toISOString(),customer_id: customerId,subscription_id: subscriptionId,usage_data: {usage_item_code: usageItemCode,amount_cents: amountCents,quantity,plan_code: planCode,customer_id: customerId,subscription_id: subscriptionId,},};const res = await fetch(INGEST_URL, {method: 'POST',headers: {'Content-Type': 'application/json',Authorization: 'Bearer ' + secretKey,},body: JSON.stringify(body),});if (!res.ok) throw new Error('Badgerlytics ingest failed: ' + res.status);}
Wire it to your invoice webhook
Verify your provider's signature, map metered / overage / pack price keys to your usage item codes, and call the helper. Use a stable unique_event_id per charge (for example the invoice line id) so retries do not double-count:
// Example: forward metered / overage / pack lines from invoice.paidimport { reportUsageCharge } from './lib/badgerlytics-usage.js';// Map Stripe price lookup_key → usage_item_code from SaaS Setup → Usage itemsconst USAGE_ITEM_BY_PRICE_LOOKUP = {// api_overage_monthly: 'api_overage',// token_pack_1000: 'token_pack',};export async function handleInvoiceWebhook(req, res) {// Verify the provider signature before trusting req.bodyconst event = req.body;if (event.type !== 'invoice.paid' && event.type !== 'invoice.finalized') {return res.sendStatus(200);}const invoice = event.data.object;const lines = invoice.lines?.data || [];for (const line of lines) {const lookup = line.price?.lookup_key || '';const usageItemCode = USAGE_ITEM_BY_PRICE_LOOKUP[lookup];if (!usageItemCode) continue; // skip base subscription lines / unknown SKUsif (!line.amount) continue;await reportUsageCharge({propertyId: process.env.BADGERLYTICS_PROPERTY_ID,secretKey: process.env.BADGERLYTICS_SECRET_KEY,customerId: String(invoice.customer || ''),subscriptionId: String(invoice.subscription || ''),usageItemCode,amountCents: line.amount,quantity: line.quantity || 1,// Optional: set plan_code so Usage Revenue breaks down by planplanCode: '',uniqueEventId: String(line.id), // stable id so retries do not double-count});}res.sendStatus(200);}
Verify
- Send a test payload with curl (see docs) or trigger a paid overage / pack invoice in your billing sandbox.
- Open Reports → Usage Revenue after events process (allow a few minutes for ingestion).
- Optional: use the SaaS Setup wizard in the app — it generates a Stripe invoice forwarder snippet pre-filled for your property and usage items.
Endpoint, auth, curl example, and Stripe adapter: SaaS → Usage charge webhook, Usage items, and SaaS Reporting Setup.