Custom profile and subscription center integrated with Sales/Service Cloud

In this tutorial, you will learn how to create a simple, yet fully customizable profile and subscription centre integrated with Sales and Service Cloud. Bear in mind, that the script utilizes a couple of standard and custom Sales/Service Cloud fields, which you will need to adjust based on your Salesforce CRM setup – the full list can be found here.

What are Profile and Subscription Centers?

Each instance of Salesforce Marketing Cloud comes with a standard, predefined Profile Center and Subscription Center.

The profile center is a webpage where subscribers can enter and maintain the personal information that you keep about them. When you import a list, you can import attribute values for your subscribers that appear when a subscriber visits the profile center. The subscriber can update their information on this page and provide additional information.

A subscription center is a web page where a subscriber can control the messages they receive from your organization. The lists (including publication lists) you identify as public in the application are available for a subscriber to opt in to on the subscription center.

If you’re using Marketing Cloud Connect to integrate with Sales/Service Cloud, you can use the standard Profile Center based on the Marketing Cloud attributes that are mapped to fields in Sales or Service Cloud. Marketing Cloud Connect documentation states, that changes made in the Profile Center update Salesforce contact and lead data – unfortunately I haven’t been able to confirm this with my tests.

Another limitation which is very hard to overcome is the look and feel of the standard Profile and Subscription Center. If you enable Marketing Cloud BrandBuilder, you will be able to customize the color scheme of your Profile and Subscription Center based on the colors of the logo you upload – but you won’t be able to change the layout or the fonts used.

Sooner or later, most Marketing Cloud users switch to custom Profile and Subscription Centers, as the standard one is not enough when it comes to more complex use cases.

Anatomy of a Subscription Center

A Subscription Center is nothing else but an HTML form. The data passed in the form is then processed by a script – in this tutorial, we will use AMPscript. If you’ve never created a form before, you might want to check out this article first: Create a Sales Cloud-integrated lead capture form using AMPscript.

In the form, you can include any fields from any of the relevant Sales/Service Cloud objects, but to make it more simple as we go, for now, we will use three standard fields from the Contact object: FirstName, LastName and Email. Additionally, I have added three read-only checkboxes to the Contact object for subscription management: Newsletter, Offers and Events. [Why are they read-only? In this tutorial, you will learn how to allow subscribers to make changes to their preferences and sync those changes back to Salesforce. The process of managing the subscriptions by a Salesforce user and syncing the changes back to Marketing Cloud is a separate procedure and won’t be addressed in this article.] I also made sure that the Email Opt Out flag is visible on a Contact page layout in Sales Cloud:

Here is a reference of all the fields from the Contact object, which we will use in our script:

Field LabelAPI Field NameTypeLength
Contact IDIdid18
EmailEmailemail80
Email Opt OutHasOptedOutOfEmailboolean
Name FirstNamestring80
Name LastNamestring80
NewsletterNewsletter__cboolean
EventsEvents__cboolean
OffersOffers__cboolean

Let’s now create a simple HTML form which contains all of the above fields:

<html>
<body>
<h2>Update your profile:</h2>
<form>
<label>First name: </label><input type="text" name="firstname"><br>
<label>Last name: </label><input type="text" name="lastname"><br>
<label>Email: </label><input type="text" name="email"><br>
<h2>Update your subscriptions:</h2>
<label>Newsletter: </label><input name="newsletter" type="checkbox"><br>
<label>Events: </label><input name="events" type="checkbox"><br>
<label>Offers: </label><input name="offers" type="checkbox"><br>
<input name="submitted" type="hidden" value="true"><br>
<input type="submit" value="Submit">
</form>
</body>
</html>

Since we want the form to be pre-populated with subscriber data from Sales/Service Cloud, we need to add the RetrieveSalesforceObjects function to pull the data, and display the retrieved data as field values:

%%[
SET @contactId = _subscriberkey
IF NOT EMPTY(@contactId) THEN
/* fetch data from Sales Cloud to show in the form */
SET @subscriberRows = RetrieveSalesforceObjects(
"Contact",
"Id,FirstName,LastName,Email,Newsletter__c,Events__c,Offers__c,HasOptedOutOfEmail",
"Id", "=", @contactId
)
IF RowCount(@subscriberRows) > 0 THEN
SET @row = row(@subscriberRows,1)
SET @FirstName = field(@row,"FirstName")
SET @LastName = field(@row,"LastName")
SET @Email = field(@row,"Email")
SET @Newsletter = field(@row,"Newsletter__c")
SET @Events = field(@row,"Events__c")
SET @Offers = field(@row,"Offers__c")
SET @HasOptedOutOfEmail = field(@row,"HasOptedOutOfEmail")
IF @Newsletter == true THEN SET @Newsletterchk = "checked" ENDIF
IF @Events == true THEN SET @Eventschk = "checked" ENDIF
IF @Offers == true THEN SET @Offerschk = "checked" ENDIF
]%%
<h2>Update your profile:</h2>
<form action="%%=RequestParameter('PAGEURL')=%%" method="post">
<label>First name: </label><input type="text" name="firstname" value="%%=v(@FirstName)=%%"><br>
<label>Last name: </label><input type="text" name="lastname" value="%%=v(@LastName)=%%"><br>
<label>Email: </label><input type="text" name="email" value="%%=v(@Email)=%%"><br>
<h2>Update your subscriptions:</h2>
<label>Newsletter: </label><input name="newsletter" type="checkbox" %%=v(@Newsletterchk)=%%><br>
<label>Events: </label><input name="events" type="checkbox" %%=v(@Eventschk)=%%><br>
<label>Offers: </label><input name="offers" type="checkbox" %%=v(@Offerschk)=%%><br>
<input name="submitted" type="hidden" value="true"><br>
<input type="submit" value="Submit">
</form>
%%[ELSE]%%
Sorry, something went wrong (no records found).
%%[ENDIF ELSE]%%
Sorry, something went wrong (missing subscriber key).
%%[ENDIF]%%

You will also notice in the above script, that we have added a form action. The RequestParameter('PAGEURL') function reloads the page when the form is submitted, posting the form parameters back to the same page. You can, of course, post the form data to another page and process it there, but for the purpose of this tutorial, let’s keep everything on one page so that it’s easier to copy and paste.

After the form is submitted and posted, the data can be retrieved by using the RequestParameter AMPscript function. We will then update the data in Sales/Service Cloud using the UpdateSingleSalesforceObject function. Let’s add them now:

%%[
SET @contactId = _subscriberkey
IF RequestParameter("submitted") == true THEN
/* update contact */
SET @updateRecord = UpdateSingleSalesforceObject(
"Contact", @contactId,
"FirstName", RequestParameter("firstname"),
"LastName", RequestParameter("lastname"),
"Email", RequestParameter("email"),
"Newsletter__c", Iif(RequestParameter("newsletter") == "on", "true", "false"),
"Events__c", Iif(RequestParameter("events") == "on", "true", "false"),
"Offers__c", Iif(RequestParameter("offers") == "on", "true", "false")
)
Output(v('<font color="green">Contact has been updated succesfully.</font><br>'))
ENDIF
IF NOT EMPTY(@contactId) THEN
/* fetch data from Sales Cloud to show in the form */
SET @subscriberRows = RetrieveSalesforceObjects(
"Contact",
"Id,FirstName,LastName,Email,Newsletter__c,Events__c,Offers__c,HasOptedOutOfEmail",
"Id", "=", @contactId
)
IF RowCount(@subscriberRows) > 0 THEN
SET @row = row(@subscriberRows,1)
SET @FirstName = field(@row,"FirstName")
SET @LastName = field(@row,"LastName")
SET @Email = field(@row,"Email")
SET @Newsletter = field(@row,"Newsletter__c")
SET @Events = field(@row,"Events__c")
SET @Offers = field(@row,"Offers__c")
SET @HasOptedOutOfEmail = field(@row,"HasOptedOutOfEmail")
IF @Newsletter == true THEN SET @Newsletterchk = "checked" ENDIF
IF @Events == true THEN SET @Eventschk = "checked" ENDIF
IF @Offers == true THEN SET @Offerschk = "checked" ENDIF
]%%
<h2>Update your profile:</h2>
<form action="%%=RequestParameter('PAGEURL')=%%" method="post">
<label>First name: </label><input type="text" name="firstname" value="%%=v(@FirstName)=%%"><br>
<label>Last name: </label><input type="text" name="lastname" value="%%=v(@LastName)=%%"><br>
<label>Email: </label><input type="text" name="email" value="%%=v(@Email)=%%"><br>
<h2>Update your subscriptions:</h2>
<label>Newsletter: </label><input name="newsletter" type="checkbox" %%=v(@Newsletterchk)=%%><br>
<label>Events: </label><input name="events" type="checkbox" %%=v(@Eventschk)=%%><br>
<label>Offers: </label><input name="offers" type="checkbox" %%=v(@Offerschk)=%%><br>
<input name="submitted" type="hidden" value="true"><br>
<input type="submit" value="Submit">
</form>
%%[ELSE]%%
Sorry, something went wrong (no records found).
%%[ENDIF ELSE]%%
Sorry, something went wrong (missing subscriber key).
%%[ENDIF]%%

