Building GTM Server side client

Google some time ago created a great new instrument. New Google Tag Manager Server-side, there is a lot of information on why it’s so cool and what it can help. So I won’t stop on this much.

The summary is that it’s a new type of tagging that runs on a Google server (now not only Google) and can run different tags on each call, improving them with some data that is not cool to keep on the front end, e.g.,, profit on the transaction. Also, it runs on your subdomain, so AdBlocks hard to get so that you will track most of the users. And you can deal with Safari’s Intelligent Tracking Prevention from capping its expiration to just 7 days.

Today I want to share how I built a client that works mostly like a smart proxy. I built this client for the Ringostat script, but you can use it the same way for any other tracking.

Continue reading Building GTM Server side client

Bulk withdrawal LinkedIn old pending invitation

UPD 11/19/2020: Due to changes in interface previous version doesn’t works properly, so had to update code, with delay 1 second for each withdrawal.

At some moment amount of pending invitations becomes too huge. That causes problems with sending new invites. So you have to withdraw some of them that are too old.

You can do i manually, but that takes too long even, when you have to withdraw 50 invitations but at some moment I had 3500+ of them.

As an SEO specialist, I used many bookmarklets (bookmarks that contain a snippet of JavaScript). So I decided to do my bookmarklet that will click the withdraw button and then confirm.

Updated script takes all withdrawal buttons, click on them with 1 second delay from end of the page to the start. With little delay clicks on the confirmation button. After all invitations on the page withdrawn it starts from the end till the moment when there would be no pending invitations from after current page. The script:

    var i = document.querySelectorAll("button[data-control-name=withdraw_single]").length - 1; //set index to the last withdraw button on page
