Skip to content
  • There are no suggestions because the search field is empty.

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:

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)

  1. Determine targeting and traffic allocation server/edge-side.
  2. Set a short-lived cookie before any HTML is sent to the browser.
  3. Serve the HTML or redirect accordingly.
  4. 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_v to respect returning users' existing variations.
  • (Cookie path) Build in your traffic allocation and segmentation targeting.
  • (Cookie path) Set the _conv_sptest cookie 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 _conv_v or are re-assigning them randomly

Parse the _conv_v cookie as shown above so repeat visitors retain their variation. Never manually alter _conv_v!

Wrong HTML

Server/CDN cache doesn't vary by cookie

Ensure your caching layer bypasses cache for _conv_sptest or sets Cache-Control: private