Hybrid personalization using Web SDK and Edge Network Server API
Overview overview
Hybdrid personalization describes the process of retrieving personalization content server-side, using the Edge Network Server API, and rendering it client-side, using the Web SDK.
You can use hybrid personalization with personalization solutions like Adobe Target, Adobe Journey Optimizer, or Offer Decisioning, the difference being the contents of the Server API payload.
Prerequisites prerequisites
Before implementing hybrid personalization on your web properties, make sure you meet the following conditions:
- You have decided what personalization solution you want to use. This will have an impact on the contents of the Server API payload.
- You have access to an application server which you can use to make the Server API calls.
- You have access to the Edge Network Server API.
- You have correctly configured and deployed the Web SDK on the pages that you want to personalize.
Flow diagram flow-diagram
The flow diagram below describes the order of the steps taken to deliver hybrid personalization.
- Any existing cookies previously stored by the browser, prefixed with
kndctr_
, are included in the browser request. - The client web browser requests the web page from your application server.
- When the application server receives the page request, it makes a
POST
request to the Server API interactive data collection endpoint to fetch personalization content. ThePOST
request contains anevent
and aquery
. The cookies from the previous step, if available, are included in themeta>state>entries
array. - The Server API returns the personalization content to your application server.
- The application server returns an HTML response to the client browser, containing the identity and cluster cookies.
- On the client page, the Web SDK
applyResponse
command is called, passing in the headers and body of the Server API response from the previous step. - The Web SDK renders Target Visual Experience Composer (VEC) offers and Journey Optimizer Web Channel items automatically, because the
renderDecisions
flag is set totrue
. - Target form-based HTML/JSON offers and Journey Optimizer code-based experiences are manually applied through the
applyProposition
method, to update the DOM based on the personalization content in the proposition. - For Target form-based HTML/JSON offers and Journey Optimizer code-based experiences, display events must manually be sent, to indicate when the returned content has been displayed. This is done via the
sendEvent
command.
Cookies cookies
Cookies are used to persist user identity and cluster information. When using a hybrid implementation, the Web application server handles the storing and sending of these cookies during the request lifecycle.
kndctr_AdobeOrg_identity
kndctr_AdobeOrg_cluster
Request placement request-placement
Server API requests are required to get propositions and send a display notification. When using a hybrid implementation, the application server makes these requests to the Server API.
Analytics implications analytics
When implementing hybrid personalization, you must pay special attention so that page hits are not counted multiple times in Analytics.
When you configure a datastream for Analytics, events are automatically forwarded so that page hits are captured.
The sample from this implementation uses two different datastreams:
- A datastream configured for Analytics. This datastream is used for Web SDK interactions.
- A second datastream without an Analytics configuration. This datastream is used for Server API requests. You must configure this datastream with the same destination configuration as the datastream that you configured for Analytics.
This way, the server-side request do not register any Analytics events, but the client-side requests do. This leads to Analytics requests being accurately counted.
Server-side request server-side-request
The sample request below illustrates a Server API request that your application server could use to retrieve the personalization content.
API format
POST /ee/v2/interact
Request request
curl -X POST "https://edge.adobedc.net/ee/v2/interact?dataStreamId={DATASTREAM_ID}"
-H "Content-Type: text/plain"
-d '{
"event":{
"xdm":{
"web":{
"webPageDetails":{
"URL":"http://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"
],
"decisionScopes":[
"__view__",
"sample-json-offer"
]
}
},
"meta":{
"state":{
"domain":"localhost",
"cookiesEnabled":true,
"entries":[
{
"key":"kndctr_XXX_AdobeOrg_identity",
"value":"abc123"
},
{
"key":"kndctr_XXX_AdobeOrg_cluster",
"value":"or2"
}
]
}
}
}'
dataStreamId
String
requestId
String
Server-side response server-response
The sample response below shows what the Server API response might look like.
{
"requestId":"5c539bd0-33bf-43b6-a054-2924ac58038b",
"handle":[
{
"payload":[
{
"id":"XXX",
"namespace":{
"code":"ECID"
}
}
],
"type":"identity:result"
},
{
"payload":[
{
"..."
},
{
"..."
}
],
"type":"personalization:decisions",
"eventIndex":0
}
]
}
Client-side request client-request
On the client page, the Web SDK applyResponse
command is called, passing in the headers and body of the server-side 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-offer"));
Form-based JSON offers are manually applied through the applyPersonalization
method, to update the DOM based on the personalization offer. For form-based activities, display events must manually be sent, to indicate when the offer 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
}],
"propositionEventType": {
"display": 1
}
}
}
}
});
}
Sample application sample-app
To help you experiment and learn more about this type of personalization, we provide a sample application which you can download and use for testing. You can download the application, along with detailed instructions on how to use it, from this GitHub repository.