BubbleCAPTCHA | Procedurally-generated Exploit-resistant Image Recognition Test

:warning: Disclaimers

  • This is a framework for a CAPTCHA. It only tells you if the player passed or failed the test. If you plan on using this for your game, you have to implement the punishment/blocking system yourself!

  • This is an image-based CAPTCHA, meaning it can be bypassed with an image recognition AI. The goal of this CAPTCHA is to deter inferior bots/AFK-farming and make it significantly harder for advanced bots to sneak in.

Hello roblox people, this is a CAPTCHA I’ve designed over the past week. I call it BubbleCAPTCHA because the testing image resembles a big group of bubbles.


Procedurally generated random images
All the images you see are randomly generated. This means there’s no AssetId linked to anything and the test cannot be bypassed using a simple library of samples.

Purely mathematical
The images are made using vector math so the generating algorithm is decently performant. On my PC it takes less than a millisecond to generate an image.

Simple to solve
It only takes a total of two clicks to solve the CAPTCHA. One to reveal the image, one to choose the answer. It’s possible to modify the system to exchange accessibility for security and use a TextBox instead.

The only information revealed to the client to start the test is the image itself (stored as buffers) and a table of choices that the client picks. The direct answer to the test is never sent. Although it’s possible for bots to guess the answer from the choices, it’s exceedingly rare to get it right a few times in a row.

* It’s possible for exploiters to examine the serverside code (this is opensource) used to scramble the answer choices. They can predict where the answer will be based on the randomseed you used to randomly scramble the table of choices. Thus, it’s important that you scramble with care and keep your randomseed a secret.

As of now, this entire system is more of a proof of concept that can be perfected, but it is usable as-is in production.

It has a very simple API to challenge players and get the result:

    p: Player,
    callBack: (UserId: number, pass: boolean, failReason: FailReason?) -> (),
    random: Random,
    duration: number?,
    minTime: number?,

p: The player to challenge.

callBack: Function to call when the test is concluded. Sends to the function the UserId of the player, whether they passed, and the reason why they failed if they did.

random: The Random instance used for generating the image.

duration: Time allowed for the player to answer. The test automatically fails when the time is up.

minTime: Optional, the player must answer after this amount of seconds to not get disqualified.


TimedOut - The player did not answer the challenge in time. Most common bot response.
Incorrect - The player chose the wrong answer.
TooFast - The player answered too fast.
AlreadyAnswered - A bug happened that let the player answer the same challenge twice.
BadData - (not used) The player responded to the challenge with something other than an answer.


The framework was last updated on June 29th, 2024. The live demo and GitHub are up to date, but the direct rbxl download is for the outdated first version.

You can try out the CAPTCHA immediately here. The place is uncopylocked.

And the source code is available on GitHub:

If you prefer/want to see the first version of this CAPTCHA, a direct download is available:

BubbleCAPTCHA.rbxl (78.5 KB)

Honorable Mentions

Thanks to @EnumBobbert for inspiring me to create this.


man this is unique, I like it, too bad you cant use SecretKey to store a random value that you can call with httpservice for the randomseed

I updated the framework to account for a potential flaw in the image generation system:

It might be possible for exploiters to guess the CAPTCHA answer by simply counting the number of bubbles present in the image. This is because each imprinted number has a specific area that shades a predictable amount of bubbles. For example, the number 1 tends to shade the least, and the number 8 tends to shade the most.


To migitate this, I added a quota system to the image generation. After imprinting the number, it will additionally shade more random bubbles to reach a certain amount. The quota is not guaranteed, but it helps obfuscate the easily guessable numbers.

Shading random bubbles may impact the legibility of the image, but it shouldn’t be too big of an issue if you properly blacklist choice combinations to avoid lookalikes. Here’s an example of what the current image generator may create; the answer is 9, and the number 4 is banned from the choices.


Along with the image bubble quota, I also restructured the Challenge API to directly accept a Random instance. It’s probably not necessary, but it hopefully makes it much more difficult for exploiters to reverse engineer the answer by simply reading the script.


And lastly, I extended the demo itself so that the CAPTCHA will have 3 attempts for each “exam” that you send to the player. The change is in the live demo and I updated the video demo in the OP to reflect this change.

1 Like

I might have autism but where is the number here ?


1 Like

Why dont you use editable images?

iirc, it’s still not fully in production; if you have an editableimage in your game, it’ll just error and not show anything


how’s this exploit-resistant if the player is use the exploit? they know what number its gonna show up? i don’t think this gonna work

It’s 2. There’s a blacklist for filtering out lookalikes in the choices. It most closely resembles the number 2 more than anything else in the choices.

What @Artzified said, and also because a 3D grid offers more room for randomization which (hopefully) makes it more difficult to crack by algorithm. It is also future-proof; if the situation demands, you can require the player to drag the ViewportFrame to reveal the image, making it way more tedious to automate. You can even implement optical illusions like what Sethbling did a while back:

Refer to the OP where I explained it:

It should be more exploit-resistant compared to other roblox CAPTCHAs as they tend to use image AssetIds or just a simple button click to verify the player. This particular CAPTCHA procedurally generates each image and automatically times-out the player if they don’t answer the challenge.