How to stop Google Tag Manager being hacked

In two high-profile data breaches this year – at Ticketmaster and British Airways – over half a million credit cards were stolen via a compromised script inserted on the payment pages. Google Tag Manager is a powerful tool which enables you to insert any script you want onto pages of your website, but that power can used against you by hackers if you're not careful – and below we’ll look at how to stop GTM being a security risk on your payment pages. Firstly, how did the hackers get the card details from these sites? And how is it relevant to GTM on your site? Security firm RiskIQ has traced the breach to a compromised Javascript file which skimmed the card details from the payment form. So when a user entered their credit card number and security code on (or their mobile app) those details were posted to a third party server, unknown to British Airways or the customer. This is a high-scale equivalent of placing a skimming devices on an ATM, which reads one card at a time. In Ticketmaster’s hack the script was one loaded from a chatbot vendor on their site, Inbenta. Inbenta claims not even to have been aware the script was used on payment pages. The changes to the script were subtle: not breaking any functionality, and in BA’s case using a domain ‘’ which looked somewhat authentic. To protect your site against a similar attack you obviously need to lock down accounts used by your developers to change scripts in the page source code, but you also need to secure GTM – which can be used to deploy such scripts. We have a few rules at Littledata to help reduce risks in using tag management on payment pages: 1. Use pixels over custom JavaScript tags on payment pages You probably need a few standard tags, such as Google Analytics, on payment pages but try to avoid any custom scripts which could possibly skim card details. Many non-standard tags use JavaScript only to create the URL of a tracking pixel – and it is much safer (and faster) to call the tracking pixel directly. Contact the vendor to find out how. (Littledata's Shopify app even removes the need to have any script on the payment pages, by hooking into the order as it's registered on Shopify's servers) 2. Avoid loading external JavaScript files in GTM Many vendors want you to load a file from their server (e.g. from GTM, so they can update the tracking code whenever they want. This is flexible for them, but risky for you. If the vendor gets hacked (e.g. with Inbenta above) then you get compromised. It’s less risky to embed that script directly in GTM, and control version changes from there (although a fraction slower to load the page). Of particular risk is embedding a tag manager within a tag manager – where you are giving the third party rights to publish any other scripts within the one tag. Don’t do that! 3. Lock down Edit and Publish rights on GTM Your organisation probably has a high turnover of contract web developers and agencies, so have you checked that only the current staff or agencies have permission to edit and publish? It's OK to have external editors use 'workspaces' for version control in GTM, but ideally someone with direct accountability to your company should check and Publish. 4. Blacklist custom JavaScript tag on the payment pages You can set a blacklist from the on-page data layer to prevent certain types of tags being deployed on the payment pages. If you have a GTM container with many users, this may be more practical that step 3. 5. Remove tags from old vendors There are many thousands of marketing tools out there, and your company has probably tried a few. Do you remove all the tags from vendors when you stop working with them? These are most at risk of being hacked. At Littledata we run a quarterly process for marketing stakeholders opt-in tags they still need for tracking or optimisation. 6. Ensure all custom JavaScript tags are reviewed by a developer before publishing It can be hard to review minimised JavaScript libraries, but worth it for payment pages if you can’t follow rules 1 and 2. If you’re still worried, you can audit the actual network requests sent from payment pages. For example, in Chrome developer tools, in the 'Network' tab, you can inspect what requests sent out by the browser and to what servers. It’s easy for malicious code to hide in the patchwork of JavaScript that powers most modern web experiences, but what is harder to hide is the network requests made from the browser to external servers (i.e. to post the stolen card information out). This request to Google Analytics is fine, but if the domain of a request is dubious, look it up or ask around the team. Good luck, and keep safe with GTM!


Categorising websites by industry sector: how we solved a technical challenge

When Littledata first started working with benchmark data we found the biggest barrier to accuracy was self-reporting on industry sectors. Here’s how we built a better feature to categorise customer websites. Google Analytics has offered benchmarks for many years, but with limited usefulness since the industry sector field for the website is often inaccurate. The problem is that GA is typically set up by a developer or agency without knowledge or care about the company’s line of business - or understanding of what that industry sector is used for. To fix this problem Littledata needed a way to categorise websites which didn’t rely on our users selecting from a drop-down list. Google Analytics has offered benchmarks for many years, but with limited usefulness since the industry sector field for the website is often inaccurate. The first iteration: IBM Watson NLP and a basic taxonomy Our first iteration of this feature used a pre-trained model as part of IBM Watson’s set of natural language APIs. It was simple: we sent the URL, and back came a category according to the Internet Advertising Bureau taxonomy. After running this across thousands of websites we quickly realised the limitations: It failed with non-English websites It failed when website homepage was heavy with images rather than text It failed when the website was rendered via Javascript Since our customer base is growing most strongly outside the UK, with graphical product lists on their homepage, and using the latest Javascript frameworks (such as React), the failure rate was above 50% and rising. So we prioritised a second iteration. The second iteration: Extraction, translation and public APIs The success criteria was that the second iteration could categorise 8 sites which the first iteration failed with, and should go on to be 80% accurate. We also wanted to use mainly public APIs, to avoid maintaining code libraries, so we broke the detection process into 3 steps: Extracting meaningful text from the website Translating that text into English Categorising the English text to an IAB category and subcategory The Watson API seemed to perform well when given sufficient formatted text, at minimal cost per use, so we kept this for step 3. For step 2, the obvious choice was Google Translate API. The magic of this API is that it can detect the language of origin (with a minimum of ~4 words) and then provide the English translation. That left us focussing the development time on step 1 - extracting meaningful text. Initially we looked for a public API, and found the Aylien article extraction API. However, after testing it out on our sample sites, it suffered from the same flaws as the IBM Watson processing: unable to handle highly graphical sites, or those with Javascript rendering. To give us more control of the text extraction, we then opted to use a PhantomJS browser on our server. Phantom provides a standard function to extract the HTML and text from the rendered page, but at the expense of being somewhat memory intensive. Putting the first few thousand characters of the website text into translation and then categorisation produced better results, but still suffered from false positives - for example if the text contained legal-ease about data privacy it got categorised as technical or legal. We then looked at categorising the page title and meta description, which any SEO-savvy site would stuff with industry language. The problem here is that the text can be short, and mainly filled with brand names. After struggling for a day we hit upon the magic formula: categorising both the page title and the page body text, and looking for consistent categorisation across the two. By using two text sources from the same page we more than doubled the accuracy, and it worked for all but one of our ‘difficult’ websites. This hold-out site - - has almost no mention of its main product (diapers, or nappies), which makes it uniquely hard to categorise. So to put it all the new steps together, here’s how it works for our long-term enterprise client's French-language site. Step 1: Render the page in PhantomJS and extract the page title and description Step 2: Extract the page body text, remove any cookie policy and format Step 3: Translate both text strings in Google Translate Step 4: Compare the categorisations of the title vs page body text Step 5: If the two sources match, store the category I’m pleased that a few weeks after launching the new website classifier we have found it to be 95% accurate. Benchmarking is a core part of our feature set, informing everything that we do here at Littledata. From Shopify store benchmarks to general web performance data, the improved accuracy and deeper industry sector data is helping our customers get actionable insights to improve their ecommerce performance. If you’re interested in using our categorisation API, please contact us for a pilot. And note that Littledata is also recruiting developers, so if you like solving these kind of challenges, think about coming to join us!


Six challenges in developing a Shopify integration

At the start of 2017 Littledata released its first Shopify app. A year on, here are my observations on the technical challenges we’ve overcome. This week we're at Shopify Unite in Toronto, and it's no surprise that their app ecosystem continues to grow. We chose Shopify as our first platform partner due to their open APIs, quality documentation and enthusiasm from other developers. Much of that has been as expected, but to help all of you looking to build your own Shopify app I’ll share some of our learnings on the hidden challenges. Littledata's Shopify app makes it a one-click process for stores to set up for Enhanced Ecommerce tracking in Google Analytics, and then get actionable insights based on the Google Analytics data. It has to hook into Shopify orders and products, as well and modify the store's theme and process ongoing transactions. 1. Handling re-installs gracefully The great advantage of Shopify’s app store over, say, Magento marketplace, is that any store admin can install and pay for an app with a couple of clicks. The disadvantage is that stores can be as quick to uninstall as install. Store admins may start, realise they don’t have permissions, time or energy to continue and roll back to try again later in the day. Since our app inserts a snippet into the store’s theme layout (see point two below), uninstalling removes the web-hooks we set up but does not remove the inserted snippet. When a store re-installs our app has to work out what state they were in when they uninstalled (audit, test mode or live), whether the script snippet is still there and what settings have been changed in the meantime. It took us a few months to get a handle on all the possible user flows, and we’ve found automated end-to-end tests to really speed up running through the different scenarios. In our Meteor framework we use Chimp [link] to run tests through Selenium on localhost and on our staging server. We've also found it essential to track our own stats of 'installs + activations' (including the date of first install and time to finally uninstall) rather than relying on the Shopify Partner stats of uninstalls and installs, which can hide the detail in between. 2. Working with script tags The other side-effect of making apps easy to install is that you can assume the early-adopter stores who will try your app already have lots of other installs. Shopify recommends using the Script Tag API to handle scripts linked to the app, so that when a store uninstalls your app it also removes any client-side scripts from the store. Unfortunately, in early tests we found the load latency to be unacceptably high: on some stores, only 50% of the page load events were getting fired before the user moved off the page. So plan B was add a snippet to the store theme, and then load this snippet at the top of the <body> element on all the layout templates. This has worked much more predictably, except when theme editors remove the snippet reference without enquiring what the Littledata app does (see our fifth challenge). 3. Charge activation vs authorisation Now a very simple gotcha. In our first month we had around 60 installs at a flat price of $20/month, but apparently no revenue. After investigation we found we had not activated the recurring charges after the store admin had authorised them. Doh! We're still not sure why an app would want to have authorised charges which are not activated -- seems like over-engineering on Shopify's side -- but luckily it was easy to correct without asking for more user permissions. 4. Tracking adds-to-cart The first version of our app tried to run the script when customers got to the ‘/cart’ page of a store. The problem here is that many stores have AJAX or ‘mini’ carts where customers can checkout without every visiting the cart page. We looked to trigger the script before the user got to the cart the page, but this appeared to run too many risks of interfering with the customer actually adding the item. Our final solution has been to poll the Shopify store for the current cart, and see if products have been added (or removed) since we last polled (and stored the previous cart contents in memory). This is somewhat inefficient, as it requires continuous network activity to grab the cart JSON from Shopify, but we’ve reduced the network requests to one every 4 seconds – judging that customers are very unlikely to add a product and checkout in less than 4 seconds. This cart polling has proved more reliable across different store templates. 5. Integrating with other Shopify apps I mentioned that early-adopter stores tend to have lots of other apps: and those apps have loyal customers who push to make Littledata's app to work their chosen app (not just vanilla Shopify). The challenge is that most of these app development companies run a very Agile process, constantly changing how their app works (hopefully to improve the experience for store owners). An integration that worked a few months ago may no longer work. We've found the best solution to be open developer-to-developer communications, via a Slack guest channel. Having the developers implementing the features on each side talk to each other really cuts down the delays caused by a well-meaning project manager slightly misinterpreting the requirement. 6. Handling ongoing updates As tested improved client-side tracking scripts, we needed to update the script in the store theme (see point 2 above). This creates a small risk for the store, as there is no UAT or test environment for most stores to check before going live with the new script. The store theme may also get edited, creating new layout templates where the Littledata snippet is not loaded. In the first version of our app we tried to update and re-insert the latest Littledata snippet automatically on a weekly cycle. However, once we reached hundreds of active installs this became unmanageable and also opaque for the store admins. In the latest version we now allow store admins to UPGRADE to the latest script, and then we check all the correct Google Analytics events are being fired afterwards. Giving the end user control of updates seems a better way of maintaining trust in our brand and also removing risk: if the update goes wrong, it’s quicker for us to alert the store owner on how to fix. Conclusion I’m still sure we made the right choice with Shopify as a platform, as their APIs, partner support and commercial traction are all number one in the ecommerce world. But I hope that by sharing some of the hidden challenges in developing Shopify integrations, we can all build better apps for the community. Have you built something for the Shopify app store? Are there development problems you’ve encountered which I haven’t shared here? PS. Are you a developer interested in joining an innovative analytics company? We're hiring in multiple locations!


Littledata at Codess

I was proud to be invited by Microsoft to speak at their Codess event in Bucharest last week to encourage women in software. We talked about how Littledata uses Meteor, Node and MongoDB to run scalable web applications; slightly controversial because none of these are Microsoft technologies! The event was well run and well attended, so I hope it inspires some of the attendees to start their own projects...or to join Littledata (we're hiring).


Cross Domain tracking for Eventbrite using Google Tag Manager (GTM)

Are you using Eventbrite for event registrations? And would you like to see the marketing campaign which drove that event registration correctly attributed in Google Analytics? Then you've come to right place! Here is a simple guide to adding a Google Tag Manager tag to ensure the correct data is sent to Eventbrite to enable cross-domain tracking with your own website. Many thanks to the Lunametrics blog for their detailed solution, which we have adapted here for GTM. Before this will work you need to have: links from your site to Eventbrite (including or the Universal Analytics tracking code on both your site and your Eventbrite pages. only have one GA tracking code on your own site - or else see the Lunametrics article to cope with this 1. Create a new tag in GTM Create a new custom HTML tag in GTM and paste this script: [code language="javascript"] <script> (function(document, window) { //Uses the first GA tracker registered, which is fine for 99.9% of users. //won't work for browsers older than IE8 if (!document.querySelector) return; var gaName = window.GoogleAnalyticsObject || "ga" ; // Safely instantiate our GA queue. window[gaName]=window[gaName]||function(){(window[gaName].q=window[gaName].q||[]).push(arguments)};window[gaName].l=+new Date; window[gaName](function() { // Defer to the back of the queue if no tracker is ready if (!ga.getAll().length) { window[gaName](bindUrls); } else bindUrls(); }); function bindUrls() { var urls = document.querySelectorAll("a"); var eventbrite = /eventbrite\./ var url, i; for (i = 0; i < urls.length; i++) { url = urls[i]; if (eventbrite.test(url.hostname) === true) { //only fetches clientID if this page has Eventbrite links var clientId = getClientId(); var parameter = "_eboga=" + clientId; // If we're in debug mode and can't find a client if (!clientId) { window.console && window.console.error("GTM Eventbrite Cross Domain: Unable to detect Client ID. Verify you are using Universal Analytics."); break; return; } = ? + "&" + parameter : "?" + parameter; } } } function getClientId() { var trackers = window[gaName].getAll(); return trackers[0].get("clientId"); } })(document, window); </script> [/code]   2. Set the tag to fire 'DOM ready' Create a new trigger (if you don't have a suitable one) to fire the tag on every page at the DOM ready stage.  We need to make sure the Google Analytics tracker has loaded first. 3. Test the marketing attribution With the script working you should see pageviews of the Eventbrite pages as a continuation of the same session. You can test this by: Opening the 'real time' reporting tag in Google Analytics, on an unfiltered view Searching for your own site in Google Navigating to the page with the Eventbrite link and clicking on it Looking under the Traffic Sources report and checking you are still listed as organic search after viewing the Eventbrite page Need more help? Comment below or get in touch!   Get Social! Follow us on LinkedIn, Twitter, and Facebook and keep up-to-date with our Google Analytics insights.


Tracking customers in Google Analytics

If your business relies on customers or subscribers returning to your site, possibly from different devices (laptop, smartphone, etc.) then it’s critical you start tracking unique customers rather than just unique visitors in Google Analytics. By default, Google Analytics tracks your customers by browser cookies. So ‘Bob’ is only counted as the same visitor if he comes to your site from the same browser, but not if he comes from a different computer or device. Worse, if Bob clears his cookies or accesses your site via another mobile app (which won't share cookies with the default browser) then he'll also be counted as a new user. You can fix this by sending a unique customer identifier every time your customer signs in. Then if you send further custom data about the user (what plan he / she is on, or what profile fields they have completed) you can segment any of the visits or goals by these customer attributes. There are 2 possible ways to track registered users: Using Google Analytics’ user ID tracker By storing the clientId from the Google cookie when a new user registers, and writing this back into the tracker every time the same user registers In both cases, we also recommend sending the user ID as a custom dimension. This allows you segment the reports by logged in / not logged in visitors. Let's look at the pros and cons. Session stitching Tracking customers involves stitching together visits from different devices into one view of the customer. Option 1, the standard User ID feature, does session stitching out the box. You can optionally turn ‘session unification’ on which means all the pageviews before they logged in are linked to that user. With option 2 you can stitch the sessions, but you can't unify sessions before the user logs in - because they will be assigned a different clientId. So a slight advantage to option 1 here. Reporting simplicity The big difference here is that with option 1 all of the user-linked data is sent to a separate 'registered users' view, whereas in options 2 it is all on the same view as before. Suppose I want a report of the average number of transactions a month for registered vs non-registered visitors. With both options, I can only do this if I also send the user ID as a custom dimension - so I can segment based on that custom dimension. Additionally, with option 1 I can see cross-device reports - which is a big win for option 1. Reporting consistency Once you start changing the way users are tracked with option 2 you will reduce the overall number of sessions counted. If you have management reports based on unique visitors, this may change. But it will be a one-time shift - and afterwards, your reports should be stable, but with a lower visit count. So option 1 is better for consistency Conclusion Option 1 - using the official user tracking - offers a better route to upgrade your reports. For more technical details on how this tracking is going to work, read Shay Sharon’s excellent customer tracking post. Also, you can watch more about customer tracking versus session tracking in this video. Have any questions? Comment below or get in touch with our team of experts!   Get Social! Follow us on LinkedIn, Twitter, and Facebook and keep up-to-date with our Google Analytics insights.


Personally Identifiable Information (PII), hashing and Google Analytics

Google has a strict policy prohibiting sending Personally Identifiable Information (PII) to Google Analytics. This is necessary to provide GA reports around the world, yet comply with country regulations about storing personal information.  Even if you send personal information accidentally, Google may be forced to delete all of your analytics data for the time range affected. This policy has recently tightened to state: You may not upload any data that allows Google to personally identify an individual (such as names and email addresses), even in hashed form. A number of our clients are using a hashed email as the unique identifier for logged in users, or those coming from email campaigns.  If so, this needs be a minimum of SHA256 hashing (not MD5 hashing), with a 'salt' to improve the security - check your implementation meets the required standard. If you want to check if personal information affects your analytics, we now include checking for PII in our complete Google Analytics audit. Google's best practice for avoiding this issue is to remove the PII at the source - on the page, before it is sent to Google Analytics.  But it may be hard to hunt down all the situations where you accidentally send personal data; for example, a form which sends the user's email in the postback URL, or a marketing campaign which add the postcode as a campaign tag. We have developed a tag manager variable that does this removal for you, to avoid having to change any forms or marketing campaigns which are currency breaking the rules. Steps to setup 1. Copy the script below into a new custom Javascript variable in GTM [code language="javascript"]function() { // Modify the object below to add additional regular expressions var piiRegex = { //matches emails, postcodes and phone numbers where they start or end with a space //or a comma, ampersand, backslash or equals "email": /[\s&amp;\/,=]([a-zA-Z0-9_.+-]+\@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+)($|[\s&amp;\/,])/, "postcode": /[\s&amp;\/,=]([A-Z]{1,2}[0-9][0-9A-Z]?(\s|%20)[0-9][A-Z]{2})($|[\s&amp;\/,])/, "phone number": /[\s&amp;\/,=](0[0-9]{3,5}(\s|%20)?[0-9]{5,8}|[0-9]{3}-[0-9]{4}-[0-9]{4})($|[\s&amp;\/,])/ }; // Ensure that {{Page URL}} is updated to match the Variable in your // GTM container to retrieve the full URL var dl = {{Page URL}} var dlRemoved = dl; for (key in piiRegex) { dlRemoved = dlRemoved.replace(piiRegex[key], 'REMOVED'); } return dlRemoved; }[/code]   2.Check {{Page URL}} is set up in your GTM container This is a built-in variable, but you'll need to check it under the variables tab.   3. Change the pageview tag to override the standard document location, and use the variable with PII removed   By default, Google Analytics takes the location to be whatever is in the URL bar (document.location in Javascript).  You will over-ride that with the PII-safe variable.  


Get the app

See for yourself why Littledata is the smartest ecommerce analytics app

Free trial