Salesforce Marketing Cloud

Loops are one of my favorite processes in programming – they allow you to step through a variable amount of data/inputs to produce a customized output! Recently while discussing an API solution with a colleague I realized just how difficult it can be to explain how to use a loop function to solve a problem, and the differences between the various loop statements.

In an attempt to help other users to navigate this topic, lets cover the key recursive functions that are supported in Salesforce SSJS.

Introduction to the FOR Loop

In my opinion the FOR Loop is best SSJS loop statement to start with – it’s the most common, practical and understandable – and shares a very similar syntax with the AMPScript FOR statement, among other programming languages. Consider the following statement and the output below:

<script runat="server">
Platform.Load("core","1");

for (i = 0; i < 10; i++) {
  Write('The Count is: '+i+'<br>');
}

</script>

//Output:
The Count is: 0
The Count is: 1
The Count is: 2
The Count is: 3
The Count is: 4
The Count is: 5
The Count is: 6
The Count is: 7
The Count is: 8
The Count is: 9

The FOR statement contains 3 parts: The Initialization, Condition, and Ending Expression.

The Initialization runs a the start of the code and sets the scene. In the example above, it’s declaring that the variable “i” shall be equal to be 0.

After Initialization, the statement evaluates the expression in the Condition section. In the example above the Condition expression is “i < 10”. Is the value of “i” less than 10?
As the function just initialized and we just declared that i = 0, the expression evaluates as TRUE, and the area of code between the curly brackets “{}” is run.

After the code between the curly brackets has run, the third and last part of the statement is run, the Ending Expression. In the example above, the expression is “i++”, which is short hand for “i = i + 1” or “Add 1 to the value of i”. You can read more about JavaScript Operators here.
The value of “i” was previously 0, however this expression has incremented it by 1. Now that the Ending Expression is complete, the statement returns (loops back) to the Condition expression to be evaluated again.
“i < 10” is re-evaluated as TRUE – since “i” is now equal to 1, and “1 is less then 10” – and the code between the brackets is run again. Once complete, the statement processes the Ending Expression and increments the value of “i” by 1.

The process continues to “loop” over itself until the Condition expression evaluates as FALSE, at which time the statement concludes. In the example above, the last output was “The Count is 9” because as soon as the value of “i” reached 10, the Condition of “i < 10” was no longer true.

Practical uses for the FOR Loop

The beautiful simplicity of the FOR Loop is that it will simply run as many times as the statement requires it to. The example above used “(i = 0; i < 10; i++)” which resulted in the statement running 10 times. Having control over these values means we can make small adjustment to achieve other goals – such as counting down to 0, or counting in numbers other than 1.
Now that we understand the basics, lets use this functionality to solve a business problem.

Lets say we needed to send an email at the start of each day with a scoreboard that displays a list of employees who scored at least 100 points the previous day.
For simplicity lets also assume that the results from the previous day are saved directly into a Data Extension along with the employee names, so there is no need for any other lookups or date manipulation.
The data we have access to is as follows:

IDNamePoints
14Ameen245
6Coby45
42Nolan25
38Trey77
62Lee112
79Duke145
24Conrad37

Based on the above information, we can use the following SSJS code to print a table of the employees that met the condition:

<table border=1>
<tr><th>ID</th><th>Name</th><th>Points</th></tr>
  
<script runat="server">
Platform.Load("core","1");

var EMP_Points = DataExtension.Init("EmployeePointsCampaign");
var filter = {Property:"EMP_Points",SimpleOperator:"greaterThanOrEqual",Value:100};
var data = EMP_Points.Rows.Retrieve(filter);

for (i = 0; i < data.length; i++) {
  Write('<tr><td>'+data[i].EMP_ID+'</td><td>'+data[i].EMP_Name+'</td><td>'+data[i].EMP_Points+'</td></tr>');
}
</script>

</table>
Result:
IDNamePoints
14Ameen245
62Lee112
79Duke145

There’s a lot going on in the code above – so lets break it down!
Notice that the TABLE tags are outside of the script – this is because the LOOP statement is looping over the rows (tr) inside the table for each eligible employee.
You can read more about DataExtension lookups in SSJS here, however the key things to know is that the variable of “data” now holds a JSON object which contains the payload of data returned by the retrieve function that we need to output. You can brush up on your JSON understanding with this JSON intro at W3Schools. The payload returned by the retrieve function is below:

