← Back to Insights

CloudPages: the URL parameter that exposes your customers' data

Pierre Frin September 2026 8 min read
barre d'adresse de la page de coordonnées /coordonnees?id=482173 un chiffre modifié /coordonnees?id=482174 coordonnées d'un autre adhérent Marketing Cloud Engagement · CloudPages · violation de données personnelles

A health insurer sends an email once a year inviting members to check their contact details. The button leads to a CloudPage showing the postal address, the phone number and the email address, with a form for changes. The link was built quickly, a few days before the send, with the membership number placed straight into the address.

Three weeks later, a member writes to the data protection officer. He had spotted his number in the address bar, replaced it with a neighbouring number, and seen someone else's contact details appear. The numbers run in sequence, so anyone could browse the file.

The page worked perfectly in testing, and nothing in its code was wrong in technical terms. It read a parameter, looked up the matching row and displayed it. The flaw sits entirely in the trust placed in that parameter, and it counts as a personal data breach, with the documentation and notification duties that follow.

A URL travels further than you think

A URL does not stay in the browser of the person you sent it to. It passes through browsing history, forwarded emails, corporate proxy logs, analytics tools, and the header sent to the sites a page calls. Treat everything it carries as known to third parties.

What Salesforce forbids in a link

Salesforce is blunt about it: do not pass a SubscriberID, a Subscriber Key or a Contact Key in clear text, and do not include personal data such as the email address in links.

How the identifier is passedReadable by a third partyEditable by handVerdict
Plain number in the query stringYesYesNever do this
Base64 or hexadecimal encodingYes, decoded in a secondYesNever do this, it is not encryption
CloudPagesURLNoNo, an altered string will not decryptThe recommended method from an email
EncryptSymmetric with a managed keyNoNoFor links built outside an email

⚠️ Base64 is not encryption: Salesforce names that encoding and hexadecimal conversion as insufficient. An encoded identifier is decoded, incremented and re-encoded in seconds. The only thing you gain is a delay before the flaw is found.

Building the link from the email

The CloudPagesURL function takes the page identifier, then name and value pairs, and returns the address with an encrypted query string. According to the documentation, that string also carries a reference to the originating email, which makes personalisation strings usable on the page.

%%[
SET @adherentId = AttributeValue("AdherentId")
]%%
<a href="%%=RedirectTo(CloudPagesURL(4521, 'cle', @adherentId, 'v', '2026'))=%%">
  Vérifier mes coordonnées
</a>

The cle parameter duplicates the context carried by the link, and that is deliberate: it will serve as a second check on the page. The v parameter identifies the campaign and lets you reject links that are too old. No data appears in clear text in the resulting address.

Reserved parameters and other channels

Some parameter names are reserved and cannot be used, among them PAGEID, MID, JID, LID, SID, JSB and URLID. Two notes for the other channels: MicrositeURL plays the same role for classic content microsites, and on an SMS or a push notification, the page returns an error when the recipient is not present in All Subscribers.

A check at the top of the page

On the page side, two functions read the parameters received. The official documentation describes RequestParameter and QueryParameter in the same terms, and presents the second as kept for compatibility. Use the first everywhere, the code reads better. A missing value raises no error, the function returns an empty string, which is handy for testing and dangerous when the code moves straight on to a lookup without checking.

Server side validation rules

The Trailhead security module sets out simple rules that are enough to avoid the incident. All validation happens server side. A page that handles data requires authentication. You check with at least two parameters that the same subscriber is the one interacting with the page. A public page opens with a global check that stops everything when a required parameter is missing.

%%[
SET @cle     = RequestParameter("cle")
SET @version = RequestParameter("v")
SET @ctxCle  = _subscriberkey
SET @acces   = "refuse"
SET @motif   = ""

IF Empty(@cle) OR Empty(@ctxCle) THEN
  SET @motif = "parametre_absent"
ELSEIF RegExMatch(@cle, "^[0-9]{6,10}$", 0) == "" THEN
  SET @motif = "format_invalide"
ELSEIF @cle != @ctxCle THEN
  SET @motif = "incoherence_contexte"
ELSEIF @version != "2026" THEN
  SET @motif = "lien_perime"
ELSE
  SET @rows = LookupRows("Adherents", "AdherentId", @cle)
  IF RowCount(@rows) == 1 THEN
    SET @acces = "ok"
    SET @ligne = Row(@rows, 1)
  ELSE
    SET @motif = "adherent_inconnu"
  ENDIF
ENDIF
]%%

Each condition has a precise job. The first blocks direct access to the page without a link. The second rejects a value that does not have the shape of an identifier. The third checks that the parameter and the context point to the same person, which is the check that was missing in the incident. The last confirms that the row exists, and only once.

Refusing with a neutral message

