This sample demonstrates using Adobe Experience Platform to get personalization content from Adobe Journey Optimizer. The web page changes based on the personalization content returned.
This sample retrieves personalization content server-side using the Adobe Experience Platform APIs and renders it on the client-side using Adobe Experience Platform Web SDK using the applyResponse
command.
Here is what the page looks like before and after personalization content is rendered.
without personalization | with personalization |
---|---|
Please review the summary of personalization content for this sample.
Keep in mind that personalization content can come from any Adobe source such as Journey Optimizer or Target. Adobe Journey Optimizer is the source of personalization content used in this sample, but the implementation described here will work for all personalization solutions Adobe offers.
Prerequisite: install node and npm.
To run this sample:
- Setup local SSL certificates for https.
- Clone the repository to your local machine.
- Open a terminal and change directory to this sample's folder.
- Run
npm install
- Run
npm start
- Open a web browser to https://localhost
- Web SDK is included and configured on the page. The configuration is based on the
.env
file within theajo
folder.
<script src="https://cdn1.adoberesources.net/alloy/2.18.0/alloy.min.js" async></script>
alloy("configure", {
defaultConsent: "in",
edgeDomain: "{{edgeDomain}}",
edgeConfigId: "{{edgeConfigId}}",
orgId:"{{orgId}}",
debugEnabled: false,
thirdPartyCookiesEnabled: false
});
- Express is used for a lean server-side implementation. To handle basic server requests and routing.
- The web page is requested and any cookies previously stored by the browser prefixed with
kndctr_
are included. - When the page is requested from the app server, an event is sent to the interactive data collection endpoint to fetch personalization content. This sample app makes use of some helper methods to simplify building and sending requests to the API (see aepEdgeClient.js). But the request is simply a
POST
with a payload that contains an event and query. The cookies (if available) from the prior step are included with the request in themeta>state>entries
array.
fetch(
"https://edge.adobedc.net/ee/v2/interact?dataStreamId=abc&requestId=123",
{
headers: {
accept: "*/*",
"accept-language": "en-US,en;q=0.9",
"cache-control": "no-cache",
"content-type": "text/plain; charset=UTF-8",
pragma: "no-cache",
"sec-fetch-dest": "empty",
"sec-fetch-mode": "cors",
"sec-fetch-site": "cross-site",
"sec-gpc": "1",
"Referrer-Policy": "strict-origin-when-cross-origin",
Referer: "https://localhost/",
},
body: JSON.stringify({
event: {
xdm: {
eventType: "decisioning.propositionFetch",
web: {
webPageDetails: {
URL: "https://localhost/",
},
webReferrer: {
URL: "",
},
},
identityMap: {
FPID: [
{
id: "xyz",
authenticatedState: "ambiguous",
primary: true,
},
],
},
timestamp: "2022-06-23T22:21:00.878Z",
},
data: {},
},
query: {
identity: {
fetch: ["ECID"],
},
personalization: {
schemas: [
"https://ns.adobe.com/personalization/default-content-item",
"https://ns.adobe.com/personalization/html-content-item",
"https://ns.adobe.com/personalization/json-content-item",
"https://ns.adobe.com/personalization/redirect-item",
"https://ns.adobe.com/personalization/dom-action",
],
surfaces: ["web://localhost/","web://localhost/#sample-json-content"],
},
},
meta: {
state: {
domain: "localhost",
cookiesEnabled: true,
entries: [
{
key: "kndctr_XXX_AdobeOrg_identity",
value: "abc123",
},
{
key: "kndctr_XXX_AdobeOrg_cluster",
value: "or2",
},
],
},
},
}),
method: "POST",
}
).then((res) => res.json());
- The application server returns a response with HTML and the identity and cluster cookies.
- Within the page, the
applyResponse
command is invoked passing in the headers and body of AEP response.
alloy("applyResponse", {
"renderDecisions": true,
"responseHeaders": {
"cache-control": "no-cache, no-store, max-age=0, no-transform, private",
"connection": "close",
"content-encoding": "deflate",
"content-type": "application/json;charset=utf-8",
"date": "Mon, 11 Jul 2022 19:42:01 GMT",
"server": "jag",
"strict-transport-security": "max-age=31536000; includeSubDomains",
"transfer-encoding": "chunked",
"vary": "Origin",
"x-adobe-edge": "OR2;9",
"x-content-type-options": "nosniff",
"x-konductor": "22.6.78-BLACKOUTSERVERDOMAINS:7fa23f82",
"x-rate-limit-remaining": "599",
"x-request-id": "5c539bd0-33bf-43b6-a054-2924ac58038b",
"x-xss-protection": "1; mode=block"
},
"responseBody": {
"requestId": "5c539bd0-33bf-43b6-a054-2924ac58038b",
"handle": [
{
"payload": [
{
"id": "XXX",
"namespace": {
"code": "ECID"
}
}
],
"type": "identity:result"
},
{
"payload": [
{...},
{...}
],
"type": "personalization:decisions",
"eventIndex": 0
}
]
}
}
).then(applyPersonalization("#sample-json-content"));
- Web SDK renders page load Visual Experience Composer (VEC) content automatically because the
renderDecisions
flag is set to true. - Code based experiences are manually applied by the sample implementation code (in the
applyPersonalization
method) to update the DOM based on the decision. - For code-based experience campaigns, display events must manually be sent to indicate when the campaign content has been displayed. This is done via the
sendEvent
command.
function sendDisplayEvent(decision) {
const { id, scope, scopeDetails = {} } = decision;
alloy("sendEvent", {
xdm: {
eventType: "decisioning.propositionDisplay",
_experience: {
decisioning: {
propositions: [
{
id: id,
scope: scope,
scopeDetails: scopeDetails,
},
],
},
},
},
});
}
- For code based experience campaigns, interaction events must manually be sent to indicate when a user has interacted with the content. This is done via the
sendEvent
command.
function sendInteractEvent(label, proposition) {
const { id, scope, scopeDetails = {} } = proposition;
alloy("sendEvent", {
xdm: {
eventType: "decisioning.propositionInteract",
_experience: {
decisioning: {
propositions: [
{
id: id,
scope: scope,
scopeDetails: scopeDetails,
},
],
propositionEventType: {
interact: 1
},
propositionAction: {
label: label
},
},
},
},
});
}
Cookies are used to persist user identity and cluster information. When using a hybrid implementation, the application server must handle the storing and sending of these cookies during the request lifecycle.
Cookie | Purpose | Stored by | Sent by |
---|---|---|---|
kndctr_AdobeOrg_identity | Contains user identity details | application server | application server |
kndctr_AdobeOrg_cluster | Indicates which experience edge cluster should be used to fulfill requests | application server | application server |
Requests to Adobe Experience Platform API are required to get propositions and send a display notification. When using a client-side implementation, the Web SDK makes these requests when the sendEvent
command is used.
Request | Made by |
---|---|
interact request to get propositions | application server calling the Adobe Experience Platform API |
interact request to send display notifications | Web SDK |
Special attention must be paid when implementing hybrid mode so that hits are not counted twice. Please review the strategy for Analytics used in this sample.
sequenceDiagram
participant App server
participant Browser
participant Alloy
participant DOM
participant Browser Cookies
participant API as Adobe Experience Platform API
autonumber
Browser Cookies->>Browser: Read identity and cluster cookies
Browser->>App server: Page request w/cookies
App server->>API: Interact request
API->>App server: Return propositions
App server->>Browser: HTML response
Browser->>Browser Cookies: Set identity and cluster cookies
Browser->>Alloy: applyResponse({...});
Alloy->>DOM: Render propositions
Alloy->>API: Send display notification(s)
Alloy->>API: Send interact notification(s)
Please refer to Sample Adobe Experience Platform Edge Network Personalization Payloads for examples of the Edge Network API request and responses in the steps 3, 4, 9 and 10 of the flow.
This sample app can serve as a starting point for you to experiment and learn more about Adobe Experience Platform. For example, you can change a few environment variables so the sample app pulls in content from your own AEP configuration. To do so, just open the .env
file within the ajo
folder and modify the variables. Restart the sample app, and you're ready to experiment using your own personalization content.
- Adobe Tech Blog: Hybrid Personalization in the Adobe Experience Platform Web SDK
- SDK Documentation: Hybrid personalization using Web SDK and Edge Network Server API