var interval = setInterval( //set interval for repeat function till the end of pending invitations
    function() {
        if ( { //check that we werent redirected to the first page of pending requests
            document.querySelectorAll("button[data-control-name=withdraw_single]")[i].click(); //click on withdraw button
            i = ((i < 1 || i > document.querySelectorAll("button[data-control-name=withdraw_single]").length - 1) ? document.querySelectorAll("button[data-control-name=withdraw_single]").length - 1 : i - 1); //decrement index or if it reached 0 restart from the end
            document.querySelector(".artdeco-button--primary").click(); //click on confirmation button
        } else {
            clearInterval(interval); //in case when we were redirected to the first page stop end interval
    }, 1000); //interval set to 1000 ms (1 seconds)

The same but in one line:

javascript:var i = document.querySelectorAll("button[data-control-name=withdraw_single]").length-1; var interval = setInterval(function() {if ( {document.querySelectorAll("button[data-control-name=withdraw_single]")[i].click();i = ((i<1||i>document.querySelectorAll("button[data-control-name=withdraw_single]").length-1)?document.querySelectorAll("button[data-control-name=withdraw_single]").length-1:i-1);document.querySelector(".artdeco-button--primary").click();} else {clearInterval(interval)}},1000);

You can add this code to a bookmark, to do that:

  • In Chrome, click Bookmarks->Bookmark Manager.
  • You should see a new tab with the bookmarks and folders listed.
  • Select the “Bookmarks Tab” folder on the left.
  • Click the “Organize” link, then “Add Page” in the dropdown.
  • You should see two input fields. Type the name of the bookmark you would like (i.e., WithdrawPendingLinkedIn) in the first field.
  • Paste the javascript code upper into the second field.

Or try to right-click on WithdrawPendingLinkedIn and select add to bookmarks on most Chrome versions that don’t work.

Then go to the page from what you need to clear like, run that bookmark, enjoy!

Set Busy status from additional Google calendars

Today I once again faced the fact that a meeting in one calendar was not displayed as a busy time in another calendar; thus, I had 2 invites for the same time in 2 calendars. I also have a personal calendar, which I also use, and it’s not very cool when someone makes an invite for the time when you have to visit the dentist.

I tried to find a normal solution, but I couldn’t find anything, so I made a simple doxy with an app script:

All other calendars must be shared on the main calendar, to which you want to add occupied slots, and you will actually need to execute the script under the user who will add all the calendars to the calendar.

  1. Copy the spreadsheet to your account and rename it, the way you’ll find it later.
  2. In column “A” add all the calendars you need to copy the “Busy” status.
  3. In column “B” add the calendars, where to add the “Busy” slots.
  4. In column “C” we set how many days ahead to review.
  5. Press the button “Test run” – accept the permissions, because this is a simple script, then it will still swear, which is unsafe.
  6. Check the calendar. The events should have “shadows.”
  7. Press the “Create trigger” button – the script will be executed every hour.

What’s under the hood: the script goes through the calendars, checks up all events, except those with “Busy from” in the name or declined. It creates an “uninformative” event in the required calendars, just so that it is reflected that the user is busy at this time. Accordingly, if the calendar is shared, then a refusal will automatically come, or it will automatically be reflected for the shared calendar.

The trigger checks the future once an hour and creates it for all meetings that do not yet have a corresponding “shadow.”

Measurement protocol V2 Google analytics Apps+Web

In Ringostat, historically, we have integration with Google Analytics from the beginning. That is a requirement.

As call tracking, we need to post data about phone calls to sessions of clients, so they will count as events, decrease bounce, and can be used as goals.

With Google Universal Analytics and release of measurement protocol, this integration became much better, and the first call we registered through measurement protocol on December 29th, 2012, it was in closed beta for that moment. As I know, we were the first call tracking service to do that, but I made a big marketing mistake not to use this in communication.

But time moves forward, and Google Analytics has a new stage in beta Apps+Web, and we got a client that uses that type of account. And we discovered that our integration doesn’t work because of new collect endpoint and entirely different way events work there.

But there is still so low amount of information the only useful article I found from David Vallejo, but it’s not currently cover what mandatory fields are and how to get their values.

So in this article, I’ll share a summary of mine tries and fails. Which fields mandatory and how to fill them.

Same as previously, we need to send data to collect endpoint, but now it has another URL. Also changed the protocol version, now it’s 2. And the way how tracking ID now looks like and now it’s not tracking ID but stream ID, and you can have different streams for one property.

Still, you need client ID to post data to the same client, but to get it now, you need to get ‘vid’ property of ‘gaGlobal’ object. Just call ‘gaGlobal.vid’ in javascript instead ga.getAll()[0].get(‘clientId’) as you could do previously. There is a lot of other differences that I’ll not cover in this article. But the main difference for me is new params:

  1. Session ID, for session ID, used UNIX timestamp of session start and stored in the new cookie that has a name like _ga_XXXXXXXXX where XXXXXXXXX is stream ID without G-.
  2. Session count, number of sessions for this clientId. Weird thing, but it looks like this is required, but it also stored in the cookie.

To parse those variables from the cookie in one line I modified suggested snippets of code to:


Just fix XXXXXXXXX with yours stream ID.

Long story short, we need to build the request with the parameters that I found as required and still send it to I found strange information that I have to use only POST request with separation some parameters in URL and other in payload, but GET works fine also. So the params are

ParametermeaningWhat should we use here
vProtocol Version2
tidStream IDG-XXXXXXXXX (find in account or your code)
cidClient IDjavascript: gaGlobal.vid
sidSession IDjavascript: document.cookie.match(‘(^|;)\\s*_ga_XXXXXXXXX\\s*=\\s*([^;]+)’).pop().split(‘.’)[2]
sctSession Countjavascript: document.cookie.match(‘(^|;)\\s*_ga_XXXXXXXXX\\s*=\\s*([^;]+)’).pop().split(‘.’)[3]
dlDocument locationjavascript: document.location
enEvent namestring, how you want to name your event ex. ‘call’

Also, it’s good to add _dbg=1 for debugging to see data in realtime at DebugView (extremely cool feature!).

After collection such params you can build URL that would look like:

Call it, and in 10 seconds, see the event in DebugView if you have done everything right. After that, you can improve this call with additional event params and user params.

Trends for eCommerce according to COVID-19

I tried not to post this, but I feel that I have to.

Panic, according to coronavirus (COVID-19) and the current international economic situation, will lead to changes. I’m not a prominent economist neither virologist.

But something I understand and as I see the possibilities. In the situation with panic levels increase and more attempts used to stop spread the virus are implemented: cancelation of significant events, quarantines, etc. will lead to a new wave of people to adopt eCommerce and delivery services.

Continue reading Trends for eCommerce according to COVID-19