[{"EMP_ID":14, "EMP_Name":"Ameen", "EMP_Points":245},
{"EMP_ID":62, "EMP_Name":"Lee", "EMP_Points":112},
{"EMP_ID":79, "EMP_Name":"Duke", "EMP_Points":145}]

The object contains an array of objects equal to the number of eligible employees (3); where each object has within it an array of Name/Value pairs which hold the data (EMP_ID, EMP_Name, EMP_Points).
To access the name “Ameen”, we need to reference its address in the JSON structure. In this example, its “data[0]. EMP_Name”. The first address reference is data[0] because it’s in the first object in the JSON payload and arrays start at Ordinal “0”. Next we reference the Name that the value is stored in, which is “EMP_Name”.
To reference “Duke’s” point value of 145, we would write: “data[2]. EMP_Points” .

Now that we’ve covered how to address the data, lets explore how the Loop works in this example.
Notice how the Condition statement in this Loop is “i < data.length”. The “.length” property detects how many elements are in the JSON object – which in this example is 3 – therefore the Condition statement actually resolves as “i < 3”.
As we learnt in the earlier example, the for loop will continue until the “i” is no longer less than 3; meaning “i” will step through the values starting at 0 and ending at 2: [0,1,2].

Within the Loop statement, the output line is using the Write() function to print the table elements along with the 3 values that we need; EMP_ID, EMP_Name, EMP_Points. Each of these values is prefixed with “data[i]”, meaning that as the Loop statement steps through the values [0,1,2] it will correctly address each of the 3 objects in the data payload!

The FOR IN Loop

FOR IN is another form of JavaScript Loop that uses a slightly different condition format to determine how many times to run the statement. An example of the FOR IN Loop is as follows:

<script runat="server">
Platform.Load("core","1");
var EMP_data = {"EMP_ID":14, "EMP_Name":"Ameen", "EMP_Points":245};
for (i in EMP_data) {
  Write('The Value of ' +i+ ' is: ' +EMP_data[i]+ '<br>');
}
</script>

//Output:
The Value of EMP_ID is: 14
The Value of EMP_Name is: Ameen
The Value of EMP_Points is: 245

Remember above how we covered the Name/Value pairs in JSON objects? The FOR IN Loop allows you to select every “name” within an array and store it as a value; in the example above it’s stored as “i”.
With the Name attribute known, we can correctly address each Name/Value pair in the payload.

This Loop is very useful when you don’t know the name of every Name/Value pair and just need to cycle through everything that is returned. An example of this is where the Name attributes could be optional or change based on inputs. Rather than hard-coding them like we did in the first example, they can be dynamically referenced with FOR IN.

The DO WHILE Loop

The DO WHILE Loop is a slightly more complex version of the standard WHILE Loop. Since the WHILE Loop functions in much the same way as the FOR Loop , I’ll only cover the DO WHILE version.

This is the most complex Loop to learn, however the utility benefit of this loop can not be understated, it is a truly powerful function for any automation marketing team.
The DO WHILE statement comes in 2 parts – DO{} and WHILE().

<script runat="server">
Platform.Load("core","1");
var i = 0;
do {
  Write("Count to " + i + "<br>");
  i++;
}
while (i < 10);
</script>

//Output:
Count to 0
Count to 1
Count to 2
Count to 3
Count to 4
Count to 5
Count to 6
Count to 7
Count to 8
Count to 9

The above example is a replica of the FOR Loop we used at the top of this article, and it follows a very similar process of running a statement, the DO{} section, “while” a condition is TRUE, the WHILE() section.

This means we have direct control over when the statement ends based on values that can be evaluated as part of the statement. This can be used to interesting effect, such as running a random number generator that will output numbers until it hits the correct number:

<script runat="server">
Platform.Load("core","1");
var random = Math.floor(Math.random() * 10);
do {
  Write("random is " + random + "<br>");
  var random = Math.floor(Math.random() * 10);
}
while (random != 7);
</script>

//Output:
random is 4
random is 9
random is 8
random is 1
random is 3
random is 2
random is 8
random is 2
random is 9
random is 5
random is 9
random is 9
random is 8
random is 1
random is 4
random is 5
random is 0

In the above, the code will continue to run while the value of “random” is not equal to 7. In my test run it took 17 iterations before the number 7 was selected by the random number generator.

