bananaleaf.ai · Developer docs

Website Analytics

See who visits your website, where they came from, and which ads, links and pages turn into sales. You add one piece of code to your website; if you sell online, your server also tells us about each payment.

View as Markdown
 Visitor ──▶ your website (bl.js) ──▶ in.bananaleaf.ai ──▶ Website Analytics
                                              ▲
 Payment ──▶ your server ──(API key)──────────┘   sale credited to the visit that led to it
  • Website code: one <script> in your page's <head>, with your site key
  • Server calls: https://in.bananaleaf.ai, JSON, an API key from your server only
  • Where to see it: Bananaleaf → Website Analytics (opens in its own tab)

1. Set up in three steps

Step 1 — Add your website

In Bananaleaf, open Website Analytics → Install and add your site: a name ("Main website") and its domain (yoursite.com). You get a site key that starts with bl_pub_.

The site key is public: it can only send visits, never read them, so it is safe in your page source. Only addresses on your domain (and its subdomains) may send with it; localhost always works, for testing.

Step 2 — Paste the code on every page

Put this inside <head> on every page, with your own site key. If your site uses one shared layout or template, paste it there once.

<script async src="https://cdn.bananaleaf.ai/v1/bl.js"></script>
<script>
  window.bl = window.bl || { _q: [] };
  ["init", "fire", "identify", "reset", "consent", "getAnonymousId", "getSessionId"].forEach(function (m) {
    bl[m] = bl[m] || function () { bl._q.push([m, arguments]); };
  });
  bl.init("bl_pub_your_site_key");
</script>

The second part lets your own code call bl.fire(...) before the script has finished loading — those calls wait and go out once it has.

Next.js (App Router): put it in app/layout.js, inside <head>:

import Script from "next/script";

<Script id="bl-init" strategy="beforeInteractive">{`
  window.bl = window.bl || { _q: [] };
  ["init","fire","identify","reset","consent","getAnonymousId","getSessionId"].forEach(function (m) {
    bl[m] = bl[m] || function () { bl._q.push([m, arguments]); };
  });
  bl.init("bl_pub_your_site_key");
`}</Script>
<Script src="https://cdn.bananaleaf.ai/v1/bl.js" strategy="afterInteractive" />

Shopify / WordPress / Webflow: paste the HTML version into the theme's "header code" or "custom code in head" box.

Step 3 — Check it works

Open your website in another tab. On the Install page, Check my install shows each page view within a few seconds. That is all a basic setup needs.

/v1/bl.js always serves the latest 1.x version; browsers may keep a copy for a few hours. To pin one exact build that never changes, use https://cdn.bananaleaf.ai/v1.3.0/bl.js.

2. What is recorded on its own

You write no code for any of this:

What Event name Notes
A page was opened page_view Also on page changes in single-page apps (React, Next.js, Vue)
Time and scroll on that page page_leave Sent when the visitor leaves the page
Where they came from on every event utm_* tags, ad click ids (Google, Meta…), the referring site
Device, system, browser on every event Read from the browser
Country, region, city on every event Worked out by our network; the visitor's IP address is never stored

Each browser gets an anonymous id (v_…) that is kept between visits, so a visitor who comes back next week is the same visitor. Bots and data-centre traffic are marked and left out of your reports.

Consent. You are responsible for your own cookie or consent banner. If a visitor declines, call:

bl.consent("denied");   // counted, but not remembered between pages or visits
bl.consent("granted");  // remembered again

3. Record your own events

Anything else you want to count — a button, a form, a step in a booking — you send with bl.fire:

bl.fire("event_name", { any: "details" }, { value, event_id });
//        name           what happened       optional extras

Examples

// someone opened a product — on the product's own page, with its id
bl.fire("product_viewed", { product_id: "YOGA-01", product_name: "Yoga batch" });

// someone started checkout — with the amount, so the report can show money
bl.fire("checkout_started", { product_id: "YOGA-01" },
        { value: { amount: 1499, currency: "INR" } });