Congratulations – you just built the first, simple version of your profile and subscription center!

Now let’s analyze the above script. The first thing that you will notice is that all the AMPscript is placed at the beginning of the document, above the form – that’s because the form data first has to be posted before it can be processed, and upon posting the data, the page reloads and starts resolving the script from top-down. If you need more clarification on this concept, click here.

If the form has been submitted, we will update the Sales/Service Cloud contact with the new data that was posted upon form submission. The form data is retrieved using the RequestParameter function, which is used in the @updateRecord variable inside the UpdateSingleSalesforceObject function.

You will also notice, that for each of the checkboxes, I have added the following in-line IF function: Iif(RequestParameter("newsletter") == "on", "true", "false"). That is because the checkbox passed from the HTML form will have a value of either on or off, while to pass it to Sales/Service Cloud, we need to convert it to a boolean value of true or false.

Unsubscribe From All option

To make the functionality of our custom subscription and profile center similar to the standard one, we can add the Unsubscribe From All and Resubscribe options. The Unsubscribe From All option should not only set all the subscription-related flags in Sales Cloud to false, but also set the subscriber’s status to Unsubscribed in Salesforce Marketing Cloud’s All Subscribers list. To achieve this, we need to log an UnsubEvent. In order to do that, we will add an additional button to the form (Unsubscribe From All) and a hidden parameter which will be passed if someone clicks this button (name="unsub" type="hidden" value="true"). After submitting the form, if unsub value equals true, we will log the UnsubEvent in addition to updating the flags in Sales/Service Cloud. The below is a simplified version of logging an UnsubEvent and it will unsubscribe the contact from all BUs – if you would like to modify it to your use case, refer to this article: Unsubscribe and Log an UnsubEvent with a LogUnsubEvent Execute Call. Here’s what we need to add to our script to make this part work:

IF RequestParameter("submitted") == true AND RequestParameter("unsub") == true THEN
/* update contact in Sales Cloud */
SET @updateRecord = UpdateSingleSalesforceObject(
"Contact", @contactId,
"Newsletter__c", "false",
"Events__c", "false",
"Offers__c", "false",
"HasOptedOutOfEmail", "true"
)
/* log unsubscribe event to mark as unsubscribed in All Subscribers */
SET @reason = "Unsubscribed through custom subscription center"
SET @lue = CreateObject("ExecuteRequest")
SETObjectProperty(@lue,"Name","LogUnsubEvent")
SET @lue_prop = CreateObject("APIProperty")
SETObjectProperty(@lue_prop, "Name", "SubscriberKey")
SETObjectProperty(@lue_prop, "Value", @contactId)
AddObjectArrayItem(@lue, "Parameters", @lue_prop)
SET @lue_prop = CreateObject("APIProperty")
SETObjectProperty(@lue_prop, "Name", "Reason")
SETObjectProperty(@lue_prop, "Value", @reason)
AddObjectArrayItem(@lue, "Parameters", @lue_prop)
SET @lue_statusCode = InvokeExecute(@lue, @overallStatus, @requestId)

Since we have the option to Unsubscribe From All, we now also have to add an option to Resubscribe. The Resubscribe option will set all the subscription-related flags in Sales Cloud to true and set the subscriber’s status to Active in Salesforce Marketing Cloud’s All Subscribers list. The Resubscribe button will appear for any contact that has the HasOptedOutOfEmail flag set to true in Sales Cloud. The button also has a hidden parameter that will be passed with the form (name="sub" type="hidden" value="true"). Upon clicking the button, we will invoke the update method on a subscriber object to set their status to Active in Marketing Cloud, as well as update their subscription-related flags in Sales Cloud:

ELSEIF RequestParameter("submitted") == true AND RequestParameter("sub") == true THEN
/* update contact in Sales Cloud */
SET @updateRecord = UpdateSingleSalesforceObject(
"Contact", @contactId,
"FirstName", RequestParameter("firstname"),
"LastName", RequestParameter("lastname"),
"Email", RequestParameter("email"),
"Newsletter__c", "true",
"Events__c", "true",
"Offers__c", "true",
"HasOptedOutOfEmail", "false"
)
/* set subscriber status to active in All Subscribers */
SET @email = RequestParameter("email")
SET @Subscriber = CreateObject("Subscriber")
SetObjectProperty(@Subscriber, "SubscriberKey", @contactId)
SetObjectProperty(@Subscriber, "EmailAddress", @email)
SetObjectProperty(@Subscriber, "Status", "Active" )
SET @Status = InvokeUpdate(@Subscriber, @createErrDesc, @createErrNo, @createOpts)

Update subscriber’s data in Marketing Cloud

If you’re using Marketing Cloud Connect, a very important thing to remember when updating subscriber’s email address is to do it in both, Sales/Service Cloud and Marketing Cloud. Here is why:

If an email address for a Lead or Contact Object is updated in Sales Cloud or Service Cloud, the corresponding email address is not updated in the All Subscribers list.

Eliot Harper, THE DATA HANDBOOK – Data Architecture for Salesforce Marketing Cloud

In order to achieve this, we will add one more piece of a script to update the subscriber object. First we will check, if the subscriber’s current email address in Marketing Cloud is different from the one provided in the form, and if it is, we will update it accordingly:

/* update email in All Subscribers list */
IF RequestParameter("email") != AttributeValue("emailaddr") THEN
SET @email = RequestParameter("email")
SET @Subscriber = CreateObject("Subscriber")
SetObjectProperty(@Subscriber, "SubscriberKey", @contactId)
SetObjectProperty(@Subscriber, "EmailAddress", @email)
SET @Status = InvokeUpdate(@Subscriber, @createErrDesc, @createErrNo, @createOpts)
ENDIF

You do not need to include the above if you already have a process in place that synchronizes email address changes from Sales/Service Cloud to All Subscribers list in Salesforce Marketing Cloud. If you’d like to learn more about synching updates between the clouds, read Markus Slabina’s blog post here.

Custom subscription and profile center full script

As a final touch, to improve the experience, let’s add a confirmation message and a button that will reload the page and display the updated data in the form. Here is the final version of the script, with all of the above changes included:

<!––– Copyright © 2020 Zuzanna Jarczynska http://sfmarketing.cloud –––>
%%[
SET @contactId = _subscriberkey
IF NOT EMPTY(@contactId) THEN
/* fetch data from Sales Cloud to show in the form */
SET @subscriberRows = RetrieveSalesforceObjects(
"Contact",
"Id,FirstName,LastName,Email,Newsletter__c,Events__c,Offers__c,HasOptedOutOfEmail",
"Id", "=", @contactId
)
IF RowCount(@subscriberRows) > 0 THEN
SET @row = row(@subscriberRows,1)
SET @FirstName = field(@row,"FirstName")
SET @LastName = field(@row,"LastName")
SET @Email = field(@row,"Email")
SET @Newsletter = field(@row,"Newsletter__c")
SET @Events = field(@row,"Events__c")
SET @Offers = field(@row,"Offers__c")
SET @HasOptedOutOfEmail = field(@row,"HasOptedOutOfEmail")
IF @Newsletter == true THEN SET @Newsletterchk = "checked" ENDIF
IF @Events == true THEN SET @Eventschk = "checked" ENDIF
IF @Offers == true THEN SET @Offerschk = "checked" ENDIF
ELSE
]%%
Sorry, something went wrong (no records found).
%%[
ENDIF
ELSE
]%%
Sorry, something went wrong (missing subscriber key).
%%[
ENDIF
IF RequestParameter("submitted") == true AND RequestParameter("unsub") == true THEN
/* update contact in Sales Cloud */
SET @updateRecord = UpdateSingleSalesforceObject(
"Contact", @contactId,
"Newsletter__c", "false",
"Events__c", "false",
"Offers__c", "false",
"HasOptedOutOfEmail", "true"
)
/* log unsubscribe event to mark as unsubscribed in All Subscribers */
SET @reason = "Unsubscribed through custom subscription center"
SET @lue = CreateObject("ExecuteRequest")
SETObjectProperty(@lue,"Name","LogUnsubEvent")
SET @lue_prop = CreateObject("APIProperty")
SETObjectProperty(@lue_prop, "Name", "SubscriberKey")
SETObjectProperty(@lue_prop, "Value", @contactId)
AddObjectArrayItem(@lue, "Parameters", @lue_prop)
SET @lue_prop = CreateObject("APIProperty")
SETObjectProperty(@lue_prop, "Name", "Reason")
SETObjectProperty(@lue_prop, "Value", @reason)
AddObjectArrayItem(@lue, "Parameters", @lue_prop)
SET @lue_statusCode = InvokeExecute(@lue, @overallStatus, @requestId)
ELSEIF RequestParameter("submitted") == true AND RequestParameter("sub") == true THEN
/* update contact in Sales Cloud */
SET @updateRecord = UpdateSingleSalesforceObject(
"Contact", @contactId,
"FirstName", RequestParameter("firstname"),
"LastName", RequestParameter("lastname"),
"Email", RequestParameter("email"),
"Newsletter__c", "true",
"Events__c", "true",
"Offers__c", "true",
"HasOptedOutOfEmail", "false"
)
/* set subscriber status to active in All Subscribers */
SET @email = RequestParameter("email")
SET @Subscriber = CreateObject("Subscriber")
SetObjectProperty(@Subscriber, "SubscriberKey", @contactId)
SetObjectProperty(@Subscriber, "EmailAddress", @email)
SetObjectProperty(@Subscriber, "Status", "Active" )
SET @Status = InvokeUpdate(@Subscriber, @createErrDesc, @createErrNo, @createOpts)
/* fetch updated data to show in the form */
SET @subscriberRows = RetrieveSalesforceObjects(
"Contact",
"Id,FirstName,LastName,Email,Newsletter__c,Events__c,Offers__c,HasOptedOutOfEmail",
"Id", "=", @contactId
)
SET @row = row(@subscriberRows,1)
SET @FirstName = field(@row,"FirstName")
SET @LastName = field(@row,"LastName")
SET @Email = field(@row,"Email")
SET @Newsletter = field(@row,"Newsletter__c")
SET @Events = field(@row,"Events__c")
SET @Offers = field(@row,"Offers__c")
SET @HasOptedOutOfEmail = field(@row,"HasOptedOutOfEmail")
IF @Newsletter == true THEN SET @Newsletterchk = "checked" ENDIF
IF @Events == true THEN SET @Eventschk = "checked" ENDIF
IF @Offers == true THEN SET @Offerschk = "checked" ENDIF
ELSEIF RequestParameter("submitted") == true THEN
/* update contact */
SET @updateRecord = UpdateSingleSalesforceObject(
"Contact", @contactId,
"FirstName", RequestParameter("firstname"),
"LastName", RequestParameter("lastname"),
"Email", RequestParameter("email"),
"Newsletter__c", Iif(RequestParameter("newsletter") == "on", "true", "false"),
"Events__c", Iif(RequestParameter("events") == "on", "true", "false"),
"Offers__c", Iif(RequestParameter("offers") == "on", "true", "false")
)
/* update email in All Subscribers list */
IF RequestParameter("email") != AttributeValue("emailaddr") THEN
SET @email = RequestParameter("email")
SET @Subscriber = CreateObject("Subscriber")
SetObjectProperty(@Subscriber, "SubscriberKey", @contactId)
SetObjectProperty(@Subscriber, "EmailAddress", @email)
SET @Status = InvokeUpdate(@Subscriber, @createErrDesc, @createErrNo, @createOpts)
ENDIF
ENDIF
IF @HasOptedOutOfEmail == true THEN
]%%
<h2>Update your profile:</h2>
<form action="%%=RequestParameter('PAGEURL')=%%" method="post">
<label>First name: </label><input type="text" name="firstname" value="%%=v(@FirstName)=%%"><br>
<label>Last name: </label><input type="text" name="lastname" value="%%=v(@LastName)=%%"><br>
<label>Email: </label><input type="text" name="email" value="%%=v(@Email)=%%"><br>
<h2>Resubscribe</h2>
You have unsubscribed from ALL publications from <i>%%member_busname%%</i>. If you wish to resubscribe, click the Resubscribe button below.
<input name="submitted" type="hidden" value="true"><br>
<input name="sub" type="hidden" value="true"><br><br>
<input type="submit" value="Resubscribe">
</form>
%%[ELSEIF RequestParameter("submitted") == false AND NOT EMPTY(@contactId) AND RowCount(@subscriberRows) > 0 THEN]%%
<h2>Update your profile:</h2>
<form action="%%=RequestParameter('PAGEURL')=%%" method="post">
<label>First name: </label><input type="text" name="firstname" value="%%=v(@FirstName)=%%"><br>
<label>Last name: </label><input type="text" name="lastname" value="%%=v(@LastName)=%%"><br>
<label>Email: </label><input type="text" name="email" value="%%=v(@Email)=%%"><br>
<h2>Update your subscriptions:</h2>
<label>Newsletter: </label><input name="newsletter" type="checkbox" %%=v(@Newsletterchk)=%%><br>
<label>Events: </label><input name="events" type="checkbox" %%=v(@Eventschk)=%%><br>
<label>Offers: </label><input name="offers" type="checkbox" %%=v(@Offerschk)=%%><br>
<input name="submitted" type="hidden" value="true"><br>
<input type="submit" value="Submit"></form>
<br><br>
<h2>Unsubscribe From All</h2>
If you wish to unsubscribe from ALL publications from <i>%%member_busname%%</i>, click the Unsubscribe From All button below.
<form action="%%=RequestParameter('PAGEURL')=%%" method="post">
<input name="submitted" type="hidden" value="true">
<input name="unsub" type="hidden" value="true"><br>
<input type="submit" value="Unsubscribe From All"></form>
%%[ELSEIF RequestParameter("submitted") == true AND NOT EMPTY(@contactId) THEN]%%
<h2>Your profile has been updated.</h2>
Go back to your profile.<br>
<form action="%%=RequestParameter('PAGEURL')=%%" method="post">
<br>
<input type="submit" value="Go Back"></form>
%%[ENDIF]%%

Remember, that you will need to use the CloudPagesURL function to link to the subscription center CloudPage from an email.

