Consent management with Salesforce Marketing Cloud

Imagine a world, where you only have Salesforce Marketing Cloud. No Sales Cloud, Service Cloud, or other applications which you would normally consider a system of record for identity and consent. Especially when looking at the Consent Data Model of Salesforce:

The Salesforce Consent Data Model is the standard data model for managing consent at multiple levels, from global preferences to more granular controls. This data model, the foundation of Salesforce’s long-term view of consent, considers the individual’s entire experience, not just a single contact point. Any record that relates to an individual can have related consent considered within this model, including leads, users, person accounts, and contacts. It also provides flexibility to choose which level to manage consent initially. You can then add levels of granularity as business needs evolve or regulatory requirements change for managing that consent data.

Source: Salesforce docs

This is not available in your regular Salesforce Marketing Cloud. And you have close to no features, to create a contemporary data model, which allows you to keep track on your customers and their signups. Also, almost more importantly, there is no native support for a subscriber key other than email address. And this is probably the last thing you want to use. You can read more on this topic in a short post on importance of selecting the right subscriber key here.

I will introduce you here to a concept of building a signup processing code resource, which will receive a JSON POST, and distribute this across a normalised data model. The advantage of using a code resource is its ability of rendering JSON as correct content type, and also not utilising super messages each time it is called (regular cloud pages consume super message each time they are viewed).

Let’s get started. In addition of the code resource, you will need a number of data extensions:

  • Contacts – this is your main data extension, where you are storing your contact/subscriber, along with identity fields: SubscriberKey, Email, Firstname, etc.
  • Consents – here are your consents stored. In my example, I allow for having a country specific consent, hence a single subscriber can sign up separately to email communication from multiple countries. You could also use a different combination of parameters. This could be a specific channel, newsletter or something third.
  • Signuplog – I am a huge fan of logging as much as I can. This data extension will log the payload received. So even if something goes wrong in the process, you will be able to find the JSON which was sent to your code resource, and even retry the request.
  • TriggerConfig – receiving signups from various sources might require differen confirmation emails, or even real time journeys. Adding this data extension allows us to map a specific source (provided in the JSON payload) to an external key of a journey or a transactional send. You can even map it to a journey in a different business unit!
  • signupSources – since we only store a single row for each contact, but you might have signed up via multiple forms over time (e.g. by participating in competitions) we want to keep that history. This is where signupSources data extension comes in handy. It will keep track on each signup, and store the source together with a subscriber key and a timestamp.

Find the complete set of data extensions as MC Package Manager JSON file here.

The payload expected by this endpoint is formatted like this:

    "Email": "",
    "FirstName": "Lukas",
    "LastName": "Lunow",
    "Locale": "DK",
    "Phone": "4511223344",
    "SourceUrl": "",
    "SourceType": "SignupForm",
    "Consents": {
        "Email": true,
        "Sms": false,
        "Phone": false,
        "Version": "10.0"

Let’s jump into the SSJS

It all starts with the standard Script opening, and a quick check, if we have the right API key present in the request:

If the x-api-key header is missing or has a wrong value, we will not process the payload. A number of headers are also added, as part of Salesforce best practice for Cloud Page security. You can also see a reference to a Content Block. More on that later.

Let’s receive the payload, initiate all the data extensions, and set the global variables:

Now you might ask: what should we do with the subscriber key? Didn’t you write, that we can only use the email address as subscriber key in marketing cloud?
Well, we are free to choose, and in this case, I have chose to use a GUID, as it is quite system agnostic (any programming language is able to autogenerate a GUID). This is how we do it in SSJS:

Once we have an ID/Subscriber Key, let’s move on:

We use email address to look up if we already have a contact with this email address. If we do, we use its ID (a GUID set at a previous signup) for further processing. If not: we keep the GUID we just generated.

Now, a lot of lines of code:

Depending on what you want to include in your signup, you can get the values by parsing the incoming JSON. All the above values can be extended with as many as you want. The reason I have all the IF statements, is to avoid overwriting existing data with empty strings from the payload, if a specific field is received without a value.

Now we do an update, using the ID as a lookup field:

As you have seen earlier in this article, we use either the ID from the existing record or one which we generated. If the above update does not generate any updated records, it means the ID does not exist in the contacts data extension, and we need to use an Add function instead.

Let’s fix the consent record too:

Here we do something similar. We look up the consent record for the combination of locale and subscriber key, and update if needed. If no record exists, we create a new consent record. You are free to choose a different field than locale, to differentiate your consents. This could be brand, or anything else that matches your business processes.

If the signup comes with en email consent, we are able to trigger a confirmation email or trigger a journey. This is of course up to you, and you don’t necessarily need to observe the consent, if your confirmation is purely transactional.

But wait a minute. What’s this? sendout is not an official SSJS function in SFMC. Well, if you go back to the beginning of the code, you will se a reference to content block 307599. What we are doing here, is to “host” a number of functions, thus creating our own library. What I especially like about this concept is:

  • You are making your code highly reusable – as it is hosted centrally, and only referenced by a single line of code everywhere it is needed. Which can both be in emails as well as in Cloud Pages.
  • Another advantage is reducing the overall number of code lines in your Code Resource. Which makes it easier to maintain, since you are only looking at code specifically needed for that particular use case which cloud page covers.

Be cautious, that you need to declare this reference before anywhere in the code, where you will need to call functions declared in the content block. Also, it needs to be contained in a script block, separate from the script block containing the rest of the code.

This code below is our sendout function, taken from the Content Builder code snippet. Be cautious, that you still need to have all code within your code snippet wrapped in script start and end tags.

What we do here is looking up the combination of the source and locale in our triggerconfig data extension. If we find a match, we use following fields:

  • triggerMid – the business unit ID where the trigger needs to point
  • triggerEvent – external API key of the journey/transactional send
  • isJourney – boolean field defining if our payload should be formed as journey or email trigger

We move on to authenticating our call in the correct business unit:

Once successful, we use triggerisjourney boolean value, to select both the endpoint and payload we will be sending:

As you can see, we have a specific function in line 10: delete json["Consents"]; – and why so? If you look at the beginning of this article, you will find that we have a nested JSON containing consents. As we want to pass all our fields into the payload to triggered email or journey, we need to remove these consents. This is simply done using this delete function, leaving us with single level JSON, we can easily pass on to the journey trigger endpoints.

This approach also makes it highly flexible, as you don’t need to predefine what fields you expect in your original payload, but add them as needed.

Lastly, the only thing left, is to log what has just happened:

And here, we throw it all together:

In addition – this is the content of the Content Builder code snippet content block, referenced in the top of the code resource:

Leave a Reply

Your email address will not be published. Required fields are marked *