// someone submitted a contact form
bl.fire("lead", { form: "Contact us" });
  • Name: letters, numbers and _, starting with a letter, up to 64 characters. Pick names in the past tense (signup_completed) and keep them the same forever — a renamed event starts a new line in the reports.
  • Details (second argument): anything that helps you break the numbers down later — product, plan, form name. Never put phone numbers, emails, passwords or card details here.
  • value: money, in rupees, not paise (₹1,499 → 1499), with a three-letter currency (default INR). Only this field is added up as revenue — an amount inside the details is not.
  • event_id: your own id for this event (8–64 characters), e.g. the order id. If the same id arrives twice, it is counted once.

Products. Put the same product_id (and product_name) on every event about a product — views, checkout, the sale. What sold uses it to name the product, and the page where its view events happen becomes the product's page for Sold here, even when the sale itself only carries the id.

Which events count as sales or leads. payment_done, purchase, order_completed, lead and signup always appear on the Conversions page. You can mark any other event as a conversion on the Events page.

Tell us who the visitor is

When someone signs in or signs up, call identify with your own id for that person (your customer or user id — not their phone number). Their earlier anonymous visits are joined to them, and their later visits on other devices are too.

bl.identify("cust_812");     // after login or signup
bl.reset();                  // on logout — the browser becomes anonymous again

4. Record sales from your server

A sale fired from the browser is unreliable: the thank-you page gets refreshed (counted twice), the tab is closed early or an ad blocker stops it (lost), and anyone can fire it by hand. Send payments from your server instead, at the moment your system marks the order as paid (your payment webhook).

Get sales right — the checklist

What to send Why
1 event_id = your order id, the same on every retry and from every place that reports it One order counts once
2 value = the amount paid, in rupees Revenue
3 who.anonymous_id and who.session_id, saved from the checkout page (4.2) Credits the sale to the visit, page and ad it came from
4 metadata.items = what was bought: product_id, name, url, price, quantity (4.3) What sold per product, Sold here per page
5 who.user_id = your customer id, if they have one Joins their visits on every device

4.1 Make a server key

In Developers → API keys, create a key and tick Website events. It starts with bl_live_. Keep it on your server, in an environment variable — never in your website or app code. A request that comes from a browser is refused.

A test key (bl_test_…) checks every request fully and records nothing — use it while you build.

4.2 Send the visitor's id with the order

So that a sale can be credited to the visit (and the ad) that led to it, your checkout page sends the browser's anonymous id — and this visit's id — along with the order:

const order = {
  // … your fields …
  bl_anonymous_id: (window.bl && bl.getAnonymousId && bl.getAnonymousId()) || null,
  bl_session_id: (window.bl && bl.getSessionId && bl.getSessionId()) || null,
};

The visit id (bl.getSessionId(), tag 1.3.0 and later) ties the sale to the exact visit it came from, even when the payment is confirmed hours later.

Save it on the order. If it is missing (ad blocker), the order must still go through — the sale is then counted, just not credited to a source.

4.3 Send the payment

curl https://in.bananaleaf.ai/v1/events \
  -H "Authorization: Bearer $BANANALEAF_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "events": [{
      "event_name": "payment_done",
      "event_id": "order_8812",
      "timestamp": "2026-10-25T10:42:00Z",
      "who": { "anonymous_id": "v_3k9q…", "session_id": "s_7c1d…", "user_id": "cust_812" },
      "value": { "amount": 1499, "currency": "INR" },
      "metadata": {
        "items": [
          { "product_id": "YOGA-01", "name": "Yoga batch", "url": "/courses/yoga",
            "price": 1499, "quantity": 1 }
        ],
        "coupon": "DIWALI"
      }
    }]
  }'
Field Required Meaning
event_name yes What happened, e.g. payment_done, refund_issued
event_id recommended Your order id. The same id on every retry — it is counted once
who yes anonymous_id (from 4.2), user_id (your own customer id), or both. Add session_id (from 4.2) to credit the exact visit
timestamp no When it happened (ISO 8601). Default: now
value for money amount in rupees (not paise) + currency
metadata no Details for breaking reports down. No personal data. items = what was bought (below)
site_id no Which of your websites; default is your first one

Up to 500 events per request. A single event without the events wrapper also works.

