Docs

WordPress REST route

Setup

wp-config.php
define('X3_PROXY_KEY', 'sk_3x_YOUR_PROXY_KEY');
define('X3_API_ORIGIN', '{{API_ORIGIN}}');
wp-content/mu-plugins/x3-proxy.php
<?php
/**
 * Plugin Name: 3x first-party proxy
 * Description: Forwards /wp-json/n3/v1/* to the 3x API (events, config, scripts) with the visitor's IP.
 */

add_action('rest_api_init', function () {
    register_rest_route('n3', '/v1/(?P<path>e|collect|cb|c/[^/]+|s/[A-Za-z0-9@._-]+)', [
        'methods'             => ['GET', 'POST', 'OPTIONS'],
        'permission_callback' => '__return_true',
        'callback'            => 'x3_proxy_forward',
    ]);
});

function x3_proxy_forward(WP_REST_Request $request) {
    $path = '/v1/' . $request->get_param('path');
    $url  = X3_API_ORIGIN . $path . (empty($_SERVER['QUERY_STRING']) ? '' : '?' . $_SERVER['QUERY_STRING']);

    $headers = [
        'user-agent'          => $request->get_header('user_agent') ?? '',
        'origin'              => $request->get_header('origin') ?? '',
        'content-type'        => $request->get_header('content_type') ?? 'text/plain;charset=UTF-8',
        'x-3x-client-ip'      => $_SERVER['REMOTE_ADDR'] ?? '',
        'x-3x-client-country' => $request->get_header('cf_ipcountry') ?? '',
        'x-3x-proxy-key'      => X3_PROXY_KEY,
    ];

    $response = wp_remote_request($url, [
        'method'      => $request->get_method(),
        'headers'     => array_filter($headers),
        'body'        => $request->get_method() === 'POST' ? $request->get_body() : null,
        'timeout'     => 5,
        'redirection' => 0,
    ]);
    if (is_wp_error($response)) return new WP_REST_Response(null, 502);

    $status = wp_remote_retrieve_response_code($response);
    $body   = wp_remote_retrieve_body($response);
    $type   = wp_remote_retrieve_header($response, 'content-type');

    $result = new WP_REST_Response(null, $status);
    if ($type) $result->header('Content-Type', $type);
    if (str_starts_with($path, '/v1/s/') && $status === 200) $result->header('Cache-Control', 'public, max-age=3600, stale-while-revalidate=86400');
    foreach (['access-control-allow-origin', 'vary'] as $name) {
        $value = wp_remote_retrieve_header($response, $name);
        if ($value) $result->header($name, $value);
    }
    add_filter('rest_pre_serve_request', function ($served) use ($body) {
        echo $body;
        return true;
    });
    return $result;
}

Then set X3_HOST in the WordPress install to /wp-json/n3.

$_SERVER['REMOTE_ADDR'] is the visitor's address only when PHP faces the internet directly. Behind Cloudflare or a load balancer, configure the web server to restore the real client IP (mod_remoteip, Nginx realip) – never copy X-Forwarded-For blindly.

Verify

curl -s -o /dev/null -w "%{http_code}\n" https://example.com/wp-json/n3/v1/s/x3.js

Prints 200. Security plugins that restrict the REST API for anonymous users must allow the n3 namespace.