Now lets have some fun and make a function that needs to count up to a target number by adding randomly generated numbers together. It will keep generating random numbers (between 1-10) until it reaches its goal; disregarding numbers that will put it over the specified target of 100.

<script runat="server">
Platform.Load("core","1");
var target = 100;
var keepgoing = true;
var total = 0
var steps = 0
do {
  var random = Math.floor(Math.random() * 10)+1;
  steps++;
  if ((total + random) == target) {
    Write("The final Number was " + random + ", bringing to total to "+target+" on step number "+steps+"<br>");
    var keepgoing = false;
  } else {
   if ((total + random) > target) {
    Write("Number " + random + " disregarded - it would push the total to "+(total+random)+"<br>");
     var keepgoing = true;
   } else {
    Write("Number " + random + " added - total is "+(total+random)+"<br>");
    var total = (total + random);
     var keepgoing = true;
   }
  }
}
while (keepgoing);
</script>

//Output:
Number 2 added - total is 2
Number 4 added - total is 6
Number 8 added - total is 14
Number 8 added - total is 22
Number 6 added - total is 28
Number 6 added - total is 34
Number 3 added - total is 37
Number 6 added - total is 43
Number 8 added - total is 51
Number 1 added - total is 52
Number 2 added - total is 54
Number 2 added - total is 56
Number 8 added - total is 64
Number 7 added - total is 71
Number 7 added - total is 78
Number 7 added - total is 85
Number 5 added - total is 90
Number 6 added - total is 96
Number 9 disregarded - it would push the total to 105
Number 3 added - total is 99
Number 5 disregarded - it would push the total to 104
Number 2 disregarded - it would push the total to 101
Number 7 disregarded - it would push the total to 106
Number 9 disregarded - it would push the total to 108
Number 4 disregarded - it would push the total to 103
Number 7 disregarded - it would push the total to 106
Number 2 disregarded - it would push the total to 101
Number 8 disregarded - it would push the total to 107
Number 8 disregarded - it would push the total to 107
Number 2 disregarded - it would push the total to 101
Number 2 disregarded - it would push the total to 101
Number 2 disregarded - it would push the total to 101
Number 2 disregarded - it would push the total to 101
Number 7 disregarded - it would push the total to 106
Number 9 disregarded - it would push the total to 108
Number 7 disregarded - it would push the total to 106
Number 6 disregarded - it would push the total to 105
Number 4 disregarded - it would push the total to 103
Number 6 disregarded - it would push the total to 105
The final Number was 1, bringing to total to 100 on step number 40

Practical Examples for the DO WHILE Loop

The function above was creative, but the real world applications for this kind of loop are endless!
We could write an automated email that selects items from a customer’s cart in price acceding order until it reaches the minimum spend needed for Free Shipping. Imagine getting an email from your favorite fashion retailer with the subject line “Buy these 7 items in your wishlist today and get FREE SHIPPING!”
Retailers with annual spend milestones required to maintain a loyalty status – such as Sephora, Witchery and Mimco – could use the same logic to create a personalized Call to Action for the customer to maintain membership for another year. “Hey %%fName%%, restock on these %%count%% items and maintain %%tier%% status for another year!”

The versatility of SSJS Loops is that they can be run on Cloud Pages, in Emails, and as activities in Automation Studio, so there are plenty of ways to ingest and activate your data! Be sure to ask for help on the Salesforce StackExchange if you need assistance building your SSJS Loops.

Read more

I recently read the Marketing Cloud Security module on Trailhead and found the Secure Your Web and Landing Pages unit to be extremely interesting, however it glossed over the reason and function of the Example Headers that are recommended.

As the defined role gap between IT and Marketing is closing quickly, I thought this content needs to be given the attention it deserves from a non-IT perspective.

Website Headers and why you should care

Response headers are used to give a more detailed context of the response provided by a server. To critically over simplify this: When you hit SEARCH on Google.com your request is sent to Google to process – the response is then sent back and interpreted by your browser where is it rendered accordingly. If you are using Chrome, you can open the DevTools to see this in action: Instructions here.

With the example above, the response from Google contains a number of headers that instruct your browser what to do, for example:

content-encoding: br
content-type: text/html; charset=UTF-8
strict-transport-security: max-age=31536000
  • content-encoding: The response is compressed, and your browser needs to know which encodings were used by the server so it can decode the message.
  • content-type: The type of content contained in the response so that your browser knows how to render it.
  • strict-transport-security: This forces the browser to use the more secure HTTPS for communicating with the server, and sets a duration (in seconds) for this to be upheld.