What was bought. List the products in metadata.items — each with product_id, name, url (the product's page), price (one unit, rupees) and quantity. Conversions → What sold then shows sales per product, and Pages → Sold here credits each sale to its product's page. One product? Top-level product_id, product_name and product_url work too.

Reply: 200 {"ok": true, "accepted": 1, "duplicates": 0, "rejected": []}. A 200 means it is stored safely. Events that were not usable are listed in rejected with the reason; the others are kept.

When the buyer gives a phone or email at checkout, tell us once, so they show as a known person and match their Bananaleaf contact:

curl https://in.bananaleaf.ai/v1/identify \
  -H "Authorization: Bearer $BANANALEAF_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "anonymous_id": "v_3k9q…",
    "user_id": "cust_812",
    "traits": { "phone": "+919876543210", "email": "priya@example.com", "name": "Priya" }
  }'
  • user_id is always your customer id, never the phone (phones change hands).
  • Phone in international format (+91…).
  • Phone and email are used only to match the person to their Bananaleaf contact; they are never stored with events.
  • Do not call it for orders placed on someone else's behalf (a staff member booking for a customer) — it would join the staff member's browser to the customer.

4.5 Retry safely

Do not make your customer wait for this call: send it in the background, after your own database write.

Reply Do
200 Done
429, 5xx, timeout, no network Retry with backoff (e.g. 1 s, 4 s, 15 s). Honour Retry-After
401, 403, 413, 422 Do not retry — fix the request (see Errors)

Because the same event_id is counted once, retrying never double-counts.

5. Mobile apps

There is no mobile SDK, and for most apps you don't need one. A native Android or iOS app already talks to your own server for everything that matters — signups, orders, payments, bookings — so send those from your server with the calls in section 4. That works the same for every platform (Android, iOS, React Native, Flutter) and needs no app update.

Sales and signups — works fully

Your server sends them exactly as in section 4, with your own user_id. It does not matter whether the purchase happened on the website or in the app: revenue and conversions are counted the same way. Add "metadata": { "channel": "app" } so you can tell them apart.

If the same person also uses your website and calls bl.identify("cust_812") there, their website visits and their app purchases join up under one person.

A website inside the app (WebView) — works fully

If the app shows web pages in a WebView, put the normal website code on those pages. They are counted like any website visit.

Screens and taps inside a native app — only if you need them

Screens and taps happen on the phone, not on your server. Two ways to record them:

  • Through your server. The app reports them to your own backend, which sends them on with /v1/events and the person's user_id. Simplest, and the API key stays on your server — but these rows carry no device or city, because the request comes from your server, not the phone.
  • Straight from the app, to the same endpoint the website code uses, with the site key. You get device and city, and you do the work the website code would do for you:
POST https://in.bananaleaf.ai/e
Content-Type: application/json
User-Agent: MyShop/2.1 (Linux; Android 14; Mobile)