When access is refused, keep the reason for the log and show a neutral message, identical whatever the reason. Telling the visitor on screen that a member cannot be found confirms to a third party whether an identifier exists, and echoes back a received value without processing. The log itself holds neither the key received nor the IP address: it exists to measure the volume of refusals by reason, not to identify people. A sharp rise in the context mismatch reason after a send points to a badly built link, while a rise in the format reason with no recent send points to someone probing the page.

💡 Display only what you have read: in the authorised branch, the page shows only values taken from the data extension, never values received in the request. A parameter echoed back as it arrived is the classic entry point for cross-site scripting.

What an encrypted link does not protect

Encryption stops someone forging a link or changing its contents. It does not stop them using it. An email forwarded to a colleague, a shared mailbox, a borrowed phone, and the page opens exactly as it does for the intended recipient. The design question is therefore not whether the link is encrypted, but what someone who holds the link without being the right person gets to see.

The answer dictates what the page contains. An encrypted link is fine for showing little data and for low stakes actions, communication preferences for example. For sensitive data or a high impact action, send people to the authenticated customer area or ask for a second check. In between, mask part of what you display, a phone number reduced to its last two digits is enough to confirm that it is up to date.

The HTTP security headers

That leaves the HTTP layer, which is set at the top of the page and tested once. Salesforce recommends serving pages over HTTPS and adding security headers: Strict-Transport-Security with a duration, X-Frame-Options set to Deny, X-Content-Type-Options set to nosniff, and Referrer-Policy set to strict-origin-when-cross-origin, the last one being what keeps external sites from receiving the full page address. The Content-Security-Policy header deserves separate handling, because the most restrictive value also blocks scripts and stylesheets loaded from a CDN. List the domains the page needs first, test on a copy, then publish.

If you are coming from Adobe Campaign

In an Adobe Campaign web application, preloading the recipient usually relies on an encrypted identifier in the link, and the page refuses access when decryption fails. CloudPagesURL does the same job on the parameter itself, which is neither readable nor editable.

The difference lies in the control. At Adobe, preloading is an option of the web application and the refusal is handled by the platform. In a CloudPage, nothing blocks the display on your behalf: when the string is empty, the code runs anyway and the page appears, blank, inviting someone to enter contact details that will be attached to nobody. The check at the top of the page is yours to write, like a validation in a server script.

The habit worth keeping

Open one of your public CloudPages, look at its address, and change one character of the parameter. If the page shows anything other than your refusal message, you have the same flaw as the insurer. The test takes thirty seconds and is worth repeating after every release.

Frequently asked questions

Can you pass a Subscriber Key or a customer identifier in a CloudPage URL?

No: Salesforce says not to pass a SubscriberID, a Subscriber Key or a Contact Key in clear text, and not to include personal data such as the email address in links. A URL does not stay in the recipient's browser: it passes through browsing history, forwarded emails, corporate proxy logs, analytics tools and the header sent to the sites the page calls. Treat everything it carries as known to third parties.

Is Base64 encoding enough to protect an identifier in a URL?

No, it is not encryption. Salesforce names that encoding and hexadecimal conversion as insufficient. An encoded identifier is decoded, incremented and re-encoded in seconds. The only thing you gain is a delay before the flaw is found.

How do you build an encrypted CloudPage link from an email?

The CloudPagesURL function takes the page identifier, then name and value pairs, and returns the address with an encrypted query string. According to the documentation, that string also carries a reference to the originating email, which makes personalisation strings usable on the page. No data appears in clear text in the resulting address, and an altered string will not decrypt. For classic content microsites, MicrositeURL plays the same role.

Which checks belong at the top of a public CloudPage?

The Trailhead security module requires that all validation happens server side, that you check with at least two parameters that the same subscriber is the one interacting with the page, and that a public page opens with a global check that stops everything when a required parameter is missing. In practice you refuse a missing parameter, an invalid format and a mismatch between the parameter and the context, then confirm the row exists, and only once. When access is refused, keep the reason for the log and show a neutral message, identical whatever the reason.

Is an encrypted link enough to protect the data shown on the page?

Encryption stops someone forging a link or changing its contents, but it does not stop them using it: a forwarded email, a shared mailbox or a borrowed phone, and the page opens exactly as it does for the intended recipient. The design question is therefore what someone who holds the link without being the right person gets to see. An encrypted link is fine for showing little data and for low stakes actions, while sensitive data or a high impact action call for the authenticated customer area or a second check.

More in this series
Pierre Frin
Grokium founder · CRM consultant · Adobe Campaign Classic and Salesforce Marketing Cloud Email Specialist certified

Do your public pages expose more than you intended?

I can audit your CloudPages, your outbound links and your access checks, then hand you a written diagnosis with the fixes to apply and the order to apply them in.

Tell me about your context →