So Headers are important. Why does Salesforce recommend we use these 6 on our pages?

The 6 headers outlined by Salesforce in their Landing Pages Security Best Practices provide an additional layer of security to prevent malicious actors from accessing your Cloud Page in ways that may hurt your business. Each of these 6 headers provide explicit instructions about how your Cloud Page can be accessed. Lets cover these in detail:

<script runat=server>
   Platform.Response.SetResponseHeader("Strict-Transport-Security","max-age=200");
   Platform.Response.SetResponseHeader("X-XSS-Protection","1; mode=block");
   Platform.Response.SetResponseHeader("X-Frame-Options","Deny");
   Platform.Response.SetResponseHeader("X-Content-Type-Options","nosniff");
   Platform.Response.SetResponseHeader("Referrer-Policy","strict-origin-when-cross-origin");
   Platform.Response.SetResponseHeader("Content-Security-Policy","default-src 'self'");
</script>

Strict-Transport-Security:
As above, this Header ensure all communication from a browser is sent over HTTPS. Without going into detail, HTTPS will protect your user’s data as it is sent to and from your Cloud Page. You really want to be using HTTPS if you are asking your subscribers to enter any form of personal information on your Cloud Page.
Note: SSL certificates are required to be installed by Salesforce Support and at the time of writing have an annual fee of ~$450 USD each. Adding this header without have an SSL installed on the appropriate Marketing Cloud Subdomain will result in the page request being blocked.

X-XSS-Protection:
XSS stands for Cross Site Scripting, and is a form of attach where a malicious actor injects scripts onto your page to make it perform differently.
This is a common header and most browsers support this by default. The “1; mode=block” value indicates that the header is active, and to block the page from rendering if a XSS attack is detected.

X-Frame-Options:
This header prevents your page from being loaded into a frame/iframe on another website.
The “Deny” value prevents any site from referencing you page in an iframe, however you can also use the “SameOrigin” or “Allow-From” values to specify trusted exceptions. You should always use this header unless you have an explicit plan for the page to be used in an iframe.

X-Content-Type-Options:
Content-Type sniffing is where an attacker can exploit a security vulnerability to interpret metadata from your site and possibly execute functions that the site was not built to expect. Forcing such errors can expose sensitive code or customer data.
Using the “nosniff” parameter tells the browser that the content-type is defined and not to be queried further.

Referrer-Policy:
The Referrer Policy controls how much referrer (origin site) information is sent in the request (destination site).
The “strict-origin-when-cross-origin” parameter will enforce HTTPS policy and only send the Full Referrer URL when the request comes from the same Origin. Cross-Origin requests will only see the base URL.

Content-Security-Policy:
This header acts as an additional layer of protection against Cross Site Scripting and other attacks.
The value recommended by Salesforce for this header is “default-src ‘self'”. This will force the browser to only load content that is served from the same place as the page with this header. This may cause problems with externally references images and CDN hosted JS libraries such as jQuery, so check the assets used on your page before enabling this one.

More ways to keep you Pages & Data safe

Cloud Pages are extremely versatile and powerful feature of Salesforce Marketing Cloud – however if used incorrectly can they can open up numerous serious security risks to your business and customers. Check with your company’s WebAdmin before enabling these headers and ask the Marketing Cloud community on Stack Exchange for help if you run into problems.

Read more

Regular Expression (RegEx) is an instruction set for matching and returning a part of a larger string, and is recognised by most programming languages.

AMPScript is unable to nativity parse JSON objects, meaning that string manipulation is the only way to extract the value from the larger object. As discussed in my OAuth 2.0 Access Token Quick Start guide, the SubString function is a quick way to return text if it’s location in the payload is reliable.

Since the order of objects in JSON payloads are not designed to be preserved, RegEx is a more reliable way of searching and returning values from a string. The following RegExMatch function is that I use to return the access_code from the V2 Token API:

SET @access_token = REGEXMATCH(@apistatusCode,'^(?:.*"access_token":")(.*?)(?:".*)$',1)

If you need more detail on how the RegExMatch() function works, I suggest checking out the examples on The AMPScript Guide.