Here are some resources if you would like to read further about subscription management best practices:

  • Here you will find an interesting concept of creating Campaigns for each subscription and adding the Contact as a Campaign Member
  • Here you can read how Effective Email Preference Centers Help Keep Subscribers Active and Engaged
  • Solve problems using the subscription-center and preference-center tags on Salesforce Stack Exchange

Questions? Comments?

Leave a comment below or email me at zuzanna@sfmarketing.cloud.


111 thoughts on “Custom profile and subscription center integrated with Sales/Service Cloud

  1. seancarew

    This is great. I love the way the form works. I get the error that…

    Sorry, something went wrong (missing subscriber key).

    How do I pass subscriber/contactkey securely to the page?

    I’d like it to look like ?qs=1231314324.. instead of the actual contact key.

    Thank you for again for posting.

    Like

      1. Bhawani

        Hi Zuzanna,

        I have simply created a landing page name “Testpage” and put the code which is provided by you and when I am publishing the code it’s giving the error: Sorry, something went wrong (missing subscriber key).

        2. Can I use Lead instead of contact with the same code or I need to do some manual steps as well as we are using the lead object?

        Thanks,
        Bhawani

        Like

      2. You need to test the CloudPage against an actual subscriber – the way you’re testing it now, it’s not able to “do” anything because there is no subscriber – thus the message about a missing subscriber key. Best would be to build a simple email with a button leading to that CloudPage and preview the email against an actual subscriber. Here you can find more details: https://help.salesforce.com/articleView?id=mc_es_subscriber_preview_test_send.htm&type=5

        Like

      3. Bhawani

        Hi Zuzanna,

        Thank you for taking the time to help me, it really meant a lot. I am able to test now. I have one question: I have one custom field in the salesforce “opt-In” and want to use for subscribe and unsub form marketing cloud as well as in salesforce. Please can you let me know how I can use this field so on this field basis I want to update this field in subscriber as well in Salesforce?

        Thanks,
        Bhawani

        Like

      4. Hi Bhawani – it’s all described in the article: “Additionally, I have added three read-only checkboxes to the Contact object for subscription management: Newsletter, Offers and Events.” – if you only need one, you can take the other two out from the script and update the one left with the API name of your custom filed.

        Like

  2. seancarew

    I placed the code on a landing page. Using %%=CloudPagesURL(123)=%% to create a link for the email, with the actual page Id of course, I am getting a Server 500 error… What am I missing? my old pref center needed a Mid code… I think I’m pretty close.

    Like

    1. Hi Sean – this is correct, you need to use the CloudPagesURL function to redirect subscribers from the email to your landing page. Using this function will ensure that all the required parameters are passed correctly. If you’re getting the 500 error – could you please verify that you have the following fields available in your Sales Cloud instance: https://sfmarketing.cloud/2020/01/14/custom-profile-and-subscription-center-integrated-with-sales-service-cloud/#fields. If this doesn’t help, you can wrap the whole script on the landing page in a try/catch statement, which should help you debug. Here is the updated version of the script, with the debugging added: https://gist.github.com/zuzannamj/ff92706f9ab4882a48b748d267dec22b

      Like

      1. seancarew

        Thank you – Yes, that was the issue! I added the fields and it works. I am currently trying to create publication list linkage. Any thoughts?

        Like

      2. To interact with Publication Lists, you will need to use Salesforce Marketing Cloud’s SOAP API. Here’s a sample script to retrieve all public Publication Lists from an account: https://gist.github.com/zuzannamj/bef4c040a9402461aa16b989943d9343. In order to update a subscriber’s status on a Publication List, you would have to use the InvokeUpdate method, just like we did in the Subscription Center script to update a subscriber’s status and email address on All Subscribers list. I will consider writing another article to address this topic.

        Like

  3. Alexander

    Hi!
    Thank you!
    It works like magic!
    One thing. When the user clicked unsubscribe this action is not tracked by the send report – Inbox Activity – unsubscribes.

    Is it possible to bypass this limitation?

    Like

    1. Hi Luiz – if you’re using your own unsubscribe process / subscription center, you need to contact support and ask them to abolish the process of checking email compliance with CAN-SPAM act. After that, you will be able to send out emails without the %%profile_center_url%% and other mandatory strings.

      Like

  4. paulina

    Hi! Really interesting blog, I’m very impressed! I have the following question. I want to add additional page before passing data to Salesforce, whenever somebody decides to click unsubscribe from all, in order to ask customer if they really want to unsubscribe from all communication. In this second page they can select Submit or Cancel, where Submit will be pass data to Sales Cloud and Cancel will redirect back to profile (without any changes).

    Like

    1. Hi Paulina!

      This is possible, even using the one-page approach that I used in the article.

      Currently, when someone click “Unsubscribe from all” this script gets resolved:

      IF RequestParameter(“submitted”) == true AND RequestParameter(“unsub”) == true THEN

      /* update contact in Sales Cloud */
      SET @updateRecord = UpdateSingleSalesforceObject(
      “Contact”, @contactId,
      “Newsletter__c”, “false”,
      “Events__c”, “false”,
      “Offers__c”, “false”,
      “HasOptedOutOfEmail”, “true”
      )

      /* log unsubscribe event to mark as unsubscribed in All Subscribers */
      SET @reason = “Unsubscribed through custom subscription center”

      SET @lue = CreateObject(“ExecuteRequest”)
      SETObjectProperty(@lue,”Name”,”LogUnsubEvent”)

      SET @lue_prop = CreateObject(“APIProperty”)
      SETObjectProperty(@lue_prop, “Name”, “SubscriberKey”)
      SETObjectProperty(@lue_prop, “Value”, @contactId)
      AddObjectArrayItem(@lue, “Parameters”, @lue_prop)

      SET @lue_prop = CreateObject(“APIProperty”)
      SETObjectProperty(@lue_prop, “Name”, “Reason”)
      SETObjectProperty(@lue_prop, “Value”, @reason)
      AddObjectArrayItem(@lue, “Parameters”, @lue_prop)

      SET @lue_statusCode = InvokeExecute(@lue, @overallStatus, @requestId)

      To add an additional step, you would have to replace the code inside this condition with two buttons:

      IF RequestParameter(“submitted”) == true AND RequestParameter(“unsub”) == true THEN]%%

      <form action="%%=RequestParameter('PAGEURL')=%%" method="post">
      <input name="submitted" type="hidden" value="true">
      <input name="unsuball" type="hidden" value="true"><br>
      <input type="submit" value="Unsubscribe From All"></form>

      <form action="%%=RequestParameter('PAGEURL')=%%" method="post">
      <input type="submit" value="Cancel"></form>

      And add an additional condition to be resolved if someone clicks on “Unsubscribe from all”:

      ELSEIF RequestParameter(“submitted”) == true AND RequestParameter(“unsuball”) == true THEN

      /* update contact in Sales Cloud */
      SET @updateRecord = UpdateSingleSalesforceObject(
      “Contact”, @contactId,
      “Newsletter__c”, “false”,
      “Events__c”, “false”,
      “Offers__c”, “false”,
      “HasOptedOutOfEmail”, “true”
      )

      /* log unsubscribe event to mark as unsubscribed in All Subscribers */
      SET @reason = “Unsubscribed through custom subscription center”

      SET @lue = CreateObject(“ExecuteRequest”)
      SETObjectProperty(@lue,”Name”,”LogUnsubEvent”)

      SET @lue_prop = CreateObject(“APIProperty”)
      SETObjectProperty(@lue_prop, “Name”, “SubscriberKey”)
      SETObjectProperty(@lue_prop, “Value”, @contactId)
      AddObjectArrayItem(@lue, “Parameters”, @lue_prop)

      SET @lue_prop = CreateObject(“APIProperty”)
      SETObjectProperty(@lue_prop, “Name”, “Reason”)
      SETObjectProperty(@lue_prop, “Value”, @reason)
      AddObjectArrayItem(@lue, “Parameters”, @lue_prop)

      SET @lue_statusCode = InvokeExecute(@lue, @overallStatus, @requestId)

      Hope this helps 🙂

      Like

  5. Hi Zuzanna –

    How do you deal with sending an email to the first time? Do you default all of the checkboxes in Salescloud to true? We currently use publication lists, but are moving away from that. So if I was going to send an email to the “events” list using a data extension. In the publication list model, it would add them to the list. How do you “opt-in” the contact and check that box in Salescloud?

    Like

    1. Hi Evan – I always set up a double opt-in process, which means that when a subscriber signs up for a newsletter/subscription, either using a webform or during a phone call with a sales agent, or any other way – this would trigger sending the double opt-in email for that particular newsletter/subscription type, and once they confirm, the relevant checkbox would be checked in Salesforce and depending on whether you are using Data Extensions or Publication lists, they would be added to that DE or list.

      Like

  6. Paula

    Hey Zuzanna, i have a question. I used tour script and working properly. However I have another functionality that I tried to add, when somebody untick all of the preferences, should be redirected to the page where will be question “do you want to unsubscribe” yes/no. If yes all values will be empty and master unsub. But if not, he should come back to orginal preference center with old values (not empty). I tested it with request parameter == or != but is not working. Do you have an idea if it can be done via amp?

    Like

    1. Hi Paula – yes, this can be done using AMPscript. You would need to add a few more variables (depending on how many checkboxes you have), for example, if you have a @Newsletter variable you could add an additional variable called @OldNewsletter or something similar and store the “original” preferences for that person in that variable. Then, using the same logic you would use to switch between different pages, pass those along as hidden form fields and if they eventually come back to the original preference centre, retrieve them as you would for the “normal” checkboxes, so: SET @Newsletter = RequestParameter(“OldNewsletter”). Hope this helps 🙂

      Like

  7. Hi Zuzanna

    Thank you for your blog, your articles are very helpful. I have used this article to create a custom subscription centre for the company I work for but I am having an unexpected result from a call to the Marketing Cloud API ListSubscriber object.

    I am attempting to query the ListSubscriber object for the subscription status as documented here: https://developer.salesforce.com/docs/atlas.en-us.noversion.mc-apis.meta/mc-apis/retrieving_all_lists_a_subscriber_is_on.htm.

    The user is subscribed to 5 publication lists, 4 of which are public and 1 private.

    This is the code I am using to make the API call.

    /* users subscription status info */
    var @rrsub, @sfpsub, @substatus

    SET @rrsub = CreateObject(“RetrieveRequest”)
    SetObjectProperty(@rrsub,”ObjectType”,”ListSubscriber”)
    AddObjectArrayItem(@rrsub, “Properties”, “ListID”)
    AddObjectArrayItem(@rrsub, “Properties”, “SubscriberKey”)
    AddObjectArrayItem(@rrsub, “Properties”, “Status”)

    SET @sfpsub = CreateObject(“SimpleFilterPart”)
    SetObjectProperty(@sfpsub,”Property”,”SubscriberKey”)
    SetObjectProperty(@sfpsub,”SimpleOperator”,”equals”)
    AddObjectArrayItem(@sfpsub,”Value”,@email)

    SetObjectProperty(@rrsub,”Filter”,@sfpsub)
    SET @substatus = InvokeRetrieve(@rrsub, @status, @requestID)
    output(concat(“rrSubStatus: “, @status))
    output(concat(“rrSubRequestID: “, @requestID))

    for @i = 1 to RowCount(@substatus) do
    set @substat = Row(@substatus, @i)
    set @ListID = Field(@substat, “ListID”)
    set @SubscriberKey = Field(@substat, “SubscriberKey”)
    set @Status = Field(@substat, “Status”)

    output(concat(“ListID: “, @ListID))
    output(concat(“SubscriberKey: “, @SubscriberKey))
    output(concat(“Status: “, @Status))
    next @i

    This is the information being returned by the API call.
    rrSubStatus: OK
    rrSubRequestID: 1e8d1b43-679c-40a1-bebc-xxxxxxxxxxxx
    ListID: 102
    SubscriberKey: xxxxxxx@gmail.com
    Status: Active
    ListID: 409
    SubscriberKey: xxxxxxx@gmail.com
    Status: Active

    There are only two list ID’s returned, ID: 102 is the All Subscribers list and ID: 409 is the private publication list. There are four other publication lists the user is subscribed to that are not being returned.

    I made the API call to the List object to confirm the other publication lists are visible. They are returned as follows:
    rrStatus: OK
    rrRequestID: b2e512b6-af6e-443c-b0fb-xxxxxxxxxxxx
    ListName: Partner Promotions
    ListID: 376
    ListName: Feature Updates
    ListID: 377
    ListName: Administrators Only
    ListID: 378
    ListName: Monthly Newsletters
    ListID: 539

    Do you know why this would be happening, have I missed something?
    Thank you in advance for your time.

    Like

  8. For those experiencing similar issues I found the problem.
    There were two issues causing incomplete list data being returned.
    Firstly there were several instances of the same email each with different subscriber keys in Marketing Cloud due to reasons I will not go into here.
    When setting the simple filter I was using the email as the subscriber key. As there were several instances of that email the I was getting back only the lists that all of the instances were subscribed too.
    By changing the filter to use the subscriber key as the subscriber key the required data was returned.

    Like

  9. Ash

    Hi, this article has been a great help! i’ve managed to create a preference centre for the person account using the above, but now i have a requirement to use the same preference centre for the same preferences that exist as leads.
    What would be the most efficient way to do this? would i duplicate the AMPScipt for leads now or is there a shorter way?

    Thanks in advance!

    Like

    1. Hi Ash, no need to copy/rewrite the code.
      I usually add an additional condition in the script, right before the updates in Salesforce are made, to check whether the subscriber coming in is a Lead or a Contact, and then update accordingly:

      /* check if it’s a Lead or a Contact */
      IF Substring(@subscriber_key, 1,3) == “00Q” THEN
      /* Update Lead */
      SET @update_lead = UpdateSingleSalesforceObject(…)
      ELSE
      /* Update Contact */
      SET @update_contact = UpdateSingleSalesforceObject(…)
      ENDIF

      Like

    2. Bhawani

      Hi Ash,

      Have you made the custom preference page for lead and contact? Please can you let me know how we can achieve this? I have also similar functionality but not able to find out where I need to check the lead and contact id?

      Thanks in advance.

      Like

      1. Casey Davis

        This can be done by using the code Zuzanna generously provided. Additionally you will need to use the sub string logic to retrieve the data from the leads differently. Pseudo code:

        IF NOT EMPTY(@contactId) AND @substringContactID != ’00Q’ THEN

        /* fetch data from Sales Cloud to show in the form */

        SET @subscriberRows = RetrieveSalesforceObjects(
        “Contact”, …
        … ELSEIF NOT EMPTY(@contactId) AND @substringContactID == “00Q” THEN
        SET @subscriberRows = RetrieveSalesforceObjects(
        “Lead”, …

        if you’re retrieving items like City, Zip, and other address information, this is stored differently for Leads. For contacts they are MailingCity, MailingPostalCode, etc where leads have this info as City, PostalCode, etc.

        Thank you Zuzanna for the write up on this. I can’t express how helpful this has been for building out our preference center.

        Like

  10. Anna

    Hi Zuzanna! Thank you so much for sharing this code, it was extremely helpful!
    I just wanted to clarify on thing, maybe I adapted to code for person account wrong, but after unsubscribing from all I get to the resubscribe “page” and after I click resubscribe button I see the same page with “resubscribe” and to go to the page with the “go back” button that confirms that changes were appended I need to click on re-subscribe again. So in general I need to click twice. Maybe you would have an idea what can be wrong?

    Like

    1. Anna

      ah, I am sorry, my bad, I missed one part of the code, everything is working perfectly!
      btw, do you hold any knowledge sharing session? 🙂

      Like

    2. Joanna

      A question – can this be utilized when sending emails from service cloud?
      We want to give our contacts ability to manage their subscription when an email from service cloud is sent.
      Customer has marketing cloud as well.

      Like

  11. Amr Shehata

    Hi Zuzanna,

    Thank you for your blog. Really helpful. I am getting still the ‘ Missing subscriber key” message. I have created a test email, linked the cloud page to that email and sent a preview message to a DE that has an actual subscriber but still getting the same message. Could you please tell what else could it be ? Many thanks

    Like

    1. Hi Amr,
      This error message means that the subscriber key doesn’t get passed from the email to the CloudPage. Without seeing the code in both the email and the CloudPage, it’s really hard to troubleshoot. I would encourage you to take it to Salesforce Stack Exchange (https://salesforce.stackexchange.com/) and paste your code there – the community is usually very fast with resolving AMPscript issues.
      Zuzanna

      Like

  12. Nicole N

    Zuzanna,

    This is very helpful and easy to follow, thank you! Are we able to also create a new contact if the contact does not already exist?

    I would like to use this same web page for updating existing contacts and creating new subscribers/contacts, but I don’t think that is possible. I’m thinking if the user accesses the page via an email, the subscriber data will be available and if they access from a link on a webpage, there will be no contact data and a new contact can be created in Service Cloud?

    Any thoughts would be appreciated.

    Thank you!
    Nicole

    Like

  13. Nicole Nicholson

    Thank you Zuzanna! Are you only able to create Leads using at code and form, or can the ‘create Lead’ be changed to ‘create Contact’?

    Nicole

    Like

  14. Doru

    Hi Zuzanna,
    thanks for your blog but I have a question, where I can change the existing page? i search where I can edit the HTML code for the Preferences page but I didn’t find it, thanks in advance

    Like

      1. Doru

        Hi, yes, I want to change the default Preferences page with a custom page which I coded, but I cant find where can I do it, where can I change the HTML for the present page with my HTML

        Like

      2. You cannot edit the default Preference Center – if you have creates your own custom one, simply replace the links in your emails: replace %%subscription_center_url%% with a %%=CloudPagesURL(123)=%% leading to your custom preference/subscription CloudPage

        Like

  15. Maurice

    Hi, this was helpful! I am getting the following error:
    Sorry, something went wrong (no records found).

    I am testing via email with a contact , so the subkey is getting passed but I’m getting the an error still.

    Like

  16. Hi, this was helpful! I am getting the following error:
    Sorry, something went wrong (no records found).

    I am testing via email with a contact , so the subkey is getting passed but I’m getting the an error still.

    Like

  17. Hi Zuzanna! I did have a different question. Are there any changes needed to the AMPscript if I wanted to add styling around the form to ensure it still functions correctly?

    Like

  18. DJ

    Hi Zuzanna!
    This was really helpful and works for me.

    In my current page, First it ask the subscriber if they want to be contacted using Radio button (Yes/No), If it say “YES” then there are different checkbox options (email, post, phone, mobile,etc) but if it say “NO” then all the values (email, post, phone, mobile,etc) should be submitted as false.

    I’m able to submit the checkbox value but not able to work with the radio button (Yes/No). Can you please help?

    Thank you in advanced.

    Like

  19. DJ

    Thank you, Zuzanna!!

    Right now I’m updating only the Lead table. Can I use the same preference page for updating the Lead and the Contact table in Salesforce CRM? If yes, then how can I achieve this?

    Like

  20. Indraja

    Hi Zuzanna,

    Where can I find preference management-related information and how these are synced between marketing cloud and salesforce other clouds like health cloud.

    Like

  21. Kim Kooren

    Hi Zuzanna

    Thank you so much for all your information.
    To check whether it’s a Lead or a Contact, to use your code below, do I place this between the:
    ENDIF and IF RequestParameter (“submitted”) == true AND RequestParameter(“unsub”) == true THEN

    ===
    Also would this work with Person Account as well?

    Thank you so much.

    /* check if it’s a Lead or a Contact */
    IF Substring(@subscriber_key, 1,3) == “00Q” THEN
    /* Update Lead */
    SET @update_lead = UpdateSingleSalesforceObject(…)
    ELSE
    /* Update Contact */
    SET @update_contact = UpdateSingleSalesforceObject(…)
    ENDIF

    Like

  22. integrity777

    Hi Zuzanna! Sorry if this is a double posted…the 1st did not show. Thank you so much for providing this information to help others learn and grow. I am trying to use this for SFMC that is not connected to Sales/Service Cloud. This isn’t exactly working and I tired removing the SalesforceObject code but am unsure what to replace it with to make it functional. I thought maybe you may have that or may be willing to re-write this for a Custom SFMC Standalone Profile/Subscription Center. Do you have that or could you write that by any chance….or provide some modification tips?
    Thank you in advance!
    Frank

    Like

  23. Sharat
    Hi Zuzanna, Thanks for this post. I am getting following error ” Sorry, something went wrong (missing subscriber key). ” when I am trying to publish the page. I have copied code from below link and created a cloud page. .gist table { margin-bottom: 0; } This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters Show hidden characters <!––– Copyright © 2020 Zuzanna Jarczynska http://sfmarketing.cloud –––> %%[ SET @contactId = _subscriberkey IF NOT EMPTY(@contactId) THEN /* fetch data from Sales Cloud to show in the form */ SET @subscriberRows = RetrieveSalesforceObjects( "Contact", "Id,FirstName,LastName,Email,Newsletter__c,Events__c,Offers__c,HasOptedOutOfEmail", "Id", "=", @contactId ) IF RowCount(@subscriberRows) > 0 THEN SET @row = row(@subscriberRows,1) SET @FirstName = field(@row,"FirstName") SET @LastName = field(@row,"LastName") SET @Email = field(@row,"Email") SET @Newsletter = field(@row,"Newsletter__c") SET @Events = field(@row,"Events__c") SET @Offers = field(@row,"Offers__c") SET @HasOptedOutOfEmail = field(@row,"HasOptedOutOfEmail") IF @Newsletter == true THEN SET @Newsletterchk = "checked" ENDIF IF @Events == true THEN SET @Eventschk = "checked" ENDIF IF @Offers == true THEN SET @Offerschk = "checked" ENDIF ELSE ]%% Sorry, something went wrong (no records found). %%[ ENDIF ELSE ]%% Sorry, something went wrong (missing subscriber key). %%[ ENDIF IF RequestParameter("submitted") == true AND RequestParameter("unsub") == true THEN /* update contact in Sales Cloud */ SET @updateRecord = UpdateSingleSalesforceObject( "Contact", @contactId, "Newsletter__c", "false", "Events__c", "false", "Offers__c", "false", "HasOptedOutOfEmail", "true" ) /* log unsubscribe event to mark as unsubscribed in All Subscribers */ SET @reason = "Unsubscribed through custom subscription center" SET @lue = CreateObject("ExecuteRequest") SETObjectProperty(@lue,"Name","LogUnsubEvent") SET @lue_prop = CreateObject("APIProperty") SETObjectProperty(@lue_prop, "Name", "SubscriberKey") SETObjectProperty(@lue_prop, "Value", @contactId) AddObjectArrayItem(@lue, "Parameters", @lue_prop) SET @lue_prop = CreateObject("APIProperty") SETObjectProperty(@lue_prop, "Name", "Reason") SETObjectProperty(@lue_prop, "Value", @reason) AddObjectArrayItem(@lue, "Parameters", @lue_prop) SET @lue_statusCode = InvokeExecute(@lue, @overallStatus, @requestId) ELSEIF RequestParameter("submitted") == true AND RequestParameter("sub") == true THEN /* update contact in Sales Cloud */ SET @updateRecord = UpdateSingleSalesforceObject( "Contact", @contactId, "FirstName", RequestParameter("firstname"), "LastName", RequestParameter("lastname"), "Email", RequestParameter("email"), "Newsletter__c", "true", "Events__c", "true", "Offers__c", "true", "HasOptedOutOfEmail", "false" ) /* set subscriber status to active in All Subscribers */ SET @email = RequestParameter("email") SET @Subscriber = CreateObject("Subscriber") SetObjectProperty(@Subscriber, "SubscriberKey", @contactId) SetObjectProperty(@Subscriber, "EmailAddress", @email) SetObjectProperty(@Subscriber, "Status", "Active" ) SET @Status = InvokeUpdate(@Subscriber, @createErrDesc, @createErrNo, @createOpts) /* fetch updated data to show in the form */ SET @subscriberRows = RetrieveSalesforceObjects( "Contact", "Id,FirstName,LastName,Email,Newsletter__c,Events__c,Offers__c,HasOptedOutOfEmail", "Id", "=", @contactId ) SET @row = row(@subscriberRows,1) SET @FirstName = field(@row,"FirstName") SET @LastName = field(@row,"LastName") SET @Email = field(@row,"Email") SET @Newsletter = field(@row,"Newsletter__c") SET @Events = field(@row,"Events__c") SET @Offers = field(@row,"Offers__c") SET @HasOptedOutOfEmail = field(@row,"HasOptedOutOfEmail") IF @Newsletter == true THEN SET @Newsletterchk = "checked" ENDIF IF @Events == true THEN SET @Eventschk = "checked" ENDIF IF @Offers == true THEN SET @Offerschk = "checked" ENDIF ELSEIF RequestParameter("submitted") == true THEN /* update contact */ SET @updateRecord = UpdateSingleSalesforceObject( "Contact", @contactId, "FirstName", RequestParameter("firstname"), "LastName", RequestParameter("lastname"), "Email", RequestParameter("email"), "Newsletter__c", Iif(RequestParameter("newsletter") == "on", "true", "false"), "Events__c", Iif(RequestParameter("events") == "on", "true", "false"), "Offers__c", Iif(RequestParameter("offers") == "on", "true", "false") ) /* update email in All Subscribers list */ IF RequestParameter("email") != AttributeValue("emailaddr") THEN SET @email = RequestParameter("email") SET @Subscriber = CreateObject("Subscriber") SetObjectProperty(@Subscriber, "SubscriberKey", @contactId) SetObjectProperty(@Subscriber, "EmailAddress", @email) SET @Status = InvokeUpdate(@Subscriber, @createErrDesc, @createErrNo, @createOpts) ENDIF ENDIF IF @HasOptedOutOfEmail == true THEN ]%% <h2>Update your profile:</h2> <form action="%%=RequestParameter('PAGEURL')=%%" method="post"> <label>First name: </label><input type="text" name="firstname" value="%%=v(@FirstName)=%%"><br> <label>Last name: </label><input type="text" name="lastname" value="%%=v(@LastName)=%%"><br> <label>Email: </label><input type="text" name="email" value="%%=v(@Email)=%%"><br> <h2>Resubscribe</h2> You have unsubscribed from ALL publications from <i>%%member_busname%%</i>. If you wish to resubscribe, click the Resubscribe button below. <input name="submitted" type="hidden" value="true"><br> <input name="sub" type="hidden" value="true"><br><br> <input type="submit" value="Resubscribe"> </form> %%[ELSEIF RequestParameter("submitted") == false AND NOT EMPTY(@contactId) AND RowCount(@subscriberRows) > 0 THEN]%% <h2>Update your profile:</h2> <form action="%%=RequestParameter('PAGEURL')=%%" method="post"> <label>First name: </label><input type="text" name="firstname" value="%%=v(@FirstName)=%%"><br> <label>Last name: </label><input type="text" name="lastname" value="%%=v(@LastName)=%%"><br> <label>Email: </label><input type="text" name="email" value="%%=v(@Email)=%%"><br> <h2>Update your subscriptions:</h2> <label>Newsletter: </label><input name="newsletter" type="checkbox" %%=v(@Newsletterchk)=%%><br> <label>Events: </label><input name="events" type="checkbox" %%=v(@Eventschk)=%%><br> <label>Offers: </label><input name="offers" type="checkbox" %%=v(@Offerschk)=%%><br> <input name="submitted" type="hidden" value="true"><br> <input type="submit" value="Submit"></form> <br><br> <h2>Unsubscribe From All</h2> If you wish to unsubscribe from ALL publications from <i>%%member_busname%%</i>, click the Unsubscribe From All button below. <form action="%%=RequestParameter('PAGEURL')=%%" method="post"> <input name="submitted" type="hidden" value="true"> <input name="unsub" type="hidden" value="true"><br> <input type="submit" value="Unsubscribe From All"></form> %%[ELSEIF RequestParameter("submitted") == true AND NOT EMPTY(@contactId) THEN]%% <h2>Your profile has been updated.</h2> Go back to your profile.<br> <form action="%%=RequestParameter('PAGEURL')=%%" method="post"> <br> <input type="submit" value="Go Back"></form> %%[ENDIF]%% view raw custom-subscription-and-profile-center.html hosted with ❤ by GitHub Thanks in Advance, Thanks, Sharat LikeLike
  24. Mario

    Hi Zuzanna, Thanks heaps for the article. It’s really helpful!!

    Just a question: Is there any prerequisite setup in order to make this code works? For example: Do we need to have Marketing Cloud connect to set up to SalesCloud? And, what else or integration we’ll need to have?

    Thanks in advance,
    Mario

    Like

  25. Mario

    Hi Zuzanna, Thanks for this post! Really helpful.

    WIth the %%=RequestParameter(‘PAGEURL’)=%%, do we need to create another cloud pages, and grab the cloud page URL to this? If yes, can you advice which part of the codes should we put in this separate page?

    Thanks,
    Mario

    Like

  26. Mario

    Hi Zuzanna,

    Thanks for all the responses! It does make sense, appreciated it.

    I’ve tried using your codes in the CloudPages, and removed all codes related to the 3x custom checkboxes).

    And, unfortunately, it’s giving me 500 errors. And, when I’m using Try and catch in the console log. The below is what I’m currently getting:

    Do you have any idea what’s causing the issues here?

    “Call to retrieve records for salesforceobject Contact failed! Error in the application.
    API Fault: Salesforce.com Fault thrown.
    Exception Type:UnexpectedErrorFault
    Exception Code:INVALID_QUERY_FILTER_OPERATOR
    Exception Message:
    HasOptedOutOfEmail FROM Contact WHERE Id = ”test-email@gmail.com.au’
    ^
    ERROR at Row:1:Column:74
    invalid ID field: test-email@gmail.com.au“”ExactTarget.OMM.FunctionExecutionException: Call to retrieve records for salesforceobject Contact failed! Error in the application.
    API Fault: Salesforce.com Fault thrown.
    Exception Type:UnexpectedErrorFault
    Exception Code:INVALID_QUERY_FILTER_OPERATOR
    Exception Message:
    HasOptedOutOfEmail FROM Contact WHERE Id = ‘test-email@gmail.com.au’
    ^
    ERROR at Row:1:Column:74
    invalid ID field: ‘test-email@gmail.com.au
    Error Code: RETRIEVESFOJBECTS_FUNC_ERROR

    Thanks again, much appreciated it!!

    Like

    1. It’s pretty self explanatory- the message says you’re trying to retrieve a Salesforce Contact with SF Contact Id “test-email@gmail.com.au” – but SF Contact Ids are always 18 digits and start with “003”. Try working with a real test contact instead of using a test email address.

      Like

      1. Mario

        Hi Zuzanna,

        Thanks again for the quick reply! Much appreciated it. The error does make sense now, thanks.

        FYI, I was actually using the real customer record to test. And, I’ve updated it to “test-email@gmail.com.au” only for privacy reasons.

        At the moment, I have the Marketing Cloud Connect configured, and syncing the data from SalesCloud. But, the tricky part is that we don’t currently have access to their SalesCloud to check the data. The records I’m currently using (which return this error) are from one of the records in the sync data extension. So, I was assuming that this record also exists in SalesCloud.

        Do you reckon this might be something wrong in the Marketing Cloud configurations?

        Much appreciated it,

        Like

  27. Mario

    Thanks, Zuzanna! Below is the codes I’m currently using based on yours. When you mentioned changing the “Contact Id” instead of “Email” as a unique identifier, did you mean I’ll need to make the changes in the codes or in SalesCloud?

    Thank you very much.

    Preference Center

    Platform.Load(“Core”,”1.1.1″);
    try{

    %%[
    SET @contactId = _subscriberkey

    IF NOT EMPTY(@contactId) THEN

    /* fetch data from Sales Cloud to show in the form */

    SET @subscriberRows = RetrieveSalesforceObjects(
    “Contact”,
    “Id,FirstName,LastName,Email,HasOptedOutOfEmail”,
    “Id”, “=”, @contactId
    )

    IF RowCount(@subscriberRows) > 0 THEN

    SET @row = row(@subscriberRows,1)
    SET @FirstName = field(@row,”FirstName”)
    SET @LastName = field(@row,”LastName”)
    SET @Email = field(@row,”Email”)
    SET @HasOptedOutOfEmail = field(@row,”HasOptedOutOfEmail”)
    ]%%

    Update your profile:

    First name:
    Last name:
    Email:

    %%[ELSE]%%

    Errors: No records found!

    %%[ENDIF ELSE]%%

    Errors: Mmissing subscriber key!

    %%[ENDIF]%%

    }
    catch(e) {
    Variable.SetValue(“errorMessage”, Stringify(e.message) + Stringify(e.description));
    }

    console.log(`%%=v(@errorMessage)=%%`);

    Like

    1. I meant that you will need to change the way you test it: in your test data extension, make sure that you have two columns, subscriber key column which contains the contact id and email column which contains the email address. Use that to preview the email that contains the subscription center link.

      Like

  28. Pingback: Best Practices to Optimize SFMC Preference Center - Email Uplers

  29. Jason

    Hi Zuzanna,

    Thanks for post. This works great when Salesforce is the source truce of managing opt-in status. Could you please share an example to retrieve unsubscribe status from Marketing Cloud publication lists instead of retrieving from Salesforce Contact object?

    Thanks,
    Jason

    Like

  30. Paul

    Hi Zuzanna,

    Amazing post! I’ve been able to adapt the code to my requirements and have successfully tested. The only bug I’ve encountered so far is selecting all of the available publication lists on the preference centre and updating. It seems if I only have one of them selected then try to update all of them, the record in Sales Cloud doesn’t update properly. However, when I unsubscribe and resubscribe, all the preferences are selected again and are updated on the Sales Cloud record.

    Thanks,

    Like

  31. Girish

    Quick question on the flow, please. Whenever a user submits their personal info from the Survey Cloudpages. Will it create a record/subscriber in SFMC first before passing that info to Sales Cloud?

    Like

  32. Avril

    Creating this custom profile center does this work alongside marketing cloud connect? As a way to capture the information or do I need to capture the information straight from the synchronized DE instead?

    Like

      1. Avril

        Great thank you! So this just updates preferences subscriber found coming through marketing cloud connect, not creating any new subscribers. And it does not create any duplicates correct?

        Like

  33. Avril

    This us super helpful thank you! How can we get custom checkbox fields like Newsletter changes to sync back to Marketing Cloud publication lists? Do you have any information on that?

    Like

  34. Avril

    Should this be input after the “IF RequestParameter(“submitted”) == true AND RequestParameter(“unsub”) == true THEN” section and before the /* log unsubscribe event to mark as unsubscribed in All Subscribers */
    SET @reason = “Unsubscribed through custom subscription center” or where should it be placed?

    Like

    1. You’d have to include it in two places, after:

      ELSEIF RequestParameter(“submitted”) == true AND RequestParameter(“sub”) == true THEN

      and after:

      ELSEIF RequestParameter(“submitted”) == true THEN

      But be aware that this is not a copy and paste, you’ll need to adjust it to your needs

      Like

      1. Avril

        I see in the link you provided it includes subscriber key, what do I input there if it is not going to be for just one subscriber?

        Like

      2. It needs to be for one subscriber at a time: the subscriber who submits the form, meaning the subscriber who changes their preferences on the preference center cloud page. Once the form is submitted by the subscriber, the preferences get overwritten in Sales Cloud and now you’re adding an additional script to also overwrite them on a publication list in Marketing Cloud

        Like

  35. Arthur

    Hi Zuzanna,

    Very usefull post! Thank you!

    I’ve built the same code in my MC org, add DE with Contact Id, email address and subscriber key(same as Contact Id). I have no problem during preview: I can see all attributes and values according the DE in Profile Center, but when I open the link from email (after Send) I got 500 error..

    Do you have any idea what I did wrong? 🙂

    Like

    1. There’s a few things you could do:
      – create a report in Sales Cloud and use it for sending SFMC emails
      – create a filtered DE from the Synchronized DE for each subscription type
      – use sql to create Sendable DEs for each subscription type using data from Synchronized DEs

      Like

  36. shreyas

    Hi zuzanne,

    I am having issue getting “error as 500 Internal server error”

    I have added this line in my email template to redirect to the Cloud Page,

    Custom Update Profile

    And I am using only one field for now that is Email field which is available in Salesforce object

    Like

    1. shreyas

      I have added this below line in a href tag in my email template to redirect to the Cloud Page,
      %%=RedirectTo(CloudPagesURL(950, ‘subscriberkey’, @_subscriberkey))=%%

      Like

      1. Avril

        Do i simply change anything that says contact to person account and from Id to person contact id and __c to __pc

        Like

  37. avril

    Sorry, something went wrong: “Call to retrieve records for salesforceobject PersonAccount failed! Error in the application.\nAPI Fault: Salesforce.com Fault thrown.\n\tException Type:InvalidSObjectFault\n\tException Code:INVALID_TYPE\n\tException Message:sObject type ‘PersonAccount’ is not supported. If you are attempting to use a custom object, be sure to append the ‘__c’ after the entity name. Please reference your WSDL or the describe call for the appropriate names.\n\tAt row:-1 and column: -1”

    here is the error when i try to all PersonAccount

    Like

  38. Avril

    Changed the object to Account and now when i open the link, nothing pops up, not even the string sorry something went wrong:

    Like

  39. George Shams

    hi Zuzannam
    can i modify the function to fetch the customer information basred on email address instead if contact ID

    %%[
    SET @contactId = _subscriberkey
    IF NOT EMPTY(@contactId) THEN
    /* fetch data from Sales Cloud to show in the form */

    SET @subscriberRows = RetrieveSalesforceObjects( “Contact”, “Id,FirstName,LastName,Email,Newsletter__c,Events__c,Offers__c,HasOptedOutOfEmail”, “Id”, “=”, @contactId )

    to be modified to :

    SET @Emailval = _subscriberkey

    SET @subscriberRows = RetrieveSalesforceObjects( “Contact”, “Id,FirstName,LastName,Email”, “Email”, “=”, @Emailval )

    Like

  40. Ally Ryman

    This is so great- do you have any links so I can modify the code so that it can also be updated in Salesforce too? I am trying to build my knowledge so apologies if i missed anything from the comments above. (Also your explanations are so clear in this resource – thank you!!)

    Like

      1. Ally

        Thank you so much for getting back to me – this is the bit that I was referring to – Apologies, I hope its clearer : )

        [Why are they read-only? In this tutorial, you will learn how to allow subscribers to make changes to their preferences and sync those changes back to Salesforce. The process of managing the subscriptions by a Salesforce user and syncing the changes back to Marketing Cloud is a separate procedure and won’t be addressed in this article.]

        Like

Leave a comment