[{"data":1,"prerenderedAt":318},["ShallowReactive",2],{"page-cookieless-unique-visitor-counts":3,"navigation":228},{"id":4,"title":5,"body":6,"date":217,"description":218,"draft":219,"extension":220,"hideYear":219,"meta":221,"navigation":222,"path":223,"readingTime":224,"seo":225,"stem":226,"__hash__":227},"articles/articles/cookieless-unique-visitor-counts.md","Counting unique visitors without using cookies, UIDs or fingerprinting.",{"type":7,"value":8,"toc":208},"minimark",[9,13,18,21,24,28,31,34,38,41,49,59,62,68,71,77,86,97,101,108,111,117,120,126,129,135,139,142,145,154,157],[10,11,12],"p",{},"Building a web analytics service without cookies poses a tricky problem: How do you distinguish unique visitors?",[14,15,17],"h3",{"id":16},"how-do-you-distinguish-unique-visits-without-using-cookies","How do you distinguish unique visits without using cookies?",[10,19,20],{},"For most cookie-based solutions, it's easy: Store a unique identifier (UID) in a cookie on your computer, so we can identify you when you return. But if there's no cookie, there's no UID... or so you'd think.",[10,22,23],{},"Many privacy-focused analytics services will generate and store a UID on the server instead of saving it in a cookie - based on a hash of your User Agent, IP, Location, Date etc. This \"fingerprint\" is stored in a database and checked every time you visit the site to see if you've visited it before. To improve privacy, it can be washed from the database once it's served it's purpose e.g. on a daily basis. Some analytics services simply rely on the page referrer being on the same domain as your current URL.",[14,25,27],{"id":26},"privacy-issues","Privacy issues",[10,29,30],{},"Previous experiments at Normally have revealed that linking data points in a database in any way, such as with a UID,  has the potential to reveal someone's identity. Connecting just few data points such as the city, time and visited pages could tell us more than we need to know about that visitor and sometimes lead to their identification in the real world.",[10,32,33],{},"While building Cabin, we didn't want to use UIDs at all, and the referrer couldn't be relied upon in all browsers (some browsers and extensions can hide it). So we came up with a different approach.",[14,35,37],{"id":36},"the-solution","The solution",[10,39,40],{},"Our solution doesn't require a database or anything stored on the server side. It even works in the oldest browsers. Here's how:",[10,42,43,44,48],{},"When the browser pings our server from a website for the first time, we send back a response with a header set to ",[45,46,47],"code",{},"Cache-Control: no-cache",", telling the browser to store the request in its cache but revalidate it with the origin server before each use. But most importantly, we send a header which is a date set to the beginning of each day:",[50,51,56],"pre",{"className":52,"code":54,"language":55},[53],"language-text","last-modified: Wed, 30 Nov 2022 00:00:00 GMT\n","text",[45,57,54],{"__ignoreMap":58},"",[10,60,61],{},"From now on, every time this request is made again, the server receives the date and adjusts it by one second, and returns it to the browser:",[50,63,66],{"className":64,"code":65,"language":55},[53],"last-modified: Wed, 30 Nov 2022 00:00:01 GMT\n",[45,67,65],{"__ignoreMap":58},[10,69,70],{},"This way, the server can calculate the distance in seconds since midnight to give us a visit count.",[72,73,74],"blockquote",{},[10,75,76],{},"The visit count is encoded within the date stored in the cached request on the visitor's machine.",[10,78,79],{},[80,81],"img",{":sizes":82,"alt":58,"format":83,"src":84,"style":85},"400px sm:800px","webp","/img/date-modified.png","border:1px solid #ccc",[10,87,88,89],{},"See this in action here 👉 ",[90,91,96],"a",{"href":92,"rel":93,"target":95},"https://lastmodified.normally.com/",[94],"nofollow","_blank","demo",[14,98,100],{"id":99},"counting-bounces-too","Counting bounces too",[10,102,103,104,107],{},"A bounce is when a visitor lands on your page and leaves that same page without visiting another page on your site. We can distinguish unique visitors (an empty ",[45,105,106],{},"last-modified","), but we can also leverage our counter to sum bounces during the day by assuming a unique visit is a bounce until we receive the second request. Following requests are then ignored.",[10,109,110],{},"First visit:",[50,112,115],{"className":113,"code":114,"language":55},[53],"visits  uniques bounces\n+1      +1      +1\n",[45,116,114],{"__ignoreMap":58},[10,118,119],{},"Second visit:",[50,121,124],{"className":122,"code":123,"language":55},[53],"visits  uniques bounces\n+1      0       -1\n",[45,125,123],{"__ignoreMap":58},[10,127,128],{},"Subsequent visits:",[50,130,133],{"className":131,"code":132,"language":55},[53],"visits  uniques bounces\n+1      0       0\n",[45,134,132],{"__ignoreMap":58},[14,136,138],{"id":137},"conclusion","Conclusion",[10,140,141],{},"Although these headers aren't intended for this, we are only updating and observing them on the server in line with browser standards.",[10,143,144],{},"This is great for privacy as we don't need to use cookies, IP addresses, fingerprinting or unique identifiers. In our tests, this method proved durable enough to be the most reliable method of counting unique visitors without using cookies.",[10,146,147,148,153],{},"We use this method in ",[90,149,152],{"href":150,"rel":151,"target":95},"https://withcabin.com",[94],"Cabin",", our Privacy-first, carbon-conscious web analytics. Cabin is also compliant with all privacy laws. It's simple, lightweight, and enables you to track carbon emissions across your site. It's a great alternative to Google Analytics. Sign up for a free account to see it in action.",[155,156],"hr",{},[158,159,160,166],"callout",{},[10,161,162],{},[163,164,165],"strong",{},"Footnotes",[167,168,169,179,187,194],"ul",{},[170,171,172,173,178],"li",{},"View a gist of this demo as an express server ",[90,174,177],{"href":175,"rel":176,"target":95},"https://gist.github.com/mulhoon/b08e8e62932a75bc655286af6beff400",[94],"here",".",[170,180,181,182,178],{},"The possibility of leveraging the if-modified-since header was mentioned in 2008 by ",[90,183,186],{"href":184,"rel":185,"target":95},"https://web.archive.org/web/20100115062236/http://use.perl.org/~bart/journal/36598",[94],"Bart Lateur",[170,188,189,190,178],{},"Read more about Last-Modified on MDN Docs ",[90,191,177],{"href":192,"rel":193,"target":95},"https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Last-Modified",[94],[170,195,196,197,202,203,178],{},"This article first appeared on Normally ",[90,198,201],{"href":199,"rel":200,"target":95},"https://notes.normally.com/cookieless-unique-visitor-counts/",[94],"Notes",". You can read comments on ",[90,204,207],{"href":205,"rel":206,"target":95},"https://news.ycombinator.com/item?id=33802683",[94],"HackerNews",{"title":58,"searchDepth":209,"depth":209,"links":210},2,[211,213,214,215,216],{"id":16,"depth":212,"text":17},3,{"id":26,"depth":212,"text":27},{"id":36,"depth":212,"text":37},{"id":99,"depth":212,"text":100},{"id":137,"depth":212,"text":138},"2022-11-30","on withcabin.com",false,"md",{},true,"/articles/cookieless-unique-visitor-counts",5,{"title":5,"description":218},"articles/cookieless-unique-visitor-counts","7Lum1-eCSaHc5w-J13W_lQXweqkrPyS2vju2p6iYDms",[229],{"title":230,"path":231,"stem":232,"children":233,"page":219},"Articles","/articles","articles",[234,240,246,252,258,264,270,276,282,288,289,295,301,307,312],{"title":235,"path":236,"stem":237,"description":238,"date":239},"Still haven't found what you're looking for?","/articles/ai-search","articles/ai-search","AI-powered search is transforming web search.","2024-10-31",{"title":241,"path":242,"stem":243,"description":244,"date":245},"Big Emoji","/articles/big-emoji","articles/big-emoji","Building an app for my kids.","2024-06-23",{"title":247,"path":248,"stem":249,"description":250,"date":251},"It probably won’t be you","/articles/it-probably-wont-be-you","articles/it-probably-wont-be-you","An interactive exploration of our lottery instincts.","2024-06-11",{"title":253,"path":254,"stem":255,"description":256,"date":257},"My memory is shot","/articles/my-memory-is-shot","articles/my-memory-is-shot","Discovering the link between writing and memory.","2024-04-28",{"title":259,"path":260,"stem":261,"description":262,"date":263},"Quotes","/articles/quotes","articles/quotes","A collection of quotes I've heard and written down at some point in my life.","2024-03-29",{"title":265,"path":266,"stem":267,"description":268,"date":269},"TikTok goes your clock","/articles/tiktok-goes-your-clock","articles/tiktok-goes-your-clock","What I learned from quitting TikTok","2024-03-26",{"title":271,"path":272,"stem":273,"description":274,"date":275},"User testing fail","/articles/user-testing-fail","articles/user-testing-fail","Attempting to think like a kid when designing a kids app.","2024-03-20",{"title":277,"path":278,"stem":279,"description":280,"date":281},"George Harrison's hands","/articles/george-harrisons-hands","articles/george-harrisons-hands","You can replicate the setup, but not the experience.","2023-01-17",{"title":283,"path":284,"stem":285,"description":286,"date":287},"Things I learned in 2022","/articles/things-i-learned-in-2022","articles/things-i-learned-in-2022","Aside from all the emergence of AI.","2022-12-20",{"title":5,"path":223,"stem":226,"description":218,"date":217},{"title":290,"path":291,"stem":292,"description":293,"date":294},"Hang up.","/articles/hang-up","articles/hang-up","Steps for for avoiding scam calls.","2022-05-06",{"title":296,"path":297,"stem":298,"description":299,"date":300},"The “yes” transaction","/articles/yes","articles/yes","Why my contact lens service is my favourite transaction.","2022-01-04",{"title":302,"path":303,"stem":304,"description":305,"date":306},"One line of code","/articles/one-line-of-code","articles/one-line-of-code","How I built an API for the London 2012 Olympics with one line of code.","2021-05-11",{"title":308,"path":309,"stem":310,"description":311,"date":306},"The potential energy savings of deprecating Cloudflare's cfduid cookie.","/articles/the-deprecated-cookie","articles/the-deprecated-cookie","How removing a tiny cookie can have a big impact on carbon emissions.",{"title":313,"path":314,"stem":315,"description":316,"date":317},"Never launching","/articles/never-launching-products","articles/never-launching-products","It's not just about the launch, it's about the process.","2021-04-09",1772188377383]