RegEx can take some time to get your head around as it’s a different syntax to most languages. To understand what the Regular Expression string above is achieving, I suggest using an online visual RegEx guide such as regexr.com or regex101.com to build and test your expressions. Copying the example JSON V2 Token response into either of these services and inspecting the rule set will give a far more detailed explanation to how this expression works; however in short, the expression above is returning everything that comes after "access_token":" and before the very next " character.

Due to the simplicity of this Regular Expression, you can amend it relitivly quickly for any value in any JSON endpoint. For example, the icanhazdadjoke.com API will return a random Dad Joke every time it’s requested:

{
  "id": "R7UfaahVfFd",
  "joke": "My dog used to chase people on a bike a lot. It got so bad I had to take his bike away.",
  "status": 200
}

We can alter the RegEx code from earlier to return the value of the “joke” object in the JSON response above:

^(?:.*"joke":.*")(.*?)(?:".*)$

Note that the Dad Joke API returns a space after the : character, so I used the .* wildcard to indicate that anything could optionally appear there.

Regular Expressions are a very powerful and can be used to great effect if you understand how they work. This is a language syntax worth spending some time learning!

Read more

Working in Marketing Automation, I spend a lot of my time in Salesforce Marketing Cloud creating API integrations to push and pull data from various sources.

Writing these calls from scratch or copying bespoke code from previous solutions can be messy, so to save time I created a boilerplate of the basic OAuth 2.0 GetToken request process in AMPScript and SSJS that was easily editable for my purposes.

TL;DR. Here’s the codes!

AMPScript OAuth 2.0 using the V2 Token Endpoint:

%%[
VAR @httppost,@authurl,@apiid,@apistatusCode,@apiresponse,@apitoken
SET @apiid = '{"client_id": "zzzzzzzzzzzzzzzzzz","client_secret": "xxxxxxxxxxxxxxxxxxxxxxx", "grant_type": "client_credentials"}'
SET @authurl = "https://ccccccccccccccccccccc.auth.marketingcloudapis.com/v2/token";
SET @httppost = HTTPPost2(@authurl,"application/json",@apiid,false,@apistatusCode,@apiresponse)
SET @access_token = REGEXMATCH(@apistatusCode,'^(?:.*"access_token":")(.*?)(?:".*)$',1)
SET @rest_instance_url = REGEXMATCH(@apistatusCode,'^(?:.*"rest_instance_url":")(.*?)(?:".*)$',1)
]%%

access_token: %%=v(@access_token)=%% <br>
rest_instance_url: %%=v(@rest_instance_url)=%% <br>
apistatusCode: %%=v(@apistatusCode)=%% <br>

The Access Token can be retrieved using @access_token. Since the API response is in JSON – a data structure not supported by AMPScript – we need to use other string functions to extract the value we need, but more on that later.

SSJS OAuth 2.0 using the V2 Token Endpoint:

<script runat="server">
    Platform.Load("Core","1");
    var payload = {
    "grant_type": "client_credentials",
    "client_id": "yyyyyyyyyyyyyyyyyyy",
    "client_secret": "xxxxxxxxxxxxxxxxxx"
};
var authurl = "https://zzzzzzzzzzzzzzzzzz.auth.marketingcloudapis.com/v2/token";
var result = HTTP.Post(authurl, 'application/json', Stringify(payload));
  
if (result.StatusCode == 200) {
    var responseJson = Platform.Function.ParseJSON(result.Response[0]);
    var accessToken = responseJson.access_token;
    var restUrl = responseJson.rest_instance_url;
} else {
    throw new Error("Error fetching access token");
}
Write(result.Response[0]); //payload response
Write(accessToken); //token
</script>

Luckily Server Side JavaScript is able to parse JavaScript Object Notation (JSON), so the “access_token” value can be referenced and stored in the variable “accessToken” for later use.

Parsing JSON Values in Salesforce Marketing Cloud

If you work with API endpoints you’ll be familiar with the XML and JSON content types. JSON took the most-popular content-type throne from XML in around 2013 and has been the dominant format ever since. As of February 2020 there is still no AMPScript-only solution for parsing JSON values in Salesforce Marketing Cloud, so we need to employ other tools to extract the values we need.

Below is an Example JSON Response of the V2 Token Endpoint from earlier:

