Developers

One line of code, zero maintenance.

The tracking snippet loads asynchronously, weighs ~6 KB compressed and doesn't modify any element on your page. It reads orders from your existing GA4 setup; if you prefer, you can send a signed webhook from your server.

1 · tracking snippet<head>
<script async
  src="https://cdn.bannivo.com/bt.js"
  data-site="YOUR_SITE_KEY"></script>

<!-- page type (optional) -->
<body data-bt-page="home">
2 · banner slotHTML or a selector in the dashboard
<div data-bt-slot="home-hero">
  <a href="/campaign/winter">
    <img src="/hero-winter.webp" alt="Winter sale">
  </a>
</div>
<!-- banner image, link and name are read automatically -->
3a · orders: GA4 dataLayerno extra code
// if your site already has it, Bannivo reads it
dataLayer.push({
  event: 'purchase',
  ecommerce: {
    transaction_id: '100045',
    value: 1899.90, currency: 'TRY',
    items: [{ item_id: 'P100', quantity: 1 }]
  }
});
3b · orders: server webhookHMAC-SHA256
BODY='{"order_id":"100045","value":1899.9,
      "visitor_id":"…","items":[…]}'
SIG=$(printf '%s' "$BODY" | openssl dgst -sha256 \
      -hmac "$WEBHOOK_SECRET" | cut -d' ' -f2)

curl -X POST \
  https://cdn.bannivo.com/v1/webhooks/orders/YOUR_SITE_KEY \
  -H "X-BT-Signature: $SIG" -d "$BODY"
Details

What you need to know

What is tracked

Page views, banner discovery (image, link, position), viewable impressions, clicks, add-to-cart and orders. Clicks are sent with sendBeacon before the page changes.

Cookie consent

If Google Consent Mode is present, the analytics_storage signal is followed automatically. For your own consent banner, load with data-consent="false" and call bt.consent(true) on consent.

JS API

bt.getVisitorId() to add the visitor ID to webhooks, bt.track() for manual events, bt.refresh() for new content in single-page apps.

Data quality

Bots and automated traffic are filtered out, data is accepted only from domains you allow, and excessive requests from the same IP are rate-limited.

Performance

The snippet doesn't block page load; observation uses IntersectionObserver and keeps the main thread free.