# Cookieless Tracking

> Plaintext version for LLMs. Canonical HTML: https://askbowtie.com/learn/tracker/cookieless-tracking/

askbowtie does not set cookies on your visitors' browsers. Not first-party, not third-party, not "analytics" cookies. This is by design, not by accident.

This page explains how it works, why it matters, and how to keep your integration cookie-free when you add server-side conversion tracking.

---

## How sessions work without cookies

The tracker stores a random session ID in the browser's **localStorage** under the key `bowtie_session`. This is fundamentally different from a cookie:

|   | Cookie | localStorage |
| --- | --- | --- |
| **Sent to server automatically** | Yes, on every HTTP request | No — never sent unless you explicitly read and pass it |
| **Readable by other domains** | Third-party cookies can be | No — same-origin only |
| **Requires consent banner (EU)** | Usually yes, for analytics | No — not classified as a tracking cookie |
| **Survives incognito** | No | No |
| **Can be blocked by browsers** | Increasingly, yes | Rarely |

The session ID is random, anonymous, and not linked to any personal identity. It expires after 30 days or 30 minutes of inactivity, whichever comes first.

---

## What this means for compliance

Because the tracker sets no cookies and collects no personal data:

- **No cookie consent banner required** for the askbowtie script
- **GDPR-friendly** — anonymous error and performance monitoring doesn't require consent under most interpretations
- **ePrivacy Directive** — localStorage for anonymous analytics generally falls outside cookie consent requirements
- **Removing the tracker** leaves no trace — no residual cookies, no orphaned data

If your site already has a cookie banner for other tools (Google Analytics, ad pixels), askbowtie doesn't need to be listed in it.

---

## Server-side conversions without cookies

When you need to track conversions server-side (payment webhooks, form processing, CRM events), you need to get the session ID from the browser to your backend. **You do not need a cookie for this.**

### The right way: pass it explicitly

Read the session ID from localStorage and include it in whatever triggers your server-side code:

**Forms and checkouts** — hidden field:

```
<input type="hidden" name="bowtie_session" id="bowtie_session">
<script>
document.getElementById('bowtie_session').value =
  localStorage.getItem('bowtie_session') || '';
</script>
```

**AJAX / SPAs** — include in the request body:

```
const sessionId = localStorage.getItem('bowtie_session');

fetch('/api/checkout', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ ...formData, bowtie_session: sessionId })
});
```

**Stripe / payment processors** — pass it as metadata when creating the checkout session, then read it back in the webhook. The example is Stripe's Node SDK, but every processor has an equivalent metadata field:

```
// When creating the checkout session (server-side)
const checkout = await stripe.checkout.sessions.create({
  metadata: { bowtie_session: req.body.bowtie_session || '' },
  // ... line items, success_url, etc.
});

// In the webhook handler
const sessionId = event.data.object.metadata.bowtie_session || null;
```

**Client-side only** — if you can detect the conversion in JavaScript, no server-side code needed:

```
bowtie.converted('purchase', { value: 99.00 });
```

### What not to do

Do not mirror `bowtie_session` from localStorage to a cookie. This pattern has appeared in some integrations:

```
// Don't do this — it defeats the cookieless design
setInterval(() => {
  const s = localStorage.getItem('bowtie_session');
  if (s) document.cookie = `bowtie_session=${s};path=/;max-age=86400;SameSite=Lax`;
}, 1000);
```

This creates a cookie that:

- Gets sent to your server on every HTTP request (bandwidth waste)
- May require a cookie consent banner
- Polls every second (unnecessary CPU work)
- Undermines the "no cookies" claim you can make about your site

Every use case this cookie solves has a better alternative above.

---

## Decision guide

| Your situation | Solution | Cookie? |
| --- | --- | --- |
| Track conversions in JavaScript (thank-you page, SPA) | `bowtie.converted()` | No |
| Form submission triggers a conversion server-side | Hidden `` field | No |
| AJAX call triggers a conversion server-side | Include in request body | No |
| Payment webhook (Stripe, PayPal) | Pass as checkout metadata | No |
| Server-side error or guardrail tracking | Read from request (form/AJAX) | No |
| Backend-only event with no browser context | Send without session ID — event still tracked, just not linked to a session | No |

---

## Verifying your integration is cookie-free

Open your browser's DevTools, go to **Application > Cookies**, and check your domain. You should see:

- Your app's own session cookie, if it sets one — that's your framework, not askbowtie
- **No** `bowtie_session` cookie
- **No** `bowtie_debug` cookie (unless you manually enabled debug mode)

Under **Application > Local Storage**, you should see:

- `bowtie_session` — the anonymous session ID
- `bowtie_uid` — present only if you call `bowtie.identify()` to connect a visitor's sessions (see the JavaScript API). Sites that never call it won't have this key.

These localStorage entries are never sent to any server automatically.

---

## Related

- [Installing the Tracker](/docs/install/) — Setup instructions
- [Server-Side Conversions](/docs/) — Full server-side tracking guide
- [Privacy Policy](/privacy/) — What data is collected and how it's handled
- [Security & Data Privacy](/learn/tracker/security-and-csp/) — Technical security details
