Roblox may present authentication challenges like captchas or two-factor authentication (2FA) for security. RoZod automatically detects these challenges and invokes your handler to solve them.
Setting up a challenge handler
Configure a global challenge handler that RoZod will call when challenges occur:
import { setHandleGenericChallenge } from 'rozod' ;
setHandleGenericChallenge ( async ( challenge ) => {
console . log ( 'Challenge type:' , challenge . challengeType );
console . log ( 'Challenge ID:' , challenge . challengeId );
if ( challenge . challengeType === 'captcha' ) {
// Solve captcha and return solution
const solution = await solveCaptcha ( challenge . challengeId );
return {
challengeType: challenge . challengeType ,
challengeId: challenge . challengeId ,
challengeBase64Metadata: solution
};
}
// Return undefined to skip challenge
return undefined ;
});
The challenge handler is called automatically when Roblox returns challenge headers. You don’t need to manually check for challenges.
Challenge types
Roblox uses different challenge types:
Captcha
Two-factor auth
Security questions
FunCaptcha challenges for bot detection: setHandleGenericChallenge ( async ( challenge ) => {
if ( challenge . challengeType === 'captcha' ) {
// Display captcha to user or solve programmatically
const solution = await solveFunCaptcha ({
publicKey: challenge . challengeId ,
pageUrl: 'https://www.roblox.com'
});
return {
challengeType: 'captcha' ,
challengeId: challenge . challengeId ,
challengeBase64Metadata: solution
};
}
});
2FA code verification: setHandleGenericChallenge ( async ( challenge ) => {
if ( challenge . challengeType === 'twostepverification' ) {
// Prompt user for 2FA code
const code = await prompt2FACode ();
return {
challengeType: 'twostepverification' ,
challengeId: challenge . challengeId ,
challengeBase64Metadata: btoa ( code )
};
}
});
Account security verification: setHandleGenericChallenge ( async ( challenge ) => {
if ( challenge . challengeType === 'securityquestions' ) {
// Answer security questions
const answers = await getSecurityAnswers ();
return {
challengeType: 'securityquestions' ,
challengeId: challenge . challengeId ,
challengeBase64Metadata: btoa ( JSON . stringify ( answers ))
};
}
});
Challenge data structure
The challenge object passed to your handler:
type ParsedChallenge = {
/** Type of challenge: 'captcha', 'twostepverification', 'securityquestions', etc. */
challengeType : string ;
/** Unique identifier for this challenge instance */
challengeId : string ;
/** Optional metadata about the challenge */
challengeBase64Metadata ?: string ;
};
Your handler should return the same structure with the solution in challengeBase64Metadata.
Captcha solving
Using a captcha service
Integrate with captcha solving services:
import { setHandleGenericChallenge } from 'rozod' ;
import { Solver } from '2captcha' ; // Example service
const solver = new Solver ( process . env . CAPTCHA_API_KEY );
setHandleGenericChallenge ( async ( challenge ) => {
if ( challenge . challengeType === 'captcha' ) {
try {
const solution = await solver . funcaptcha ({
publickey: challenge . challengeId ,
pageurl: 'https://www.roblox.com' ,
});
return {
challengeType: challenge . challengeType ,
challengeId: challenge . challengeId ,
challengeBase64Metadata: solution . data
};
} catch ( error ) {
console . error ( 'Captcha solving failed:' , error );
return undefined ; // Skip challenge
}
}
});
Captcha solving services cost money per solve. Monitor usage and implement rate limiting to control costs.
Manual captcha solving
For user-facing applications, show captcha UI:
import { setHandleGenericChallenge } from 'rozod' ;
setHandleGenericChallenge ( async ( challenge ) => {
if ( challenge . challengeType === 'captcha' ) {
// Show captcha modal to user
const solution = await new Promise (( resolve ) => {
showCaptchaModal ({
challengeId: challenge . challengeId ,
onSolved : ( token ) => resolve ( token )
});
});
return {
challengeType: challenge . challengeType ,
challengeId: challenge . challengeId ,
challengeBase64Metadata: solution
};
}
});
Two-factor authentication
Handle 2FA challenges by prompting for codes:
import { setHandleGenericChallenge } from 'rozod' ;
setHandleGenericChallenge ( async ( challenge ) => {
if ( challenge . challengeType === 'twostepverification' ) {
// Prompt user for 2FA code
const code = await prompt ({
message: 'Enter your 2FA code:' ,
type: 'text'
});
// Encode code as base64
const encoded = Buffer . from ( code ). toString ( 'base64' );
return {
challengeType: challenge . challengeType ,
challengeId: challenge . challengeId ,
challengeBase64Metadata: encoded
};
}
});
For automated systems, consider storing 2FA backup codes or using app-based authenticators that can be automated.
Challenge flow
How RoZod handles challenges:
Request made : Initial API request is sent
Challenge detected : Roblox returns challenge headers
Handler invoked : Your handleGenericChallenge function is called
Solution returned : Handler returns challenge response
Automatic retry : RoZod retries the request with challenge headers
Success or failure : Request succeeds or challenge fails
// Internal RoZod flow (for reference)
if ( handleGenericChallengeFn ) {
const challenge = parseChallengeHeaders ( response . headers );
if ( challenge && challengeRetries < MAX_CHALLENGE_RETRIES ) {
const data = await handleGenericChallengeFn ( challenge );
if ( data ) {
// Retry with challenge solution
return fetch ( url , info , data , csrfRetries , challengeRetries + 1 );
}
}
}
RoZod retries up to 3 times for challenges. After 3 failures, the original response is returned.
Skipping challenges
Return undefined to skip a challenge:
setHandleGenericChallenge ( async ( challenge ) => {
// Only handle captchas, skip everything else
if ( challenge . challengeType !== 'captcha' ) {
return undefined ;
}
// Solve captcha
const solution = await solveCaptcha ( challenge . challengeId );
return {
challengeType: challenge . challengeType ,
challengeId: challenge . challengeId ,
challengeBase64Metadata: solution
};
});
Skipping challenges (returning undefined) will cause the API request to fail with the original challenge error.
Advanced patterns
Challenge queueing
Queue challenges for batch processing:
import { setHandleGenericChallenge } from 'rozod' ;
const challengeQueue : Array <{
challenge : ParsedChallenge ;
resolve : ( value : ParsedChallenge | undefined ) => void ;
}> = [];
setHandleGenericChallenge ( async ( challenge ) => {
// Add to queue
return new Promise (( resolve ) => {
challengeQueue . push ({ challenge , resolve });
});
});
// Process queue separately
setInterval ( async () => {
if ( challengeQueue . length === 0 ) return ;
const batch = challengeQueue . splice ( 0 , 10 );
const solutions = await solveCaptchaBatch (
batch . map ( item => item . challenge )
);
batch . forEach (( item , i ) => {
item . resolve ( solutions [ i ]);
});
}, 5000 );
Rate limiting challenges
Limit challenge solving to control costs:
import { setHandleGenericChallenge } from 'rozod' ;
let challengesThisHour = 0 ;
const MAX_CHALLENGES_PER_HOUR = 100 ;
setInterval (() => {
challengesThisHour = 0 ;
}, 60 * 60 * 1000 );
setHandleGenericChallenge ( async ( challenge ) => {
if ( challengesThisHour >= MAX_CHALLENGES_PER_HOUR ) {
console . warn ( 'Challenge rate limit reached' );
return undefined ;
}
challengesThisHour ++ ;
const solution = await solveCaptcha ( challenge . challengeId );
return {
challengeType: challenge . challengeType ,
challengeId: challenge . challengeId ,
challengeBase64Metadata: solution
};
});
Fallback strategies
Implement fallbacks when challenge solving fails:
setHandleGenericChallenge ( async ( challenge ) => {
// Try primary solver
try {
const solution = await primarySolver . solve ( challenge );
return {
challengeType: challenge . challengeType ,
challengeId: challenge . challengeId ,
challengeBase64Metadata: solution
};
} catch ( error ) {
console . error ( 'Primary solver failed:' , error );
}
// Try fallback solver
try {
const solution = await fallbackSolver . solve ( challenge );
return {
challengeType: challenge . challengeType ,
challengeId: challenge . challengeId ,
challengeBase64Metadata: solution
};
} catch ( error ) {
console . error ( 'Fallback solver failed:' , error );
}
// Give up
return undefined ;
});
Error handling
Handle errors in challenge solving:
import { setHandleGenericChallenge } from 'rozod' ;
import { fetchApi , isAnyErrorResponse } from 'rozod' ;
setHandleGenericChallenge ( async ( challenge ) => {
try {
const solution = await solveCaptcha ( challenge . challengeId );
return {
challengeType: challenge . challengeType ,
challengeId: challenge . challengeId ,
challengeBase64Metadata: solution
};
} catch ( error ) {
console . error ( 'Challenge solving error:' , error );
// Log for monitoring
await logError ({
type: 'challenge_solving_failed' ,
challengeType: challenge . challengeType ,
error: error . message ,
});
return undefined ; // Skip challenge
}
});
// Handle challenge failures in API calls
const result = await fetchApi ( endpoint , params );
if ( isAnyErrorResponse ( result )) {
if ( result . message . includes ( 'challenge' )) {
console . error ( 'Challenge was required but not solved' );
// Implement retry or alert logic
}
}
Testing
Test your challenge handler:
import { setHandleGenericChallenge } from 'rozod' ;
// Mock challenge handler for testing
setHandleGenericChallenge ( async ( challenge ) => {
console . log ( 'Test: Challenge received' , challenge );
// Return mock solution
return {
challengeType: challenge . challengeType ,
challengeId: challenge . challengeId ,
challengeBase64Metadata: 'mock_solution_for_testing'
};
});
// Make requests that might trigger challenges
const result = await fetchApi ( endpoint , params );
console . log ( 'Test: Result' , result );
Mock solutions will fail in production. Only use mocking for development and testing.
Best practices
Handle all challenge types
Implement handlers for all challenge types you might encounter: captcha, 2FA, security questions, etc.
Track when challenges occur to identify patterns, monitor costs, and detect anomalies.
Set timeouts for challenge solving to avoid hanging requests indefinitely.
Track success rates for challenge solving to detect issues with your solver or configuration.
Cache solutions when possible
Some challenges may be reusable for a short period. Cache solutions to reduce costs.
Implement error tracking to detect and alert on challenge solving failures.
Challenge prevention
Minimize challenges by:
Using realistic user agents : RoZod includes browser-like user agents
Rate limiting requests : Avoid aggressive request patterns
Using cookie pools : Distribute load across multiple accounts
Maintaining good account health : Avoid actions that trigger security flags
import { configureServer } from 'rozod' ;
configureServer ({
cookies: [ cookie1 , cookie2 , cookie3 ],
cookieRotation: 'round-robin' ,
userAgents: [ 'realistic' , 'browser' , 'user agents' ],
userAgentRotation: 'random' ,
});
Next steps
Security features Understand all security mechanisms
Cookie pools Use multiple accounts to reduce challenges
Error handling Handle challenge-related errors
Browser authentication Learn about browser-specific challenges