Audience: Technical - Deep
Technical depth: ★★★★★
Emplifi widgets can inject AggregateRating and reviews structured data in the browser after JavaScript runs. Many AI crawlers and some search bots do not execute JavaScript, so that markup never appears in the initial HTML they fetch.
The server-side structured data endpoint returns fully rendered schema.org JSON-LD (aggregate rating plus a page of reviews) so your product detail page (PDP) can include it in the first HTML response.
Use this endpoint when you want ratings and reviews structured data present on initial page load for AI search / generative engine optimization (GEO), without waiting for widgets to load.
For an overview of structured data concepts, see Introduction to Structured Data.
For widget-injected AggregateRating (JSON-LD or microdata), see Working with Structured Data.
What the endpoint returns
A single schema.org Product node that can include:
-
aggregateRating— average rating and review count for the product (direct-only or direct plus related, depending on site aggregate ratings settings). -
review— a page of reviews using the same direct/related population asaggregateRating. See "How review list content is chosen" section below on how the list of reviews is determined. -
optional
@id— when you providecanonicalId,@idis populated so Emplifi’s node links to your own Product JSON-LD. -
name— catalog product title only whencanonicalIdis omitted (standalone Product). WhencanonicalIdis present,nameis omitted so your own Product JSON-LD owns the title.
Example payload (linked to merchant Product JSON-LD via @id):
{
"@context": "<https://schema.org>",
"@type": "Product",
"@id": "<https://www.example.com/products/amazing-thing-321>",
"aggregateRating": {
"@type": "AggregateRating",
"ratingValue": "4.8",
"reviewCount": "749",
"bestRating": "5",
"worstRating": "1"
},
"review": [
{
"@type": "Review",
"name": "Great product",
"description": "Works exactly as described.",
"author": { "@type": "Person", "name": "Jane D" },
"datePublished": "2025-10-10T00:00:00Z",
"reviewRating": {
"@type": "Rating",
"ratingValue": "5",
"bestRating": "5",
"worstRating": "1"
}
}
]
}
If there is nothing to emit (no aggregate rating and no reviews), the endpoint returns HTTP 200 with an empty body.
Endpoint
GET <https://cdn-ws.turnto.com/v5/sitedata/{siteKey}/{sku}/d/structuredData>
GET <https://cdn-ws.turnto.com/v5/sitedata/{siteKey}/{sku}/d/structuredData/{canonicalId}>
If your site is hosted in the EU, the domain name is cdn-ws.turnto.eu.
Path parameters
|
Parameter |
Required |
Description |
|---|---|---|
|
|
Yes |
Your Emplifi Ratings & Reviews site key |
|
|
Yes |
Product SKU |
|
|
No |
Unpadded base64 url encoding of the opaque |
There is no auth / pass key, no applied filters, and no search term. Structured data always reflects the initial unfiltered review list.
Response formats (Accept header)
|
Accept |
Content-Type |
Body |
|---|---|---|
|
|
|
Embed-ready |
|
|
|
Raw JSON-LD object |
Any other Accept value (for example text/javascript) returns 406 Not Acceptable.
The script-tag response is HTML markup to splice into your PDP. It is not executable JavaScript and should not be loaded with a <script src="…"> tag.
Responses include Cache-Control: no-cache. CDN caching for this path is controlled by Emplifi’s edge configuration.
Matching @id is required for correct SEO
If your PDP already emits Product JSON-LD (name, image, offers, description, and so on), Emplifi’s Product node and yours must share the exact same @id value.
Search engines and validators treat JSON-LD nodes with different @id values as separate products. When the identifiers do not match:
-
Rating and review data may not attach to your product in rich results
-
You can see structured data errors such as missing
offers,review, oraggregateRatingon the product node Google evaluates -
Aggregate ratings and review snippets may fail to appear in SERPs (Search Engine Results Pages) even though both scripts are present on the page
Always encode the same @id your Product markup uses and pass it as {canonicalId}. Do not omit {canonicalId} when you already publish Product JSON-LD on the page.
Linking Emplifi JSON-LD to your Product JSON-LD (@id)
-
Choose the
@idvalue your Product markup uses (page URL, canonical URL, fragment such as#product, or another opaque token). -
Base64url-encode that value without padding and pass it as the
{canonicalId}path segment. -
Emplifi decodes it and sets
"@id"on the returned Product node. The value does not have to be a URL, it only has to match yours character-for-character. Emplifi does not emitnamewhen@idis present.
Encoding examples
PHP
$canonicalId = '<https://www.example.com/products/amazing-thing-321>';
$encodedCanonicalId = rtrim(strtr(base64_encode($canonicalId), '+/', '-_'), '=');
JavaScript (Node)
const encodedCanonicalId = Buffer.from(canonicalId, 'utf8')
.toString('base64url'); // Node 16+; already unpadded
Java
String encoded = Base64.getUrlEncoder().withoutPadding()
.encodeToString(canonicalId.getBytes(StandardCharsets.UTF_8));
Serving structured data only to AI bots
You do not have to embed this markup for every shopper request. Many implementations fetch and inject server-side structured data only when the request User-Agent (or equivalent bot signal) indicates an AI or search crawler.
Typical approach:
-
On PDP render, inspect the incoming
User-Agent(and any bot headers your CDN or WAF already classifies). -
If the client is a normal browser shopper, skip the Emplifi structured data fetch and rely on widgets as today (or on your own Product JSON-LD only).
-
If the client is an AI crawler, LLM fetcher, or search bot that may not execute JavaScript, call the endpoint server-side and embed the response in the initial HTML.
This keeps PDP latency and HTML size unchanged for most shoppers while still exposing ratings and reviews structured data to bots that read static HTML.
Maintain your own allowlist of bot user agents and review it periodically as AI crawlers change. Emplifi does not require or validate User-Agent on the structured data endpoint. Selective serving of the structured data is entirely your decision.
Example:
function isAiOrSearchBot(string $userAgent): bool {
// Maintain this list for your environment; examples only
$patterns = [
'GPTBot',
'ChatGPT-User',
'ClaudeBot',
'anthropic-ai',
'PerplexityBot',
'Google-Extended',
'Googlebot',
'bingbot',
];
foreach ($patterns as $pattern) {
if (stripos($userAgent, $pattern) !== false) {
return true;
}
}
return false;
}
$userAgent = $_SERVER['HTTP_USER_AGENT'] ?? '';
if (isAiOrSearchBot($userAgent)) {
// Fetch structured data and echo the script tag into <head>
}
When you serve Emplifi structured data only to bots, still disable duplicate widget JSON-LD for those same responses (see below), and keep @id matching on every bot-rendered page.
Implementation steps
1. Fetch on the server and embed in the initial HTML
Preferred pattern: request the embed-ready script tag (Accept: text/html) during PDP render and print the response into <head> (or early in <body>). Optionally, only fetch when an AI / search bot user agent is discovered as described above.
PHP example
$siteKey = 'YOUR_SITE_KEY';
$sku = 'YOUR_SKU';
$canonicalId = '<https://www.example.com/products/>' . rawurlencode($sku);
$encodedCanonicalId = rtrim(strtr(base64_encode($canonicalId), '+/', '-_'), '=');
$url = 'https://YOUR_WIDGET_CDN_HOST/v5/sitedata/'
. rawurlencode($siteKey) . '/'
. rawurlencode($sku) . '/d/structuredData/'
. rawurlencode($encodedCanonicalId);
$context = stream_context_create([
'http' => [
'method' => 'GET',
'header' => "Accept: text/html\r\n",
'timeout' => 10,
'ignore_errors' => true,
],
]);
$html = @file_get_contents($url, false, $context);
if ($html !== false && $html !== '') {
echo $html;
}
2. Keep your own Product JSON-LD (recommended)
Continue emitting your product name, image, offers, and description. Use the same @id you encoded for {canonicalId}. Matching @id is required for search engines to combine Emplifi Ratings and Reviews with your product details.
3. Disable duplicate client-side Emplifi structured data
If your widgets have been configured to inject AggregateRating / review JSON-LD, turn off the widget structured data load when loading the data server-side, skipStructuredData: true. Otherwise, crawlers can see duplicate Product nodes.
Example excluding client-side widget structured data load
var turnToConfig = {
locale: 'en_US',
pageId: 'pdp-page',
skipStructuredData: true,
reviewsList: {},
reviewsSummary: {}
};
Ask our Emplifi Technical Support Team if you need site-level structured data settings adjusted.
4. Optional: merge raw JSON-LD yourself
If you prefer one Product object instead of two linked nodes, request Accept: application/ld+json and merge aggregateRating / review into your Product schema before rendering a single <script type="application/ld+json"> tag. The merged object must still use a single @id consistent with the rest of your product markup.
How review list content is chosen
The endpoint returns the same unfiltered review list behaviour as your live PDP reviews list widget. No filters or search terms.
Sort order comes from your Emplifi portal configuration: the site's default review sort (the first enabled sort option in portal settings). If no default is configured, reviews are ordered by most recent.
Which reviews appear and how many follow the deployed PDP Reviews List widget build on your site:
|
Widget behaviour on the PDP |
Structured data behaviour |
|---|---|
|
Related reviews shown in the same list as direct reviews |
One combined list of direct and related reviews |
|
Related reviews shown in a separate section (typical default) |
Direct reviews first, then related reviews, combined into one |
|
Initial page size for the direct review list |
Same number of direct reviews in structured data |
|
Initial page size for the related review list (when shown separately) |
Same number of related reviews appended after direct reviews |
These settings are part of the widget build deployed to your site. They are not exposed as path or query parameters on this endpoint. This is intentional to keep the structured data in sync with what is displayed by the widgets.
Error responses
|
Situation |
HTTP status |
|---|---|
|
Invalid |
400 |
|
Unsupported |
406 |
|
No structured data to emit |
200 with empty body |
What's next?
Validate the embedded markup with Google’s Rich Results Test or Schema Markup Validator. Confirm a single coherent Product with matching @id, aggregateRating, and review.
See Maintaining Structured Data for locating JSON-LD on the page and common error messages.
For widget-only AggregateRating without a server fetch, see Working with Structured Data.