{
  "key": "bl_pub_your_app_site_key",
  "sent_at": "2026-10-25T10:42:05Z",
  "batch": [{
    "event": "screen_view",
    "event_id": "0192f4c6-7b2a-7c1e-9d3f-2b8e5a1c4d70",
    "ts": "2026-10-25T10:42:03Z",
    "anonymous_id": "v_8d2f1c…",
    "session_id": "s_41aa…",
    "user_id": "cust_812",
    "url": "app://myshop/checkout",
    "title": "Checkout",
    "screen": "393x852",
    "language": "en-IN",
    "props": { "app_version": "2.1.0" }
  }]
}
  • Add a separate site for the app (e.g. "Android app") with no domain, so its numbers stay apart from the website's and its requests are not refused.
  • Make your own anonymous id on first launch (v_ + a random UUID), save it on the device, and send it with every event. Make a new session_id after 30 minutes without activity.
  • Set a User-Agent like the one above. It is how we know the device: (Linux; Android 14; Mobile) reads as an Android phone, (iPhone; iOS 17.4; Mobile) as an iPhone. Android's default network client (okhttp, also used by React Native) is treated as a bot and those events are hidden from your reports — so this header is not optional on Android.
  • Use url for the screen (app://myshop/checkout) — it shows as /checkout on the Pages report.
  • user_id after the person signs in; leave it out before.
  • Send in small batches (up to 100), and keep unsent events on the device to retry later, so nothing is lost offline.

What does not work for apps yet

  • Which ad led to an install. That needs the Play Store's install referrer (Android) or Apple's ad attribution (iOS). Not built — app installs show as "direct".
  • Automatic screen and session tracking. Without an SDK, the app sends every event itself.

6. Count visitors who use ad blockers (optional)

Some ad blockers stop calls to other companies' addresses, so those visitors are not counted. A developer can fix this by forwarding a path on your own domain to us; the code and the visits then come from your site.

The Install page shows your site's path (something like /k3x9q/) and ready-to-paste setups for Cloudflare Workers, Next.js, Vercel and Nginx. After setting it up, use the second code snippet on that page, which loads the script and sends the visits through your path.

The forwarder must pass on each visitor's address and, where it can, their location — the setups on the Install page already do. Without that, every visit looks like it comes from your own server.

7. Rules for clean numbers

  • Money is value, in rupees. ₹499 is 499. If your system stores paise, divide by 100. Send the sale's real currency.
  • One sale = one event_id. Use the order id, and the same id on every retry. If your website still fires the sale too, give it the same event_id — the server's copy wins and it is counted once.
  • Send the sale from the server, not the thank-you page.
  • Say what was bought. metadata.items with a product_id and the product's page url — without it, What sold and Sold here stay empty.
  • Send the visit's id. Save bl.getSessionId() with the order at checkout and send it as who.session_id; the sale is then credited to that exact visit.
  • user_id is your customer id. Never a phone, email or a placeholder like "guest" or "0" (those are ignored).
  • No personal data in event details. Phones and emails go only in identify. Never send passwords, OTPs or card details anywhere.
  • Keep event names stable. Decide them once; renaming splits the reports.
  • Test on a separate site. localhost is always allowed, so tests with your real site key land in your real numbers. Add a "Dev" site for local work.

8. Errors

Code Means Fix
401 The key is missing, wrong or revoked Check the key and which account it belongs to
403 Website: the page's domain is not on the site's allowed list. Server: the key lacks Website events, or the call came from a browser Add the domain on the Install page / tick the scope / call from your server
413 Too large (website: 100 events or 512 KB; server: 500 events or 1 MB) Send smaller batches
422 The JSON is not in the expected shape Read the detail in the reply
429 Too many requests Wait for Retry-After, then retry
503 We could not store it just now Retry with backoff

Reasons you may see in rejected (server calls): bad_event_name, bad_event_id, who_required (no usable anonymous_id or user_id), bad_timestamp, bad_value, bad_currency, bad_metadata, metadata_too_large, unknown_site.

9. Something is not showing up

  • No page views at all. Open your site, then the browser's developer tools → Network. You should see bl.js load (200) and requests to in.bananaleaf.ai/e return 200. A 403 there means your domain is not on the site's allowed list.
  • Visits show, sales don't. Check your server got a 200 with accepted: 1. A sale appears on Conversions within about a minute.
  • Sales show with ₹0. The amount was sent in the details instead of value, or in paise. See Rules.
  • Sales show as "unattributed". The order had no bl_anonymous_id and the buyer had never been identified. Check step 4.2 on your checkout page.
  • The same sale counted twice. The browser and the server sent it with different event_ids. Use the same one, or stop sending it from the browser. (A thank-you page that fires on every reload does the same — give it the order id.)
  • What sold says "named no product". The sale carries no metadata.items and no product_id. See the checklist in section 4.
  • What sold shows an id instead of a name. Send name in items (or product_name), or fire a view event with product_id + product_name.
  • Sold here is empty, or points at a checkout page. Send each item's url (the product page's path, e.g. /courses/yoga).
  • App events missing. Check the User-Agent header (see Mobile apps).

BananaLeaf AI (bananaleaf.ai) is the WhatsApp AI sales agent and CRM for Indian businesses, built by Krishav Technologies Pvt. Ltd., Hyderabad.© 2026 bananaleaf.aiPrivacy PolicyTerms & ConditionsRefund Policy