This article contains lots of information about the best way to implement Dynamic Yield in an eXPerience API site using server side APIs.
Defining User and Session Identifiers
In most cases, your application would already have the notion of a User ID. This ID actually represents a specific device or browser used by someone whose identity you often do not know at all. In addition, there is often the notion of a Session ID that is limited in time and invalidated after a set period of inactivity. If you already have both, great! Make sure to consistently use the same IDs across all API calls you're making from various places in your codebase.
If you do not have a user or session ID, here are a few things to consider when designing your solution:
- Use secure storage to prevent hijacking of IDs: on the web, prefer using HttpOnly cookies, if possible, to hide sensitive identifiers from client-side code. In this scenario, you allocate the user ID in the server and return it in a cookie that is not accessible to the client code at all. You can use this technique for JWTs as well.
- Use only secure HTTPS connections between your client and server to prevent Man-In-The-Middle (MITM) attacks. This also protects your cookies while in transit.
- If you need to define a session identifier, ensure a new session is created after a fixed inactivity duration and that sessions always have some maximum duration so they will not last forever. A common value for inactivity time is 30 minutes.
Session definitions in analytics platforms
Various analytics platforms have their own nuanced definitions of a session, which may cause a slight difference in session counts between what you see in your analytics tool of choice and reports within Dynamic Yield.
For example, Google Analytics has a 30 minute inactivity period, but sessions also always end at midnight. You don't have to follow this last rule, but it will inherently generate some difference in session counts.
Limit the Scope of API Keys and Avoid Them in Clients
Take care to grant your API keys with only the permissions needed for your use case, avoiding excess rights.
In case you're using the Unified Customer Profile API or pushing product updates via API, create dedicated keys with these permissions and make them available only to the relevant server-side components.
For increasing security further, we recommend that all calls made from your client apps (be it web, native apps, or otherwise) be done without using a permanent API key at all. You can either proxy requests through your server-side or use server-provisioned tokens (JWTs) to identify that specific client, and for a limited time.
When using tokens, your server-side is responsible for generating a token through our API, bound to a user ID, and expiry time. That token should then be handed over to the client, which would use it instead of an API key. In this mode, the client cannot impersonate as any user ID other than the one determined by the server.
Use Batching to Minimize the Number of Network Calls
Having multiple campaigns running on the same page is common. In this case, instead of making the round-trip of calling the chosen endpoint for each campaign, you should use the selector parameter to request the chosen variations for all relevant campaigns at once. This usually requires some planning ahead.
A More Dynamic (and Complex) Layout
Having a list of relevant campaign names by page type is neat. However, things may get a bit more complex if you're working with a Headless CMS platform.
With such platforms, page layouts can be changed dynamically and dramatically by Marketers and Content Editors at any time. A layout may be a complex nested structure of components and child-components and contain reusable components shared by multiple layouts. Since the layout may change at any time, the rendering code does not know which components there are, and which relevant Dynamic Yield campaigns they might refer to, before actually fetching the up-to-date layout. In other words, the list of campaigns has to be discovered and the correct variations resolved before actual rendering is done.
In such a case, we can still implement efficient personalization by tweaking our CMS component topology a bit. Assume we have a banner component that can be placed in any page layout and holds various data attributes (e.g. background image, title, sub-title, target URL, etc.). By itself, it does not provide for testing nor targeting of its attribute values. How do we enable experimenting with multiple variations?
Instead of having the banner component directly hold its attributes, we can define a parent-child relationship between a banner campaign component and 1..n banner variation child components.
- At the banner campaign level, we will hold the name of a matching Dynamic Yield campaign, and a collection of child banner variations
- Each banner variation has a unique name, plus all the actual banner attributes.
- The Dynamic Yield Campaign will hold a variation for each banner variation CMS component. Each variation payload will refer to that component's unique name.
Now, we need to tweak the rendering process a bit. After the logical page layout is fetched, the rendering code should:
- Search for any banner campaign components in the layout
- Get the Dynamic Yield campaign name from all found components into a list
- Call choose to get the chosen variations for the list of campaign names. For each campaign, a chosen variation will be returned - holding the unique name of the appropriate banner variation component!
Now, our code can proceed to render the specific banners that were chosen.
Use Asynchronous Calls for Recommendations
While the core layout of your page is often rendered at once, it is recommended to consider loading some elements asynchronously to speed up the initial rendering.
Here are two great candidates for this:
- Recommendations: Recommendations widgets may be placed on the page in various ways, some more inventive than others. Widgets are typically simple, in-line components, or they may reside inside floating carts and sliding drawers on mobile to conserve precious screen real estate.
Common to all types is that they're usually not visible immediately on page load, as they are either located below the fold or because further user interaction is required to expand them. Therefore, you're advised to fetch a recommendation campaign after or in parallel to rendering the "core" part of the page.
- Overlays and Notifications: If it fits your application, elements shown on top of the page may be loaded and then rendered asynchronously as well.
Handle Timeouts and Errors Gracefully
Needless to say, we're making a great effort to keep our service healthy and fast. However, you have full control over how to respond to calls taking a long time to finish or returning with an error.
Always set a timeout when calling our API. In case a request times out or fails for any reason, ensure you know what the proper fallback should be. In some cases, it should simply be not taking any action at all, or not rendering an optional component - as long as this doesn't break the page. For elements drawn on top of the page (i.e. overlays and notifications), a "do nothing on error" approach is typically the right behavior.
In cases where default content must appear, have this default at the ready in code, or preferably in the configuration.
If you receive an error from the API, please write to log not only the error code and when it happened but also the unique request ID that's returned in the error body. Troubleshooting can be done throwout our logs dashboard.
Review Your Code for Consistency
Most real-life projects involve more than one developer, increasing the chance for some issues of inconsistency - which are not always apparent until some discrepancy is detected in reports for live campaigns.
Make sure that:
- The userId and sessionId you choose are consistent across your application.
- The SKUs passed in (a) the product feed, (b) the parameters to choose, and (c) predefined events, are all formatted exactly the same way. It's not uncommon to have more than one developer involved in integrating Dynamic Yield with your system. For example, the developer implementing the feed integration is often not the one integrating it in client apps.
- Have a common approach to storing the variation identifiers received from API calls (decisionId, variationId, and slotId where applicable) and using them to report engagement.