Server Side Testing
Run your A/B or Split-URL tests before any HTML leaves your server or CDN while maintaining accurate analytics in Convert.
| Author: | George F. Crew |
THIS ARTICLE WILL HELP YOU:
- Know why to use Server Side Testing
- Show Supported Platforms
- Understand Pre-requisites
- Understand Patterns
- Get a Checklist
- Know how to Troubleshoot
Why Use Server-Side Testing?
-
Minimize content flash for server-rendered pages
-
Fully control variation logic backend-side or at the edge.
-
Preserve experiment tracking integrity
Supported Patterns
| Pattern | Use Case |
|---|---|
Cookie hand-off (_conv_sptest + Convert Tracking Script) |
You already use the Convert Tracking Script and need early rendering or redirects using your server (PHP) or CDN Edge Workers. |
| Convert SDKs (no tracking script) |
You want complete server-side control over bucketing and tracking using Convert's Full Stack SDKs. |
Prerequisites
| Requirement | Cookie Hand-Off | SDKs |
|---|---|---|
Set cookies (_conv_sptest) |
✅ | ❌ |
| Backend Environment / Edge Worker | ✅ | ✅ |
| Convert Tracking Script | ✅ | ❌ |
| Public SDK key | ❌ | ✅ |
| Stable visitor ID | Recommended (_conv_v) |
✅ |
Pattern A – Cookie Hand-Off (with Tracking Script)
- Determine targeting and traffic allocation server/edge-side.
- Set a short-lived cookie before any HTML is sent to the browser.
- Serve the HTML or redirect accordingly.
- The Convert Tracking Script reads this cookie on load and tracks appropriately.
⚠️ IMPORTANT: Targeting & Location Conditions for _conv_sptest
Experiments triggered with the _conv_sptest cookie need to be targeted to the right page and visitors server-side using your own code.
Because targeting, variation assignation, and traffic allocation will be done entirely server-side, the related test in Convert must have an unmatchable condition on its Location conditions.
- Recommendation: Set a JS Condition with a value of false. This prevents the client-side Convert script from attempting to bucket users or override your server-side logic.
PHP Example: Targeting, Allocation, and Cookie Hand-off
This example checks the _conv_v tracking cookie to see if the visitor is already bucketed. If they aren't, it targets visitors on the /pricing page who arrive via the summer_sale UTM campaign, splits them evenly (50/50), and hands the assignment to the browser.
<?php
$expId = '123456789'; // Your Experiment ID
$controlId = '111111111'; // Original Variation ID
$varId1 = '987654321'; // Challenger Variation ID
$assignedVarId = null;
// 1. Read the _conv_v cookie to see if the user is ALREADY bucketed in this experiment
if (isset($_COOKIE['_conv_v'])) {
if (preg_match('/' . $expId . '\.\{v\.([0-9]+)/', $_COOKIE['_conv_v'], $matches)) {
$assignedVarId = $matches[1]; // Extract existing variation ID
}
}
// 2. Target a specific URL path
$requestPath = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
$isTargetPage = ($requestPath === '/pricing');
// 3. Target a specific traffic segment (utm_campaign)
$isTargetCampaign = (isset($_GET['utm_campaign']) && $_GET['utm_campaign'] === 'summer_sale');
// 4. If the user isn't bucketed yet, verify targeting rules and assign a variation
if (!$assignedVarId && $isTargetPage && $isTargetCampaign) {
// Simple Traffic Allocation (50/50 split)
$assignedVarId = (rand(1, 100) <= 50) ? $varId1 : $controlId;
// Set the _conv_sptest cookie (expires in 10 seconds)
setcookie('_conv_sptest', "{$expId}:{$assignedVarId}", time() + 10, '/', '', false, false);
}
// 5. Serve the corresponding variation HTML
if ($assignedVarId === $varId1) {
echo "<h1>Welcome to the Summer Sale Pricing!</h1>";
} else {
echo "<h1>Standard Pricing Plan</h1>";
}
?>
CDN Edge Examples (JavaScript)
You can run the same logic directly on your CDN to intercept requests and rewrite responses or redirect users before they ever hit your origin server.
Cloudflare Workers:
export default {
async fetch(request) {
const expId = '123456789';
const controlId = '111111111';
const varId1 = '987654321';
const cookieHeader = request.headers.get('Cookie') || '';
// 1. Read the _conv_v cookie to check for existing assignment
const match = cookieHeader.match(new RegExp(expId + '\\.\\{v\\.([0-9]+)'));
let assignedVarId = match ? match[1] : null;
let headersToSet = { 'Cache-Control': 'private' };
// 2. Allocate if not already bucketed
if (!assignedVarId) {
// Simple 50/50 allocation
assignedVarId = (Math.random() < 0.5) ? varId1 : controlId;
// 3. Hand-off cookie for the Convert Tracking Script
headersToSet['Set-Cookie'] = `_conv_sptest=${expId}:${assignedVarId}; Max-Age=10; Path=/; SameSite=Lax`;
}
// 4. Render corresponding content (or modify URL)
const html = await renderVariant(assignedVarId); // Your custom render function
return new Response(html, {
headers: headersToSet
});
}
}
AWS Lambda@Edge (Viewer Request):
exports.handler = async (event) => {
const request = event.Records[0].cf.request;
const headers = request.headers;
const expId = '123456789';
const controlId = '111111111';
const varId1 = '987654321';
let cookieString = headers.cookie ? headers.cookie[0].value : '';
// 1. Read the _conv_v cookie to check for existing assignment
const match = cookieString.match(new RegExp(expId + '\\.\\{v\\.([0-9]+)'));
let assignedVarId = match ? match[1] : null;
// 2. Allocate if not already bucketed
if (!assignedVarId) {
assignedVarId = (Math.random() < 0.5) ? varId1 : controlId;
// 3. Hand-off cookie for the Convert Tracking Script
request.headers['set-cookie'] = [{
key: 'Set-Cookie',
value: `_conv_sptest=${expId}:${assignedVarId}; Max-Age=10; Path=/; SameSite=Lax`
}];
}
// Note: For Split-URL tests in Lambda@Edge, you would rewrite request.uri
// here based on the assignedVarId before sending the request to your origin.
return request;
};
Checklist
- Create the experiment and obtain
experimentId/variationId. - Choose the Cookie Hand-off.
- Implement server/edge logic in your PHP app or CDN.
- (Cookie path) Build in a way to read
_conv_vto respect returning users' existing variations. - (Cookie path) Build in your traffic allocation and segmentation targeting.
- (Cookie path) Set the
_conv_sptestcookie before any headers or HTML are sent. - (Cookie path) Set Location Conditions to an unmatchable JS Condition (e.g.,
false) in the Convert app to allow your code to handle targeting/allocation. - QA: Use the Convert Chrome Debugger Extension to verify bucketing.
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
| No data in reports (cookie path) |
Cookie not present before tracking script |
Ensure cookie writing logic runs before any HTML output |
|
Visitor switches variation |
You overwrote |
Parse the |
|
Wrong HTML |
Server/CDN cache doesn't vary by cookie |
Ensure your caching layer bypasses cache for |