Skip to main content
Roblox is gradually implementing .ROBLOSECURITY cookie rotation for improved security. RoZod automatically detects when cookies are rotated and provides callbacks to persist new values. RoZod monitors response headers and automatically updates the internal cookie pool when rotation occurs:
import { configureServer } from 'rozod';

configureServer({
  cookies: process.env.ROBLOX_COOKIE,
  onCookieRefresh: async ({ oldCookie, newCookie, poolIndex }) => {
    // Roblox rotated the cookie - persist the new value
    await db.updateCookie(poolIndex, newCookie);
    console.log('Cookie rotated and saved!');
  }
});
The internal cookie pool is automatically updated, so the callback is only needed if you want to persist cookies across restarts.
The onCookieRefresh callback receives detailed information about the rotation:
type CookieRefreshEvent = {
  /** The old cookie value that was used in the request */
  oldCookie: string;
  /** The new cookie value received from Roblox */
  newCookie: string;
  /** The index in the cookie pool that was updated */
  poolIndex: number;
};

Example: Database persistence

import { configureServer } from 'rozod';

configureServer({
  cookies: await db.getAllCookies(),
  onCookieRefresh: async ({ oldCookie, newCookie, poolIndex }) => {
    // Update database
    await db.updateCookie(poolIndex, newCookie);
    
    // Log rotation event
    await db.logEvent({
      type: 'cookie_rotation',
      accountIndex: poolIndex,
      timestamp: new Date(),
    });
    
    console.log(`Cookie ${poolIndex} rotated successfully`);
  }
});

Example: Environment variable update

import fs from 'fs';
import { configureServer } from 'rozod';

configureServer({
  cookies: process.env.ROBLOX_COOKIE,
  onCookieRefresh: async ({ newCookie, poolIndex }) => {
    // Update .env file (for development only)
    const envContent = fs.readFileSync('.env', 'utf8');
    const updated = envContent.replace(
      /ROBLOX_COOKIE=.*/,
      `ROBLOX_COOKIE=${newCookie}`
    );
    fs.writeFileSync('.env', updated);
    
    console.log('Cookie updated in .env file');
  }
});
Automatically updating .env files is only suitable for development. In production, use proper secret management.
When using multiple cookies, each cookie is tracked independently:
import { configureServer } from 'rozod';

configureServer({
  cookies: [
    await db.getCookie(0),
    await db.getCookie(1),
    await db.getCookie(2),
  ],
  onCookieRefresh: async ({ oldCookie, newCookie, poolIndex }) => {
    // poolIndex indicates which cookie was rotated
    await db.updateCookie(poolIndex, newCookie);
    console.log(`Account ${poolIndex} cookie rotated`);
  }
});
The poolIndex allows you to track which specific account in your pool had its cookie rotated.
Proactively refresh cookies before they expire:
import { refreshCookie } from 'rozod';

// Refresh a specific cookie
const result = await refreshCookie(0); // Index 0 = first cookie

if (result.success) {
  console.log('New cookie:', result.newCookie);
  await db.updateCookie(result.poolIndex, result.newCookie);
} else {
  console.error('Refresh failed:', result.error);
}

Refresh all cookies in a pool

import { refreshCookie, getCookies } from 'rozod';

async function refreshAllCookies() {
  const cookies = getCookies();
  const results = [];
  
  for (let i = 0; i < cookies.length; i++) {
    const result = await refreshCookie(i);
    results.push(result);
    
    if (result.success) {
      await db.updateCookie(i, result.newCookie);
      console.log(`Cookie ${i} refreshed successfully`);
    } else {
      console.error(`Cookie ${i} refresh failed:`, result.error);
    }
  }
  
  return results;
}

// Run on a schedule
setInterval(refreshAllCookies, 24 * 60 * 60 * 1000); // Daily
Manual refresh is useful for proactive cookie management, but RoZod handles automatic rotation without manual intervention.

Refresh result type

The refreshCookie() function returns detailed results:
type RefreshCookieResult = {
  success: boolean;
  /** The new cookie value if refresh was successful */
  newCookie?: string;
  /** Error message if refresh failed */
  error?: string;
  /** The index in the cookie pool that was refreshed */
  poolIndex: number;
};

Get current cookies

Retrieve all cookies from the pool:
import { getCookies } from 'rozod';

const cookies = getCookies();
console.log(`Pool has ${cookies.length} cookies`);

// Check cookie health
for (let i = 0; i < cookies.length; i++) {
  const isValid = await validateCookie(cookies[i]);
  console.log(`Cookie ${i}: ${isValid ? 'valid' : 'invalid'}`);
}
Manually update a cookie in the pool:
import { updateCookie } from 'rozod';

// Update the first cookie
const success = updateCookie(0, newCookieValue);

if (success) {
  console.log('Cookie updated in pool');
} else {
  console.error('Invalid index or cookie');
}
Manually updating cookies bypasses the onCookieRefresh callback. Only use this for manual management scenarios.

How rotation detection works

