{"id":99016,"date":"2025-02-20T08:04:16","date_gmt":"2025-02-20T08:04:16","guid":{"rendered":"https:\/\/peraltafinancing.com\/analytics\/create-a-cookie-rewrite-web-service-using-the-google-cloud-platform\/"},"modified":"2025-02-20T08:04:16","modified_gmt":"2025-02-20T08:04:16","slug":"create-a-cookie-rewrite-web-service-using-the-google-cloud-platform","status":"publish","type":"post","link":"https:\/\/fivemor.com\/?p=99016","title":{"rendered":"Create A Cookie Rewrite Web Service Using The Google Cloud Platform"},"content":{"rendered":"<p> <br \/>\n<\/p>\n<div>\n<blockquote>\n<p><strong>Last updated 11 September 2020<\/strong>: Added <a href=\"#step-4-map-the-custom-domain-to-the-endpoint\">important note<\/a> about how the custom domain should be mapped with A\/AAAA DNS records rather than a CNAME record.<\/p>\n<\/blockquote>\n<p>Ah, Safari\u2019s <a href=\"https:\/\/webkit.org\/blog\/category\/privacy\/\">Intelligent Tracking Prevention<\/a> &#8211; the gift that <a href=\"https:\/\/www.simoahava.com\/analytics\/itp-2-1-and-web-analytics\/\">keeps on giving<\/a>. Having almost milked this cow for all it\u2019s worth, I was sure there would be little need to revisit the topic. Maybe, I thought, it would be better to just sit back and watch the world burn.<\/p>\n<p>But then Mr. <a href=\"https:\/\/www.linkedin.com\/in\/charlesfarina\">Charles Farina<\/a>, a good friend and so amicable he\u2019s practically Canadian, tempted me into testing out a quick proof-of-concept web service that would give us back our first party cookies.<\/p>\n<p>As it turns out, it\u2019s not all that time-consuming to do and it works pretty nicely. Want proof? Visit this site with Safari, and check the expiration of the <code>_ga<\/code> cookie. Cool, huh?<\/p>\n<div style=\"aspect-ratio: 1860 \/ 354;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2019\/06\/safari-cookies.jpg\" title=\"Cookies in Safari\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"354\" width=\"1860\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2019\/06\/safari-cookies.jpg#ZgotmplZ\" alt=\"Cookies in Safari\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>In this article, I\u2019ll show you how to set it up.<\/p>\n<p>The article will focus on the <code>_ga<\/code> cookie used by Google Analytics, but the same method can be used for any first-party cookie set by JavaScript libraries running on your site.<\/p>\n<p>\n  <iframe src=\"https:\/\/www.youtube-nocookie.com\/embed\/cmRrSeR51tc?enablejsapi=1\" style=\"position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;\" allowfullscreen=\"\" title=\"YouTube Video\"><\/iframe>\n<\/p>\n<p>You can also follow the steps outlined in this article by watching the <a href=\"https:\/\/www.youtube.com\/watch?v=cmRrSeR51tc\">video<\/a> above.<\/p>\n<p>                <span class=\"simmer\"><br \/>\n  <span class=\"close\">X<\/span><\/p>\n<p>\n    <span class=\"fa fa-md fa-bell\"\/><br \/>\n    <strong>The Simmer Newsletter<\/strong>\n  <\/p>\n<p>\n    Subscribe to the <a href=\"https:\/\/www.simoahava.com\/newsletter\/\">Simmer newsletter<\/a> to get the latest news and content from Simo Ahava into your email inbox!\n  <\/p>\n<p>  <\/span><\/p>\n<h2 id=\"but-why\">But why?<\/h2>\n<p>With ITP 2.1, Safari caps first party cookies written with JavaScript to a <a href=\"https:\/\/webkit.org\/blog\/8613\/intelligent-tracking-prevention-2-1\/\">maximum 7-day expiration<\/a>. The <a href=\"https:\/\/analytics.google.com\/\">Google Analytics<\/a> library writes its cookie with JavaScript. Ergo, on Safari, the Google Analytics cookie persists user data for only 7 days at a time, meaning if the user visits the site with more than 7 days between visits, they will be considered a new user in their return visit.<\/p>\n<p>With <a href=\"https:\/\/webkit.org\/blog\/8828\/intelligent-tracking-prevention-2-2\/\">ITP 2.2<\/a>, expiration of some cookies is set to 24 hours.<\/p>\n<div style=\"aspect-ratio: 1562 \/ 584;\" class=\"figure \">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2019\/06\/itp-2-2.jpg\" title=\"From https:\/\/webkit.org\/blog\/8828\/intelligent-tracking-prevention-2-2\/\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"584\" width=\"1562\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2019\/06\/itp-2-2.jpg#ZgotmplZ\" alt=\"From https:\/\/webkit.org\/blog\/8828\/intelligent-tracking-prevention-2-2\/\"\/><\/p>\n<p>    <\/a><\/p>\n<p>    <span class=\"caption\">From https:\/\/webkit.org\/blog\/8828\/intelligent-tracking-prevention-2-2\/<\/span><\/p>\n<\/div>\n<p>However, Safari does <strong>not<\/strong> (currently) cap cookies set with the <code>Set-Cookie<\/code> HTTP header. Why? Because creating a setup that makes use of the <code>Set-Cookie<\/code> header requires developer resources and is always a <strong>deliberate<\/strong> decision how to handle first-party persistence.<\/p>\n<p>ITP\u2019s main beef is against third-party JavaScript libraries executing code that repurposes first-party storage for something other than first-party data collection (namely, <a href=\"https:\/\/webkit.org\/blog\/8613\/intelligent-tracking-prevention-2-1\/\">cross-site tracking<\/a>).<\/p>\n<p>So when you have a site deliberately building or using a web service which requires the extension of resources to things like the creation of new DNS records, I guess Safari thinks that by doing all this work, you\u2019re accepting responsibility for any of the scummy things that can be done on top of such a setup.<\/p>\n<p>But I don\u2019t care about any of that. I just want to tackle the problem itself and then share the solution.<\/p>\n<h2 id=\"how-does-it-work\">How does it work?<\/h2>\n<div style=\"aspect-ratio: 2976 \/ 1494;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2019\/06\/process-diagram.jpg\" title=\"Process\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"1494\" width=\"2976\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2019\/06\/process-diagram.jpg#ZgotmplZ\" alt=\"Process\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>I\u2019ve been at this blog for more than six years now, and my skills at drawing a process diagram or flowchart are just getting worse and worse. Sorry about the mess above.<\/p>\n<p>Here\u2019s how the setup works:<\/p>\n<ol>\n<li>\n<p>When the user loads a page, a Google Analytics tracker is generated with the first GA event.<\/p>\n<\/li>\n<li>\n<p>This tracker checks if the browser has the <code>_ga<\/code> cookie. If not, a new one is generated.<\/p>\n<\/li>\n<li>\n<p>If the URL has <a href=\"https:\/\/developers.google.com\/analytics\/devguides\/collection\/analyticsjs\/cross-domain\">cross-domain linker parameters<\/a> and the tracker has the <code>allowLinker:true<\/code> field, then the tracker will use those parameters to update the <code>_ga<\/code> cookie.<\/p>\n<\/li>\n<li>\n<p>Since this cookie is generated with JavaScript, it\u2019s susceptible to ITP 2.1. Thus, in the <a href=\"https:\/\/developers.google.com\/analytics\/devguides\/collection\/analyticsjs\/sending-hits#hitcallback\"><code>hitCallback<\/code><\/a> of the Page View tag, the web service is called with a request to return a cookie named <code>_ga<\/code> with the value from the cookie generated by analytics.js.<\/p>\n<\/li>\n<li>\n<p>The web service returns this in the <code>Set-Cookie<\/code> header, and thus the <code>_ga<\/code> cookie is updated as an HTTP cookie, ignoring the impact of ITP 2.1 and ITP 2.2.<\/p>\n<\/li>\n<\/ol>\n<p>The only way the above works smoothly is by adding the new (and poorly documented) <code>cookieUpdate<\/code> field to the tag settings. This field ensures that the tracker does <strong>not<\/strong> update the expiration of the cookie if one is found. This is instrumental, as otherwise each time a tracker is initialized the cookie is updated from an HTTP cookie back to a JavaScript cookie.<\/p>\n<h2 id=\"what-youll-need\">What you\u2019ll need<\/h2>\n<p>For the solution to work, you\u2019ll need the following:<\/p>\n<ol>\n<li>\n<p>A <strong>credit card<\/strong>, since you won\u2019t be able to efficiently create a Google Cloud Project without using a billing account.<\/p>\n<\/li>\n<li>\n<p>A domain for which you have full access to <strong>DNS settings<\/strong>. This domain must share the same root\/parent as the domain from where you\u2019ll be sending requests to the web service. So if your site is at <strong><a href=\"http:\/\/www.website.com\">www.website.com<\/a><\/strong>, the domain you\u2019ll set up as the web service must be a subdomain of (or the actual domain) <strong>website.com<\/strong>.<\/p>\n<\/li>\n<li>\n<p>Necessary access to modify the settings of every single Google Analytics tag that fires on the site.<\/p>\n<\/li>\n<\/ol>\n<p>The domain mapping is important. Since the web service uses the <code>Set-Cookie<\/code> header to set the cookie, it can only do so in a first-party context if the header sets the cookie on a hostname shared by both the domain from where the request is originating (e.g. <strong><a href=\"http:\/\/www.website.com\">www.website.com<\/a><\/strong>) and the domain you have mapped to the web service (e.g. <strong>tracker.website.com<\/strong>).<\/p>\n<h2 id=\"step-1-set-up-google-cloud\">Step 1: Set up Google Cloud<\/h2>\n<p>First, visit <a href=\"https:\/\/console.cloud.google.com\/billing\">Billing management<\/a> with your favorite Google ID and create a new Billing Account for your Google Cloud organization or project. If you already have billing accounts setup, you can skip this step. You\u2019ll need to add your credit card details for situations where you blow past the free quotas. Don\u2019t worry! You can add <a href=\"https:\/\/cloud.google.com\/billing\/docs\/how-to\/budgets\">budgets<\/a> to your billing account to avoid surprises.<\/p>\n<p>Next, visit <a href=\"https:\/\/console.cloud.google.com\/cloud-resource-manager\">the Google Cloud console<\/a> with the same login, and click to <strong>Create project<\/strong>.<\/p>\n<div style=\"aspect-ratio: 1492 \/ 1116;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2019\/06\/create-cloud-project.jpg\" title=\"Create new project\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"1116\" width=\"1492\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2019\/06\/create-cloud-project.jpg#ZgotmplZ\" alt=\"Create new project\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>Give the project a name and make sure to <strong>EDIT<\/strong> the ID so that it\u2019s easier to remember \/ handle. You\u2019ll be prompted to add a billing account to the project, so choose the one you just created, or whatever account you want to use.<\/p>\n<p>Next, visit <a href=\"https:\/\/console.cloud.google.com\/apis\/api\/cloudbuild.googleapis.com\/overview\">https:\/\/console.cloud.google.com\/apis\/api\/cloudbuild.googleapis.com\/overview<\/a> and click <strong>Enable API<\/strong> to enable the Cloud Build API. We\u2019ll need this to upload our configurations to the cloud.<\/p>\n<p>Finally, go to <a href=\"https:\/\/cloud.google.com\/sdk\/install\">https:\/\/cloud.google.com\/sdk\/install<\/a> and follow the steps. You\u2019ll want to install the Google Cloud SDK so that you can run most of the necessary commands from the command line.<\/p>\n<p>After installing the SDK, run <code>gcloud init<\/code> in your terminal or command-line program. This will initialize the Google Cloud SDK for you, and it will let you choose which project to activate (choose the one you created earlier in this chapter).<\/p>\n<h2 id=\"step-2-clone-the-github-repository\">Step 2: Clone the GitHub repository<\/h2>\n<p>First, make sure you have the <code>git<\/code> client <a href=\"https:\/\/git-scm.com\/book\/en\/v2\/Getting-Started-Installing-Git\">installed<\/a>. I\u2019m using the command-line client in these examples, but feel free to use a GUI (graphical user interface) client if you wish.<\/p>\n<p>Once the <code>git<\/code> client is installed, run <code>git clone https:\/\/github.com\/sahava\/cookie-bouncer-service.git<\/code> in a directory of your choice to clone the source code to your computer.<\/p>\n<div style=\"aspect-ratio: 2212 \/ 434;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2019\/06\/git-clone-done.jpg\" title=\"git clone done\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"434\" width=\"2212\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2019\/06\/git-clone-done.jpg#ZgotmplZ\" alt=\"git clone done\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>Let\u2019s recap.<\/p>\n<ul>\n<li>\n<p>You have created a <strong>Google Cloud Platform<\/strong> project, linked to a <strong>Billing account<\/strong> with the <strong>Cloud Build API<\/strong> enabled.<\/p>\n<\/li>\n<li>\n<p>You have installed the Google Cloud SDK, and you have initialized it with the same login you used to create the Google Cloud project, and you\u2019ve configured it to set your new project as the active project.<\/p>\n<\/li>\n<li>\n<p>You have cloned the GitHub repository.<\/p>\n<\/li>\n<\/ul>\n<h2 id=\"step-3-create-and-deploy-the-app-engine-application\">Step 3: Create and deploy the App Engine application<\/h2>\n<p>Your next step is to create an <strong>App Engine<\/strong> application. <a href=\"https:\/\/cloud.google.com\/appengine\/\">App Engine<\/a> is Google\u2019s fully managed, serverless application platform. It\u2019s like a light-weight virtual machine environment, where Google handles things like scaling and instance generation for you.<\/p>\n<p>The web service we\u2019ll create in this exercise will run on the App Engine Standard environment.<\/p>\n<p>While in the folder where you cloned the Git repository, run the following command:<\/p>\n<p><code>gcloud app create<\/code><\/p>\n<p>This will initiate the App Engine creation process. It will ask you for a <strong>region<\/strong>, so choose one that is geographically close to your location, and make sure it supports the <strong>standard<\/strong> environment.<\/p>\n<div style=\"aspect-ratio: 1484 \/ 936;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2019\/06\/app-create.jpg\" title=\"App Engine create app\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"936\" width=\"1484\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2019\/06\/app-create.jpg#ZgotmplZ\" alt=\"App Engine create app\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>Next, open the file <code>app.js<\/code> for editing, and find the lines containing <code>const allowedHosts = [...]<\/code>. In this array, you need to add all the hostnames <strong>from which<\/strong> you expect to call the web service. Remember, they must share the root domain with all the custom domains you\u2019ll map to the web service. For example, on my site I only intend to call the web service from <code>https:\/\/www.gtmtools.com<\/code> and any of its pages, so I would simply add:<\/p>\n<div class=\"highlight\">\n<pre style=\"background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-javascript\" data-lang=\"javascript\"><span style=\"color:#00a\">const<\/span> allowedHosts = [\n  <span style=\"color:#a50\">'https:\/\/www.gtmtools.com'<\/span>\n];\n<\/code><\/pre>\n<\/div>\n<div style=\"aspect-ratio: 1242 \/ 752;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2019\/06\/app-js-config.jpg\" title=\"App.js config\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"752\" width=\"1242\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2019\/06\/app-js-config.jpg#ZgotmplZ\" alt=\"App.js config\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>If I also wanted to map something like <code>service.simoahava.com<\/code> to the App Engine service (yes, you can add multiple domains), I could also add <code>https:\/\/www.simoahava.com<\/code> to the list of allowed origins.<\/p>\n<p>Next, run:<\/p>\n<p><code>gcloud app deploy<\/code><\/p>\n<div style=\"aspect-ratio: 1400 \/ 846;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2019\/06\/gcloud-app-deploy.jpg\" title=\"gcloud app deploy\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"846\" width=\"1400\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2019\/06\/gcloud-app-deploy.jpg#ZgotmplZ\" alt=\"gcloud app deploy\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>It will deploy the App Engine application using the source code from the repository. <strong>Note!<\/strong> You <strong>must<\/strong> be in the folder with the <code>app.yaml<\/code> file from the repository for this command to work.<\/p>\n<p>Soon it will tell you that everything is running. You can then type <code>gcloud app browse<\/code> to automatically open a browser window in the application root. If it works, you should see a warning that GET requests are not supported.<\/p>\n<div style=\"aspect-ratio: 1144 \/ 412;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2019\/06\/get-works.jpg\" title=\"GET request doesn't work\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"412\" width=\"1144\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2019\/06\/get-works.jpg#ZgotmplZ\" alt=\"GET request doesn't work\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>The reason it complains about GET is because the endpoint isn\u2019t configured to handle GET requests. It\u2019s only meant to handle POST requests.<\/p>\n<p>You\u2019re almost done with the web service. The one piece of the puzzle that\u2019s missing is to map the web service to your custom domain, so that it can set cookies on your site with the <code>Set-Cookie<\/code> header.<\/p>\n<h2 id=\"step-4-map-the-custom-domain-to-the-endpoint\">Step 4: Map the custom domain to the endpoint<\/h2>\n<p>Browse to <a href=\"https:\/\/console.cloud.google.com\/appengine\/settings\/domains\">https:\/\/console.cloud.google.com\/appengine\/settings\/domains<\/a> and make sure you\u2019ve got your App Engine project selected.<\/p>\n<p>Click <strong>Add a custom domain<\/strong> to get the domain verification process started.<\/p>\n<p>From the <strong>Select the domain you want to use<\/strong>, choose <strong>Verify a new domain<\/strong>, and type the domain name into the respective field. Click <strong>Verify<\/strong>.<\/p>\n<blockquote>\n<p><strong>NOTE!<\/strong> It\u2019s important that you <strong>verify<\/strong> the subdomain that you want to use, rather than add a new subdomain to an already verified \u201cnaked\u201d domain. The difference is that with the first option you need to set A\/AAAA DNS records, and with the second option you need to set a CNAME record. With <a href=\"https:\/\/bugs.webkit.org\/show_bug.cgi?id=215201\">upcoming changes<\/a> to Safari, it\u2019s important that <strong>you do not use a CNAME record for this<\/strong>, as it negates the benefit of the entire solution.<\/p>\n<\/blockquote>\n<div style=\"aspect-ratio: 1294 \/ 970;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2019\/06\/verify-new-domain.jpg\" title=\"Verify new domain\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"970\" width=\"1294\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2019\/06\/verify-new-domain.jpg#ZgotmplZ\" alt=\"Verify new domain\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>It will shuttle you off to something that looks very much like Google Search Console\u2019s domain verification screen. You\u2019ll need to verify that you own the domain you are trying to map to App Engine.<\/p>\n<div style=\"aspect-ratio: 1570 \/ 846;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2019\/06\/verify-domain.jpg\" title=\"Verify domain in GSC\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"846\" width=\"1570\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2019\/06\/verify-domain.jpg#ZgotmplZ\" alt=\"Verify domain in GSC\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>For example, I\u2019m using <a href=\"https:\/\/www.godaddy.com\/\">GoDaddy<\/a> as my service provider, so it\u2019s easy to just follow the instructions and add the necessary <strong>TXT record<\/strong>.<\/p>\n<div style=\"aspect-ratio: 1802 \/ 486;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2019\/06\/txt-record.jpg\" title=\"TXT record\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"486\" width=\"1802\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2019\/06\/txt-record.jpg#ZgotmplZ\" alt=\"TXT record\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>Once you\u2019ve done the change, you\u2019ll need to wait for the DNS record to propagate. Sometimes it happens really fast, sometimes it can take hours. So just stay in the verification page, and click <strong>Verify<\/strong> every now and then to see if the TXT records have been updated.<\/p>\n<div style=\"aspect-ratio: 894 \/ 474;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2019\/06\/add-tracker-gtmtools.jpg\" title=\"Add domain\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"474\" width=\"894\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2019\/06\/add-tracker-gtmtools.jpg#ZgotmplZ\" alt=\"Add domain\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>Once the domain is verified, you can click forward in the steps to add a custom domain to the App Engine project. Google will issue a free SSL certificate for the domain, securing the endpoint itself behind HTTPS.<\/p>\n<p>In the last step, <strong>Update your DNS records to enable security<\/strong>, it will list you a number of <code>A<\/code> and <code>AAAA<\/code> records you\u2019ll need to add to your DNS settings for the host you want to map to App Engine. For example, in my example of <code>tracker.gtmtools.com<\/code>, I get the following records:<\/p>\n<div style=\"aspect-ratio: 1270 \/ 940;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2019\/06\/add-dns-records.jpg\" title=\"Add DNS records\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"940\" width=\"1270\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2019\/06\/add-dns-records.jpg#ZgotmplZ\" alt=\"Add DNS records\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>In GoDaddy, I\u2019ve now added them as instructed:<\/p>\n<div style=\"aspect-ratio: 2084 \/ 1230;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2019\/06\/godaddy-records.jpg\" title=\"Configure A AAAA records\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"1230\" width=\"2084\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2019\/06\/godaddy-records.jpg#ZgotmplZ\" alt=\"Configure A AAAA records\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>Now, again, it\u2019s a question of just waiting for the records to propagate. Once everything works, typically well within 24 hours of making the changes, you should be able to visit <code>https:\/\/tracker.yourdomain.com<\/code>, i.e. the custom domain you just mapped, and it should return the similar GET error you saw when visiting the App Engine endpoint directly.<\/p>\n<h2 id=\"step-5-test-in-the-browser\">Step 5: Test in the browser<\/h2>\n<p>Now it\u2019s time to do a quick browser test. Browse to one of the domains that you configured in <code>app.js<\/code> in the <code>allowedHosts<\/code> array (see <a href=\"#step-3-create-and-deploy-the-app-engine-application\">this chapter<\/a> for a refresher). Use the Safari browser.<\/p>\n<p>Then, open the <strong>JavaScript Console<\/strong> &#8211; that\u2019s CMD-OPT-C on a Mac, but you can also find it in the <a href=\"http:\/\/osxdaily.com\/2011\/11\/03\/enable-the-develop-menu-in-safari\/\">Develop menu<\/a>.<\/p>\n<p>In the console, copy-paste this code. You need to change the value of the <code>endpoint<\/code> variable to the web service domain you configured in the App Engine domain mappings, and you need to change the value of the <code>domain<\/code> variable to match the root domain your site and the endpoint have in common.<\/p>\n<div class=\"highlight\">\n<pre style=\"background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-javascript\" data-lang=\"javascript\"><span style=\"color:#00a\">const<\/span> endpoint = <span style=\"color:#a50\">'https:\/\/tracker.gtmtools.com'<\/span>;\n<span style=\"color:#00a\">const<\/span> domain = <span style=\"color:#a50\">'gtmtools.com'<\/span>;\n<span style=\"color:#00a\">const<\/span> xhr = <span style=\"color:#00a\">new<\/span> XMLHttpRequest();\nxhr.open(<span style=\"color:#a50\">'POST'<\/span>, endpoint);\nxhr.setRequestHeader(<span style=\"color:#a50\">'Content-Type'<\/span>, <span style=\"color:#a50\">'application\/json'<\/span>);\nxhr.withCredentials = <span style=\"color:#00a\">true<\/span>;\nxhr.send(JSON.stringify({name: <span style=\"color:#a50\">'testCookie'<\/span>, value: <span style=\"color:#a50\">'testValue'<\/span>, options: {domain: domain, maxAge: <span style=\"color:#099\">1000<\/span>*<span style=\"color:#099\">60<\/span>*<span style=\"color:#099\">60<\/span>*<span style=\"color:#099\">24<\/span>*<span style=\"color:#099\">365<\/span>*<span style=\"color:#099\">2<\/span>}}));\n<\/code><\/pre>\n<\/div>\n<p>This generates a POST request to the endpoint you have configured, and it instructs the web service to return a cookie named <code>testCookie<\/code> with value <code>testValue<\/code>, written on the root domain. Since you don\u2019t provide an expiration, the cookie will have an expiration of two years.<\/p>\n<p>If all worked out, you should see the POST request come back with a response saying the cookie was processed. Also, the response should have a proper <code>Set-Cookie<\/code> header. Finally, the cookie should appear in the Storage section of Safari\u2019s developer console.<\/p>\n<div style=\"aspect-ratio: 2294 \/ 638;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2019\/06\/it-works-1.jpg\" title=\"HTTP response\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"638\" width=\"2294\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2019\/06\/it-works-1.jpg#ZgotmplZ\" alt=\"HTTP response\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<div style=\"aspect-ratio: 2098 \/ 470;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2019\/06\/it-works-2.jpg\" title=\"HTTP headers\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"470\" width=\"2098\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2019\/06\/it-works-2.jpg#ZgotmplZ\" alt=\"HTTP headers\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<div style=\"aspect-ratio: 2040 \/ 488;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2019\/06\/it-works-3.jpg\" title=\"Cookie in storage\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"488\" width=\"2040\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2019\/06\/it-works-3.jpg#ZgotmplZ\" alt=\"Cookie in storage\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>So <strong>that\u2019s how you create first-party cookies with no expiration limitations in Safari post-ITP-2.1!<\/strong>.<\/p>\n<p>But let\u2019s check out the final step &#8211; how to enable this in Google Analytics.<\/p>\n<p>To make the <code>_ga<\/code> cookie work with the web service, you need to do two things.<\/p>\n<ol>\n<li>\n<p>Add the <code>cookieUpdate<\/code> field with value <code>false<\/code> to <strong>every single Google Analytics tag firing on the page<\/strong>. This is absolutely necessary. If there\u2019s even a single GA tag that doesn\u2019t have this field (and shares the cookie settings with your tags that DO have the field), the JavaScript cookie will keep on overwriting the cookie set in the HTTP response.<\/p>\n<\/li>\n<li>\n<p>Add a <code>hitCallback<\/code> field and variable to your Page View tag (or whatever tag fires on every single page) that handles the actual logic.<\/p>\n<\/li>\n<\/ol>\n<p>The first step is easy. Just use a <a href=\"https:\/\/www.simoahava.com\/analytics\/google-analytics-settings-variable-in-gtm\/\">Google Analytics Settings variable<\/a> or, alternatively, add the field manually to every single tag.<\/p>\n<p>The key is to browse to <strong>More Settings<\/strong> -&gt; <strong>Fields to Set<\/strong>, and add a new field with:<\/p>\n<p>Field name: <code>cookieUpdate<\/code><br \/>Value: <code>false<\/code><\/p>\n<div style=\"aspect-ratio: 1822 \/ 604;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2019\/06\/cookie-update-false.jpg\" title=\"Cookie Update field\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"604\" width=\"1822\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2019\/06\/cookie-update-false.jpg#ZgotmplZ\" alt=\"Cookie Update field\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>If you\u2019re using <strong>analytics.js<\/strong>, you set the field on the tracker like this:<\/p>\n<div class=\"highlight\">\n<pre style=\"background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-javascript\" data-lang=\"javascript\">ga(<span style=\"color:#a50\">'create'<\/span>, <span style=\"color:#a50\">'UA-12345-1'<\/span>, {cookieUpdate: <span style=\"color:#00a\">false<\/span>});\n<\/code><\/pre>\n<\/div>\n<p>And if you\u2019re using <strong>gtag.js<\/strong>, it\u2019s like this:<\/p>\n<div class=\"highlight\">\n<pre style=\"background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-javascript\" data-lang=\"javascript\">gtag(<span style=\"color:#a50\">'config'<\/span>, <span style=\"color:#a50\">'UA-12345-1'<\/span>, {\n  cookie_update: <span style=\"color:#00a\">false<\/span>\n});\n<\/code><\/pre>\n<\/div>\n<p>Once that is done, the next step is to create the <code>hitCallback<\/code> function. I\u2019ll show you how to do it in Google Tag Manager, but something similar could easily be done with <a href=\"https:\/\/developers.google.com\/analytics\/devguides\/collection\/analyticsjs\/sending-hits#hitcallback\">analytics.js<\/a> and <a href=\"https:\/\/developers.google.com\/analytics\/devguides\/collection\/gtagjs\/sending-data#implement_event_callback_functions\">gtag.js<\/a> &#8211; it\u2019s just JavaScript, after all.<\/p>\n<p>In GTM, create a new <strong>Custom JavaScript variable<\/strong>.<\/p>\n<blockquote>\n<p>What? Why not use <a href=\"https:\/\/www.simoahava.com\/analytics\/custom-templates-guide-for-google-tag-manager\/\">custom templates<\/a>? Well, unfortunately they don\u2019t support creating POST requests yet. You could reconfigure the endpoint to work with GET requests, but POST is more suitable for handling structured data.<\/p>\n<\/blockquote>\n<p>In the Custom JavaScript variable, add this code:<\/p>\n<div class=\"highlight\">\n<pre style=\"background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-javascript\" data-lang=\"javascript\"><span style=\"color:#00a\">function<\/span>() {\n  <span style=\"color:#00a\">return<\/span> <span style=\"color:#00a\">function<\/span>() {\n    <span style=\"color:#aaa;font-style:italic\">\/\/ Change these\n<\/span><span style=\"color:#aaa;font-style:italic\"\/>    <span style=\"color:#00a\">var<\/span> endpoint = <span style=\"color:#a50\">'https:\/\/tracker.gtmtools.com'<\/span>;\n    <span style=\"color:#00a\">var<\/span> domain = <span style=\"color:#a50\">'gtmtools.com'<\/span>;\n    \n    <span style=\"color:#aaa;font-style:italic\">\/\/ If your GA cookie name is something different than the default _ga, change this accordingly\n<\/span><span style=\"color:#aaa;font-style:italic\"\/>    <span style=\"color:#00a\">var<\/span> gaCookieName = <span style=\"color:#a50\">'_ga'<\/span>;\n    \n    <span style=\"color:#aaa;font-style:italic\">\/\/ From https:\/\/www.w3schools.com\/js\/js_cookies.asp\n<\/span><span style=\"color:#aaa;font-style:italic\"\/>    <span style=\"color:#00a\">function<\/span> getCookie(cname){<span style=\"color:#00a\">var<\/span> name=cname+<span style=\"color:#a50\">\"=\"<\/span>;<span style=\"color:#00a\">var<\/span> decodedCookie=<span style=\"color:#0aa\">decodeURIComponent<\/span>(<span style=\"color:#0aa\">document<\/span>.cookie);<span style=\"color:#00a\">var<\/span> ca=decodedCookie.split(<span style=\"color:#a50\">\";\"<\/span>);<span style=\"color:#00a\">for<\/span>(<span style=\"color:#00a\">var<\/span> i=<span style=\"color:#099\">0<\/span>;i<ca.length style=\"color:#00a\">var c=ca[i];<span style=\"color:#00a\">while<\/span>(c.charAt(<span style=\"color:#099\">0<\/span>)==<span style=\"color:#a50\">\" \"<\/span>){c=c.substring(<span style=\"color:#099\">1<\/span>)}<span style=\"color:#00a\">if<\/span>(c.indexOf(name)==<span style=\"color:#099\">0<\/span>){<span style=\"color:#00a\">return<\/span> c.substring(name.length,c.length)}}<span style=\"color:#00a\">return<\/span><span style=\"color:#a50\">\"\"<\/span>}\n    \n    <span style=\"color:#aaa;font-style:italic\">\/\/ Don't touch anything below\n<\/span><span style=\"color:#aaa;font-style:italic\"\/>\t\n\t<span style=\"color:#aaa;font-style:italic\">\/\/ Using getCookie() because we need to fetch the cookie value when the variable is executed, not when its added to the tag\n<\/span><span style=\"color:#aaa;font-style:italic\"\/>    <span style=\"color:#00a\">var<\/span> gaCookie = getCookie(gaCookieName);\n\t\n    <span style=\"color:#00a\">var<\/span> safariApiPoll = {{Cookie - _safari_api_poll}};\n    \n    <span style=\"color:#aaa;font-style:italic\">\/\/ Only run if GA cookie is set and if Safari hasn't been polled in the last 6 days\n<\/span><span style=\"color:#aaa;font-style:italic\"\/>    <span style=\"color:#00a\">if<\/span> (!gaCookie || safariApiPoll) { \n      <span style=\"color:#00a\">return<\/span>; \n    }\n\n    <span style=\"color:#00a\">var<\/span> data = JSON.stringify([{\n      name: <span style=\"color:#a50\">'_ga'<\/span>, \n      value: gaCookie,\n      options: {\n        path: <span style=\"color:#a50\">'\/'<\/span>, \n        domain: domain, \n        maxAge: <span style=\"color:#099\">1000<\/span> * <span style=\"color:#099\">60<\/span> * <span style=\"color:#099\">60<\/span> * <span style=\"color:#099\">24<\/span> * <span style=\"color:#099\">365<\/span> * <span style=\"color:#099\">2<\/span>\n      }\n    }]);\n\t<span style=\"color:#00a\">var<\/span> xhr = <span style=\"color:#00a\">new<\/span> XMLHttpRequest();\n\txhr.open(<span style=\"color:#a50\">'POST'<\/span>, endpoint, <span style=\"color:#00a\">true<\/span>);\n\txhr.setRequestHeader(<span style=\"color:#a50\">'Content-Type'<\/span>, <span style=\"color:#a50\">'application\/json'<\/span>);\n\txhr.withCredentials = <span style=\"color:#00a\">true<\/span>;\n\txhr.send(data);\n    <span style=\"color:#0aa\">document<\/span>.cookie = <span style=\"color:#a50\">'_safari_api_poll=true; domain='<\/span> + domain + <span style=\"color:#a50\">'; path=\/; max-age='<\/span> + <span style=\"color:#099\">60<\/span>*<span style=\"color:#099\">60<\/span>*<span style=\"color:#099\">24<\/span>*<span style=\"color:#099\">6<\/span>;\n  }\n}\n<\/ca.length><\/code><\/pre>\n<\/div>\n<p>Remember to change the <code>endpoint<\/code> and <code>domain<\/code> to match the service endpoint and the root domain, respectively.<\/p>\n<p>Give the variable name such as <strong>{{hitCallback &#8211; Cookie rewrite}}<\/strong>.<\/p>\n<p>The <code>hitCallback<\/code> function does two things.<\/p>\n<ol>\n<li>\n<p>If the <code>_ga<\/code> cookie has been set, and if it\u2019s been more than <strong>6 days<\/strong> since the web service was last polled, it requests the web service to write a new cookie named <code>_ga<\/code>, using the value of the actual <code>_ga<\/code> cookie written by Google Analytics.<\/p>\n<\/li>\n<li>\n<p>It sets a new cookie with a 6 day expiration to avoid polling the web service any more than necessary.<\/p>\n<\/li>\n<\/ol>\n<p>The reason we have the 6-day-expiration cookie is to prevent the API from being constantly polled by your tags. Since the API sets a cookie with a two-year-long expiration, it\u2019s OK to only poll it once every six days.<\/p>\n<p><em>There\u2019s a complicated logic here, so bear with me.<\/em><\/p>\n<p>There\u2019s no way to know using JavaScript what the expiration of the <code>_ga<\/code> cookie is. Thus, we need to poll the API every now and then to make sure the cookie stays written without a short expiration.<\/p>\n<p>The maximum length of time we can persist information about when the API was last polled is 7 days. Why? Because this information is stored in a cookie written with JavaScript. The 6 days is just a precaution &#8211; you could easily modify the <code>max-age<\/code> parameter to make the cookie last a while longer.<\/p>\n<p>And if you don\u2019t mind the API being polled constantly, feel free to remove this cookie check or to make it poll the API once a day, for example.<\/p>\n<p><strong>Anyway<\/strong>. Next you\u2019ll need to create two <strong>First Party Cookie<\/strong> variables:<\/p>\n<div style=\"aspect-ratio: 1156 \/ 912;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2019\/06\/ga-cookie.jpg\" title=\"_ga Cookie\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"912\" width=\"1156\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2019\/06\/ga-cookie.jpg#ZgotmplZ\" alt=\"_ga Cookie\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<div style=\"aspect-ratio: 1204 \/ 914;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2019\/06\/safari-api-poll-cookie.jpg\" title=\"_safari_api_poll_cookie\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"914\" width=\"1204\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2019\/06\/safari-api-poll-cookie.jpg#ZgotmplZ\" alt=\"_safari_api_poll_cookie\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>Finally, you need to find your Page View tag (or whatever tag fires consistently on every page), and scroll down to its <strong>More Settings<\/strong> -&gt; <strong>Fields to Set<\/strong> (check \u201cEnable overriding settings in this tag\u201d first). Add a new field:<\/p>\n<p>Field name: <code>hitCallback<\/code><br \/>Value: <code>{{hitCallback - Cookie rewrite}}<\/code><\/p>\n<div style=\"aspect-ratio: 1798 \/ 624;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2019\/06\/pageview-hitcallback.jpg\" title=\"hitCallback in the tag\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"624\" width=\"1798\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2019\/06\/pageview-hitcallback.jpg#ZgotmplZ\" alt=\"hitCallback in the tag\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>Now that you\u2019re all set, it\u2019s time to test!<\/p>\n<p>Take the container to Preview mode, and visit the site with Safari. You should see a request to <code>tracker.domain.com<\/code> in the <strong>Network<\/strong> tab, and you should see your <code>_ga<\/code> cookie with a full two-year-long expiration. Reload the page and you should see no more requests to the web service, nor should the expiration of the <code>_ga<\/code> cookie change.<\/p>\n<div style=\"aspect-ratio: 2716 \/ 410;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2019\/06\/cookies-storage.jpg\" title=\"Cookies in storage\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"410\" width=\"2716\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2019\/06\/cookies-storage.jpg#ZgotmplZ\" alt=\"Cookies in storage\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<h2 id=\"troubleshooting\">Troubleshooting<\/h2>\n<h3 id=\"i-dont-see-any-requests-to-trackermywebservicecom\">I don\u2019t see any requests to tracker.mywebservice.com<\/h3>\n<p>Make sure you configure the <code>hitCallback<\/code> variable correctly and that you add it to the tag.<\/p>\n<h3 id=\"i-see-a-request-to-trackermywebservicecom-but-it-fails\">I see a request to tracker.mywebservice.com but it fails<\/h3>\n<p>Check the JavaScript console. There should be details about why the request failed. Most likely you\u2019ve misconfigured the <code>allowedHosts<\/code> array in <code>app.js<\/code>, and thus the cross-origin request isn\u2019t going through.<\/p>\n<p>Make sure the <code>domain<\/code> is correct &#8211; it needs to be a root domain shared by your website and the web service.<\/p>\n<p>Make sure you\u2019ve set the <code>cookieUpdate<\/code> field to <code>false<\/code> in <strong>all<\/strong> your GA tags.<\/p>\n<h3 id=\"i-have-problems-setting-up-the-app-engine-application-or-something-else\">I have problems setting up the App Engine application or something else<\/h3>\n<p>Leave a question in the comments and I\u2019ll try to help you out.<\/p>\n<h2 id=\"final-thoughts\">Final thoughts<\/h2>\n<p>I hope you find this exercise useful &#8211; it should give you some idea about what the technical steps are for creating some type of cookie routing service.<\/p>\n<p>There are other ways to handle this, listed in <a href=\"https:\/\/www.simoahava.com\/analytics\/itp-2-1-and-web-analytics\/#technical-implications-and-solutions\">this article<\/a>. However, the beauty of an API service like this is that it\u2019s as scalable as you like (or as your budget allows).<\/p>\n<p>Using App Engine Standard environment isn\u2019t too expensive. For example, the modest amount of hits my site is generating for the API (around 7000 requests per day) is well within the free quota. App Engine charges based on instance hours, and you\u2019re given 28 instance hours for free per day. I\u2019m barely hitting 25 with this load.<\/p>\n<p>Naturally, there are ways to optimize this even further. For example, you could check if the user\u2019s browser is Safari and only use the API then. Similarly, you could extend the API poll cookie from six days to far longer expirations by setting THAT cookie with the web service, too. Though it might create some sort of temporal cosmic paradox, I don\u2019t know.<\/p>\n<p>Regardless, let me know in the comments what you think of this solution!<\/p>\n<\/p><\/div>\n\n","protected":false},"excerpt":{"rendered":"<p>Last updated 11 September 2020: Added important note about how the custom domain should be mapped with A\/AAAA DNS records rather than a CNAME record. Ah, Safari\u2019s Intelligent Tracking Prevention &#8211; the gift that keeps on giving. Having almost milked this cow for all it\u2019s worth, I was sure there would be little need to [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":99017,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[12033],"tags":[12564,14315,7957,1531,970,46349,4934,5393],"dealstore":[],"offerexpiration":[],"class_list":["post-99016","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-analytics","tag-cloud","tag-cookie","tag-create","tag-google","tag-platform","tag-rewrite","tag-service","tag-web"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v26.4 - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>Create A Cookie Rewrite Web Service Using The Google Cloud Platform - Som2ny Network<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/fivemor.com\/?p=99016\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Create A Cookie Rewrite Web Service Using The Google Cloud Platform - Som2ny Network\" \/>\n<meta property=\"og:description\" content=\"Last updated 11 September 2020: Added important note about how the custom domain should be mapped with A\/AAAA DNS records rather than a CNAME record. Ah, Safari\u2019s Intelligent Tracking Prevention &#8211; the gift that keeps on giving. Having almost milked this cow for all it\u2019s worth, I was sure there would be little need to [&hellip;]\" \/>\n<meta property=\"og:url\" content=\"https:\/\/fivemor.com\/?p=99016\" \/>\n<meta property=\"og:site_name\" content=\"Som2ny Network\" \/>\n<meta property=\"article:published_time\" content=\"2025-02-20T08:04:16+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/02\/safari-cookies.jpg\" \/>\n\t<meta property=\"og:image:width\" content=\"1860\" \/>\n\t<meta property=\"og:image:height\" content=\"354\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/jpeg\" \/>\n<meta name=\"author\" content=\"admin\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"admin\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"18 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\/\/fivemor.com\/?p=99016#article\",\"isPartOf\":{\"@id\":\"https:\/\/fivemor.com\/?p=99016\"},\"author\":{\"name\":\"admin\",\"@id\":\"https:\/\/fivemor.com\/#\/schema\/person\/b85e3c3dc0e1daea076524dc8810c371\"},\"headline\":\"Create A Cookie Rewrite Web Service Using The Google Cloud Platform\",\"datePublished\":\"2025-02-20T08:04:16+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/fivemor.com\/?p=99016\"},\"wordCount\":3193,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\/\/fivemor.com\/#organization\"},\"image\":{\"@id\":\"https:\/\/fivemor.com\/?p=99016#primaryimage\"},\"thumbnailUrl\":\"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/02\/safari-cookies.jpg\",\"keywords\":[\"Cloud\",\"Cookie\",\"create\",\"Google\",\"Platform\",\"Rewrite\",\"Service\",\"Web\"],\"articleSection\":[\"Analytics\"],\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\/\/fivemor.com\/?p=99016#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/fivemor.com\/?p=99016\",\"url\":\"https:\/\/fivemor.com\/?p=99016\",\"name\":\"Create A Cookie Rewrite Web Service Using The Google Cloud Platform - Som2ny Network\",\"isPartOf\":{\"@id\":\"https:\/\/fivemor.com\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/fivemor.com\/?p=99016#primaryimage\"},\"image\":{\"@id\":\"https:\/\/fivemor.com\/?p=99016#primaryimage\"},\"thumbnailUrl\":\"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/02\/safari-cookies.jpg\",\"datePublished\":\"2025-02-20T08:04:16+00:00\",\"breadcrumb\":{\"@id\":\"https:\/\/fivemor.com\/?p=99016#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/fivemor.com\/?p=99016\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/fivemor.com\/?p=99016#primaryimage\",\"url\":\"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/02\/safari-cookies.jpg\",\"contentUrl\":\"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/02\/safari-cookies.jpg\",\"width\":1860,\"height\":354},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/fivemor.com\/?p=99016#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/fivemor.com\/?bp_activities=1\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Create A Cookie Rewrite Web Service Using The Google Cloud Platform\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/fivemor.com\/#website\",\"url\":\"https:\/\/fivemor.com\/\",\"name\":\"Som2ny Network\",\"description\":\"Daily Deals\",\"publisher\":{\"@id\":\"https:\/\/fivemor.com\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/fivemor.com\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\/\/fivemor.com\/#organization\",\"name\":\"Som2ny Network\",\"url\":\"https:\/\/fivemor.com\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/fivemor.com\/#\/schema\/logo\/image\/\",\"url\":\"https:\/\/fivemor.com\/wp-content\/uploads\/2026\/07\/4a0953c4-logo-300x86-1.png\",\"contentUrl\":\"https:\/\/fivemor.com\/wp-content\/uploads\/2026\/07\/4a0953c4-logo-300x86-1.png\",\"width\":300,\"height\":86,\"caption\":\"Som2ny Network\"},\"image\":{\"@id\":\"https:\/\/fivemor.com\/#\/schema\/logo\/image\/\"}},{\"@type\":\"Person\",\"@id\":\"https:\/\/fivemor.com\/#\/schema\/person\/b85e3c3dc0e1daea076524dc8810c371\",\"name\":\"admin\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/fivemor.com\/#\/schema\/person\/image\/\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/729ae85bf62b9917e93538db2f2688ca?s=96&r=g&default=https%3A%2F%2Ffivemor.com%2Fwp-content%2Fplugins%2Fbuddypress-first-letter-avatar%2Fimages%2Fdefault%2F96%2Flatin_a.png\",\"contentUrl\":\"https:\/\/secure.gravatar.com\/avatar\/729ae85bf62b9917e93538db2f2688ca?s=96&r=g&default=https%3A%2F%2Ffivemor.com%2Fwp-content%2Fplugins%2Fbuddypress-first-letter-avatar%2Fimages%2Fdefault%2F96%2Flatin_a.png\",\"caption\":\"admin\"},\"sameAs\":[\"https:\/\/fivemor.com\"],\"url\":\"https:\/\/fivemor.com\/?author=1\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Create A Cookie Rewrite Web Service Using The Google Cloud Platform - Som2ny Network","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/fivemor.com\/?p=99016","og_locale":"en_US","og_type":"article","og_title":"Create A Cookie Rewrite Web Service Using The Google Cloud Platform - Som2ny Network","og_description":"Last updated 11 September 2020: Added important note about how the custom domain should be mapped with A\/AAAA DNS records rather than a CNAME record. Ah, Safari\u2019s Intelligent Tracking Prevention &#8211; the gift that keeps on giving. Having almost milked this cow for all it\u2019s worth, I was sure there would be little need to [&hellip;]","og_url":"https:\/\/fivemor.com\/?p=99016","og_site_name":"Som2ny Network","article_published_time":"2025-02-20T08:04:16+00:00","og_image":[{"width":1860,"height":354,"url":"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/02\/safari-cookies.jpg","type":"image\/jpeg"}],"author":"admin","twitter_card":"summary_large_image","twitter_misc":{"Written by":"admin","Est. reading time":"18 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/fivemor.com\/?p=99016#article","isPartOf":{"@id":"https:\/\/fivemor.com\/?p=99016"},"author":{"name":"admin","@id":"https:\/\/fivemor.com\/#\/schema\/person\/b85e3c3dc0e1daea076524dc8810c371"},"headline":"Create A Cookie Rewrite Web Service Using The Google Cloud Platform","datePublished":"2025-02-20T08:04:16+00:00","mainEntityOfPage":{"@id":"https:\/\/fivemor.com\/?p=99016"},"wordCount":3193,"commentCount":0,"publisher":{"@id":"https:\/\/fivemor.com\/#organization"},"image":{"@id":"https:\/\/fivemor.com\/?p=99016#primaryimage"},"thumbnailUrl":"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/02\/safari-cookies.jpg","keywords":["Cloud","Cookie","create","Google","Platform","Rewrite","Service","Web"],"articleSection":["Analytics"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/fivemor.com\/?p=99016#respond"]}]},{"@type":"WebPage","@id":"https:\/\/fivemor.com\/?p=99016","url":"https:\/\/fivemor.com\/?p=99016","name":"Create A Cookie Rewrite Web Service Using The Google Cloud Platform - Som2ny Network","isPartOf":{"@id":"https:\/\/fivemor.com\/#website"},"primaryImageOfPage":{"@id":"https:\/\/fivemor.com\/?p=99016#primaryimage"},"image":{"@id":"https:\/\/fivemor.com\/?p=99016#primaryimage"},"thumbnailUrl":"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/02\/safari-cookies.jpg","datePublished":"2025-02-20T08:04:16+00:00","breadcrumb":{"@id":"https:\/\/fivemor.com\/?p=99016#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/fivemor.com\/?p=99016"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/fivemor.com\/?p=99016#primaryimage","url":"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/02\/safari-cookies.jpg","contentUrl":"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/02\/safari-cookies.jpg","width":1860,"height":354},{"@type":"BreadcrumbList","@id":"https:\/\/fivemor.com\/?p=99016#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/fivemor.com\/?bp_activities=1"},{"@type":"ListItem","position":2,"name":"Create A Cookie Rewrite Web Service Using The Google Cloud Platform"}]},{"@type":"WebSite","@id":"https:\/\/fivemor.com\/#website","url":"https:\/\/fivemor.com\/","name":"Som2ny Network","description":"Daily Deals","publisher":{"@id":"https:\/\/fivemor.com\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/fivemor.com\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/fivemor.com\/#organization","name":"Som2ny Network","url":"https:\/\/fivemor.com\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/fivemor.com\/#\/schema\/logo\/image\/","url":"https:\/\/fivemor.com\/wp-content\/uploads\/2026\/07\/4a0953c4-logo-300x86-1.png","contentUrl":"https:\/\/fivemor.com\/wp-content\/uploads\/2026\/07\/4a0953c4-logo-300x86-1.png","width":300,"height":86,"caption":"Som2ny Network"},"image":{"@id":"https:\/\/fivemor.com\/#\/schema\/logo\/image\/"}},{"@type":"Person","@id":"https:\/\/fivemor.com\/#\/schema\/person\/b85e3c3dc0e1daea076524dc8810c371","name":"admin","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/fivemor.com\/#\/schema\/person\/image\/","url":"https:\/\/secure.gravatar.com\/avatar\/729ae85bf62b9917e93538db2f2688ca?s=96&r=g&default=https%3A%2F%2Ffivemor.com%2Fwp-content%2Fplugins%2Fbuddypress-first-letter-avatar%2Fimages%2Fdefault%2F96%2Flatin_a.png","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/729ae85bf62b9917e93538db2f2688ca?s=96&r=g&default=https%3A%2F%2Ffivemor.com%2Fwp-content%2Fplugins%2Fbuddypress-first-letter-avatar%2Fimages%2Fdefault%2F96%2Flatin_a.png","caption":"admin"},"sameAs":["https:\/\/fivemor.com"],"url":"https:\/\/fivemor.com\/?author=1"}]}},"_links":{"self":[{"href":"https:\/\/fivemor.com\/index.php?rest_route=\/wp\/v2\/posts\/99016","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/fivemor.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/fivemor.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/fivemor.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/fivemor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=99016"}],"version-history":[{"count":0,"href":"https:\/\/fivemor.com\/index.php?rest_route=\/wp\/v2\/posts\/99016\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/fivemor.com\/index.php?rest_route=\/wp\/v2\/media\/99017"}],"wp:attachment":[{"href":"https:\/\/fivemor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=99016"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/fivemor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=99016"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/fivemor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=99016"},{"taxonomy":"dealstore","embeddable":true,"href":"https:\/\/fivemor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fdealstore&post=99016"},{"taxonomy":"offerexpiration","embeddable":true,"href":"https:\/\/fivemor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fofferexpiration&post=99016"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}