{
"access_token":"eyJhbLciOiJIPzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjIifQ.eyJhY2Nlc3NfdG9rZW4iOiJhYmJUQTlpSHZqRjkyd3Jkb0xWZEFCaloiLCJjbGllbnRfaWQiOiI3ZTRmYW1xaWUzcWtzdzlhNDRrcmxvZDgiLCJlaWQiOjEwNzU3Njc2LCJzdGFja19rZXkiOiJRQTFTMSJ9.wSFfEdeNrkoiU_tnmJ2ihm8iUqnJKlZoI3GlavTGBhs.hU4EsiC1e9txh_TCt90YlI2l7xZZ5E6_oa0xku3Jj9CCk1B72M4bhO3kUIyhwfVuB0MFbL0y9KD_RRFzg-nuqPgjPyONnby-iWopdZPBHd-3woupxCMST5-vfJO9qAED9qiUfYLS4WmHRuJTCX4NPScyu8BdROTVEe-D3iAoAeFoJX_rLZ9d5eEhIn1AvkYgoj9siuxAprHEvmySTgNIXkQA6uT_IQ-H1dbfOyJmlFKpYzvhvHb0KH7NJ24zy5bd2MQ5",
"expires_in":1200,
"token_type":"Bearer",
"rest_instance_url":"mc563885gzs27c5t9-63k636tzgm.rest.marketingcloudapis.com",
"soap_instance_url":"mc563885gzs27c5t9-63k636tzgm.soap.marketingcloudapis.com",
"scope": "email_read email_write email_send"
}

In the example above, we could use a SubString function to extract the specific character index range that we need for the token. In this example, it’s character 17 through 529; or from character 17 for a length of 512 characters, written as: %%=Substring(@response,17,512)=%% in AMPScript.

SubString works well so long as the response payload doesn’t change in any way, however by default JSON is an unordered set of name/value pairs, so even though Salesforce does preserve object orders, we shouldn’t rely on it.

RegEx is the most reliable way to extract a string value from a JSON payload in AMPScript. I cover this topic in more detail here: RegEx to get JSON values in AMPScript

Read more

Server Side JavaScript is a powerful language in Salesforce Marketing Cloud with numerous practical applications for digital marketers. It can lookup Data Extensions, it has a simple WSProxy model for accessing SOAP objects, and best of all – it can handle JSON objects!

However developing these kinds of activities in SFMC can sometimes be tedious due to the lack of error detection and handling in Emails and Cloud Pages. Before using this function I could easily waste hours hunting down my SSJS code errors!

So here is a SSJS code snippet that I use to detect and handle any errors in my Cloud Pages:

<script runat="server">
Platform.Load("Core","1");
try{
//Do your SSJS Functions Here
} 
catch(error) {
Write('Message: '+ error);
}
</script>

The try statement identifies a block of code to watch for errors while it’s being run.
The catch(error){} statement works like an if statement – it only runs if the condition “was there an error in the try section” is true. The variable “error” is where the the exception message – why the error occurred – will be stored.
In the code above, I’ve used the “error” variable inside of a Write() function so that my Cloud Page outputs the cause of any errors that occur.

Why should I use Try-Throw-Catch

Everyone makes mistakes when writing code, and the worst kind of mistakes are the ones you can’t find! Normally I like to us the Output/Print/Write functions when developing to see where the error occurred in my code – however this isn’t possible in SFMC, as the Cloud Page just fails to load or throws an ambiguous 500 error.

Using the Try-Throw-Catch function above will ensure you get a response from the page, and if an error has occurred, you’ll have some clues about what went wrong.

What about the “Throw” part?

By default, the try statement only listens for invalid operations – like syntax errors. However you can create your own “errors” by using the throw statement. For example:

<script runat="server">
Platform.Load("Core","1");
try{
//Do your SSJS Functions Here
var subKey = Attribute.GetValue("_subscriberkey");
if (!subKey) {throw "Empty SubscriberKey";}
} 
catch(error) {
Write('Message: '+ error);
}
</script>

In the code above, I’m trying to set the Cloud Page attribute “_subscriberkey” into the variable “subKey”.

If this page was accessed via a %%=RedirectTo(CloudPagesURL(x))=%% link in an email, the recipient’s SubscriberKey attribute would be populated as part of the Query String, and page would load.
However if the page was access directly and without a QS parameter, then the _subscriberkey attribute will be empty, and the page will throw the custom error I created.

Further Reading & Resources

I highly recommend reading through some of the JS Documentation on error handling; it’s a great way to speed up your development and put controlled exceptions around unstable 3rd party API calls.
Here’s a few resources I used to learn how the try statement works:

Read more