RoZod monitors response headers for cookie rotation:
// Internal RoZod logic (for reference)
async function handleCookieRotation(response: Response, usedCookie?: CookieSelection): Promise<void> {
  // Check for Set-Cookie header with new .ROBLOSECURITY
  const setCookieHeader = response.headers.get('set-cookie');
  if (!setCookieHeader) return;
  
  const newCookie = extractRoblosecurityFromSetCookie(setCookieHeader);
  if (!newCookie || newCookie === usedCookie.cookie) return;
  
  // Update pool and invoke callback
  updateCookieInPool(usedCookie.cookie, newCookie, usedCookie.index);
  await invokeRefreshCallback(usedCookie.cookie, newCookie, usedCookie.index);
}
RoZod checks every response for Set-Cookie headers containing .ROBLOSECURITY and compares against the cookie used for the request.
Rotation detection is thread-safe and works correctly with concurrent requests using cookie pools.

Concurrent request handling

RoZod tracks which cookie was used for each request:
// Multiple concurrent requests
const [result1, result2, result3] = await Promise.all([
  fetchApi(endpoint1, params1), // Uses cookie at index 0
  fetchApi(endpoint2, params2), // Uses cookie at index 1
  fetchApi(endpoint3, params3), // Uses cookie at index 2
]);

// If any cookie is rotated, only that specific cookie is updated
Each request remembers which cookie it used, ensuring correct rotation even with concurrent requests.

Error handling

Handle errors in the refresh callback:
import { configureServer } from 'rozod';

configureServer({
  cookies: process.env.ROBLOX_COOKIE,
  onCookieRefresh: async ({ oldCookie, newCookie, poolIndex }) => {
    try {
      await db.updateCookie(poolIndex, newCookie);
      console.log('Cookie persisted successfully');
    } catch (error) {
      // Log error but don't throw - would break request flow
      console.error('Failed to persist cookie:', error);
      
      // Send alert
      await sendAlert({
        type: 'cookie_rotation_persistence_failed',
        poolIndex,
        error: error.message,
      });
    }
  }
});
Errors in onCookieRefresh are caught and logged to prevent breaking the request flow. Ensure your callback handles errors gracefully.

Rotation frequency

Roblox cookie rotation behavior:
  • Gradual rollout: Not all accounts have rotation enabled
  • Frequency varies: Some accounts rotate daily, others weekly or monthly
  • Triggered by events: May occur after security events or location changes
  • Not predictable: Rotation timing cannot be determined in advance
Implement onCookieRefresh callbacks even if you don’t see rotations immediately. Roblox is gradually enabling this feature.

Best practices

Even if your account doesn’t rotate cookies yet, implement the callback. Roblox is gradually enabling rotation.
Store cookies in a database, file system, or secret manager. Don’t rely on in-memory storage across restarts.
Don’t throw errors from onCookieRefresh. Log them and alert, but let the request flow continue.
After receiving a new cookie, make a test request to verify it works before relying on it.
Track rotation frequency to detect anomalies or security issues with your accounts.
Periodically refresh cookies to ensure they’re fresh and avoid expiration-related issues.

Testing rotation handling

Test your rotation callback:
import { configureServer, refreshCookie } from 'rozod';

let rotationCount = 0;

configureServer({
  cookies: process.env.ROBLOX_COOKIE,
  onCookieRefresh: async ({ oldCookie, newCookie, poolIndex }) => {
    rotationCount++;
    console.log(`Rotation ${rotationCount}:`, {
      poolIndex,
      oldCookieLength: oldCookie.length,
      newCookieLength: newCookie.length,
    });
    
    // Your persistence logic
    await db.updateCookie(poolIndex, newCookie);
  }
});

// Trigger manual refresh to test callback
const result = await refreshCookie(0);
if (result.success) {
  console.log('Callback triggered with new cookie');
}

Migration guide

If you’re currently managing cookies manually:

Before (manual management)

// Old approach - manually tracking cookies
let currentCookie = process.env.ROBLOX_COOKIE;

const result = await fetchApi(endpoint, params, {
  headers: {
    'Cookie': `.ROBLOSECURITY=${currentCookie}`
  }
});

// Manual rotation detection (error-prone)
const newCookie = result.headers.get('set-cookie');
if (newCookie) {
  currentCookie = extractCookie(newCookie);
  // Manually persist
}

After (automatic with RoZod)

// New approach - automatic rotation handling
configureServer({
  cookies: process.env.ROBLOX_COOKIE,
  onCookieRefresh: async ({ newCookie, poolIndex }) => {
    await db.updateCookie(poolIndex, newCookie);
  }
});

// Just make requests - rotation is automatic
const result = await fetchApi(endpoint, params);
RoZod’s automatic rotation is more reliable and handles edge cases like concurrent requests and cookie pools.

Next steps

Cookie pools

Use multiple accounts with rotation

Security features

Understand all security mechanisms

Server authentication

Configure server-side authentication

Error handling

Handle cookie-related errors

Build docs developers (and LLMs) love