Solve BotDeflector without a browser, on standalone sites and on the Queue-it waiting room or softblock that Footlocker, Arsenal, Ticketmaster, ... put in front of a waiting room. Antibots part is handled by TakionAPI.
Five flows, all running end to end against the real thing, no headless browser involved.
- Quick Start
- Important Notes
- How to Bypass BotDeflector
- Queue-it and the Softblock
- The Icon Challenge on Its Own
- TakionAPI Solution
- FAQ
- Connect with me
Create a free account on the TakionAPI Dashboard to start your trial and get your TAKION_API_KEY.
git clone https://github.com/aster-god/botdeflector-bypass.git
cd botdeflector-bypass
pip install -r requirements.txtCopy .env.example to .env, drop your TAKION_API_KEY in, and run the headline flow, a Queue-it softblock cleared end to end:
python softblock.pyFor a standalone BotDeflector site, or the icon captcha on its own:
python classic_solve.py # invisible only, then loads the page the token unlocks
python full_flow_solve.py # invisible + icon + invisible on one site key
python icon_solve.py # just the icon captcha, coordinates back in ~70msEvery script has defaults that run on a clean clone, and every input is a flag when you want your own target:
python softblock.py --customer footlocker --event stgtimtest --target 'https://staging.kidsfootlocker.com/'
python classic_solve.py --site-key aZIq7m7j --account-id 5fc28a02-8f2f-4903-8dda-5ba2bf149683
python icon_solve.py --lib 1783351600 --ico 3504...516.png --img CKFX...RN.webp --top 6Already holding a flow token you pulled off a live page? queue_it_solve.py takes it straight, from --token or from BOTDEFLECTOR_FLOW_TOKEN in .env:
python queue_it_solve.py --token 'eyJhbGciOiJIUzI1NiIs...'Every script takes --proxy ip:port or --proxy ip:port:user:pass, and runs without one out of the box, which is the fastest way to see it work. Read IP and Proxy Management before you point any of it at a real event.
Every example shares one client, takion_botdeflector.py. It keeps the API key and the proxy pinned together for the whole session, because BotDeflector stamps the requesting IP into the token it hands back:
{"accountId": "5fc28a02-...", "siteKey": "aZIq7m7j", "sessionId": "a97b5c43-...", "requestingIP": "x.x.x.x", "solutionTime": 0, "exp": 1788103166}Solve on one exit and redeem from another and you've handed the origin a token that disagrees with the connection carrying it. Don't.
I keep this repo up to date with any changes to the flows. Always refer to it for a working example.
This part is general. It's about BotDeflector wherever it runs, not just behind Queue-it. Only the way you get hold of the challenge changes.
BotDeflector shows up in two shapes and the API takes both.
Classic. The widget is embedded in the page with a site key and an account id. You pass those two, you get a token back, and the token goes on the request as tt_solutions. Nothing else is needed, the solver mints its own session. This is classic_solve.py and full_flow_solve.py.
solve_data = solver.solve("aZIq7m7j", "5fc28a02-8f2f-4903-8dda-5ba2bf149683")Queue-it. There is no site key to pass. The challenge arrives as a flow token, a short-lived JWT printed into the page, and everything the solver needs is inside it. You hand over the token and nothing else:
solve_data = solver.solve_queue_it(flow_token)Rule of thumb, if you can read a siteKey off the embed you're in classic mode, if you're staring at a botDeflectorJwtToken you're in queue-it mode.
The flow token is the whole contract. Decode the middle segment and it tells you exactly what you're being asked to do:
{
"accountId": "footlocker",
"siteKey": "stgtimtest",
"challengeFlow": [{"type": "inv", "complexity": 1}, {"type": "icon", "complexity": 1, "allowAudio": true}],
"sessionId": "ee1234a0-807f-482b-a368-4accda91ef85",
"iat": 1788103153,
"exp": 1788103273
}exp is always iat + 120. A queue-it mode solve of an inv + icon flow lands between 3 and 7 seconds, so the budget is comfortable if you use the token as soon as you mint it. Sit on one for a minute while you set something else up and the API answers:
{"error": "flow_token was rejected, it is expired or already used. Mint a fresh one and send it within 120s"}The examples check the TTL locally before spending a solve, which is the cheap way round. And note the or already used part, a flow token is single use.
challengeFlow is an ordered list and the solver walks it in order. Two types do the work:
| Type | What it is | Cost |
|---|---|---|
inv |
Invisible, a proof of work the browser grinds through | ~1.5s |
icon |
Pick the icons in the order the legend shows them | a couple of seconds on top |
Each entry carries its own complexity, which is the knob the operator turns when they want the proof of work to hurt, and icon also carries allowAudio for the accessible variant. The response tells you what actually ran, and the names normalise (inv in the token comes back as invisible):
{"token": "eyJhbGciOi...", "solve_time_ms": 3316, "challenges": ["invisible", "icon"]}Both configurations are one call from your side, which is the nice part. full_flow_solve.py exists purely to prove the three step chain works, it differs from the classic one by a site key.
Two different stories depending on where BotDeflector is running.
Standalone. botdeflector.eu barely cares. The examples run without a proxy and pass.
Behind Queue-it. dual.challenge.queue-it.net rate limits IPs, and hard. If you're solving at any volume you need proxies, and it has to be the same proxy the rest of your task runs on, because the verify call records the exit IP it saw:
"sessionInfo": {"sessionId": "ee1234a0-...", "checksum": "IqWm49kwGw9xiWnhXT9gII9Z2kyXAVFq3kOK4BqJnzU=", "sourceIp": "x.x.x.x", "challengeType": "botdeflector", "version": 6, "customerId": "footlocker", "waitingRoomId": "stgtimtest"}Residential or mobile. Pass the proxy once and the client uses it for the solve and for its own requests:
solver = TakionBotDeflector(api_key, "ip:port:user:pass")
session.proxies = solver.proxiesThis is the part people actually come here for, so let's walk the whole thing.
Queue-it's softblock is the interstitial you land on before the waiting room, the one that decides whether you're allowed to take a place in line at all. It lives at:
https://{customer}.queue-it.net/softblock/?c={customer}&e={event}&t={url-encoded target}
The page picks a challenge from whatever the operator configured, and BotDeflector is one option among several. Read the config it prints into the HTML and you can see the whole menu:
challenges: [{"name":"BotDeflector","hasAnimation":false}],
botDeflectorSource: 'https://dual.challenge.queue-it.net/api.js',
botDeflectorVerifyEndpoint: '/spa-api/queue/footlocker/stgtimtest/challenge/botdeflector/verify',Sitting right next to it in that config are reCaptchaSource, turnstileSource, akamaiBotManagerHeaderVerificationSource and an invite-only email verification. The softblock picks one of them, so don't assume BotDeflector just because you saw it once on that customer.
Other than during a softblock WAF, the botdeflector challenge can be served in a Queue-IT Queue as a required challenge step before entering it. It depends on the website setup.
Load the softblock page and the token is sitting in the HTML:
flow_token = re.search(r"botDeflectorJwtToken:\s*'([^']+)'", softblock_response.text).group(1)Take the verify endpoint and the in-queue URL off the same page while you're there. Both are printed as config and both move around per region and per queuePathPrefix, so rebuilding them by hand is a job you'd only have to redo:
inqueueUrl: '%2F%3Fc%3Dfootlocker%26e%3Dstgtimtest%26t%3Dhttps%253A%252F%252Fstaging.kidsfootlocker.com%252F%26cid%3Den-US',The page also sets Queue-it-visitorsession on that first load. Keep it, the rest of the flow rides on the same session.
You solve the token, then you post the solution back to the endpoint the page named. Note what goes in sessionId, it's the flow token, not the sessionId field inside it:
verify_response = session.post(
f"https://{CUSTOMER}.queue-it.net{verify_endpoint}",
json={
"challengeType": "botdeflector",
"sessionId": flow_token,
"solution": solution,
"customerId": account_id,
"eventId": site_key,
"challengesIssuedByReason": None,
"version": 6,
},
)customerId and eventId are the accountId and siteKey you just decoded out of the token, which reads backwards the first time you see it, Queue-it's naming and BotDeflector's naming don't line up. challengesIssuedByReason is null on a plain softblock, the page prints its real value when there is one.
A pass is {"isVerified": true, "sessionInfo": {...}} and that sessionInfo is not decoration. BotDeflector answers doesIssueEnqueueTokens() with false, so the verified session is all you get, and it goes back into the enqueue call as an url encoded scv parameter. Skip that and a live event drops you straight back on /softblock. softblock.py does the hop and reads the queueittoken out of the redirect chain.
Sometimes you don't want the whole flow solved for you. You're driving a real browser, or an extension, or a mobile webview, and the only thing you can't do is look at the picture. That's icon_solve.py.
Three icons in the legend, a background chewed up with colour noise and overlapping shapes, click them in the legend's order. Easy for you, and the whole point of the challenge is that it isn't easy for the thing you're writing.
Pull lib, ico and img out of your own /icon/get challenge token, fetch the two images and post the bytes:
legend = requests.get(f"https://dual.challenge.queue-it.net/icons/{LIB}/ico/{ICO}").content
background = requests.get(f"https://dual.challenge.queue-it.net/icons/{LIB}/bgnd/{IMG}").content
solve_data = solver.solve_icon(background, legend, top=6)You get coordinates back, in click order, on the background image at its natural size:
detections=47 solve_time_ms=67
solution=[{"x": 206, "y": 79}, {"x": 165, "y": 144}, {"x": 98, "y": 45}]
orderings=6 ranked alternatives
Around 70ms, no proxy, no session, no token. It's the cheapest thing in the repo by a distance.
orderings is the part worth reading. The detector is confident about which icons, less so about the order when two candidates are close, so top asks for that many ranked alternatives. First one refused, submit the second. solutions is the same list in {x, y} form for whatever you're feeding, and solution_fingerprint is there so you can tell two solves of the same image apart in your logs.
Unless you want to start pulling apart dual.challenge.queue-it.net/api.js, working out the proof of work, training something for the icon grid, and, the annoying part, keeping all of it up to date every time it changes, we at TakionAPI already do that for you. You focus on your business logic, we handle the bypass.
takion_botdeflector.py is the whole client, ready to drop in.
- TakionAPI Docs
- TakionAPI Trial
- TakionAPI Discord for custom development and support.
Start with a free trial at takionapi.tech to get a key with some tokens in it. Nothing more than an email required, run your tests and reach out for anything.
One endpoint, two modes. Classic:
curl -X POST 'https://botdeflector.takionapi.tech/solve' \
-H 'x-api-key: TAKION_API_XXX' \
-H 'content-type: application/json' \
-d '{"site_key": "aZIq7m7j", "account_id": "5fc28a02-8f2f-4903-8dda-5ba2bf149683", "proxy": false}'Queue-it, where the flow token replaces both:
curl -X POST 'https://botdeflector.takionapi.tech/solve' \
-H 'x-api-key: TAKION_API_XXX' \
-H 'content-type: application/json' \
-d '{"mode": "queue-it", "flow_token": "eyJhbGciOiJIUzI1NiIs...", "proxy": false}'Or with the included client:
from takion_botdeflector import TakionBotDeflector
solver = TakionBotDeflector("TAKION_API_XXX", "ip:port:user:pass")
solve_data = solver.solve("aZIq7m7j", "5fc28a02-8f2f-4903-8dda-5ba2bf149683")
solve_data = solver.solve_queue_it(flow_token)Both answer with token, solve_time_ms and the challenges that actually ran. The failures worth knowing:
| Status | Body | Meaning |
|---|---|---|
401 |
Invalid API key |
key is wrong or not cleared for this product |
401 |
No API key provided |
you forgot the x-api-key header |
400 |
site_key is required |
classic mode with nothing to solve against |
400 |
flow_token was rejected... |
stale or already spent, mint a new one |
The icon solver takes raw image bytes, base64'd, and nothing else. No key material, no session, no proxy:
solve_data = solver.solve_icon(background, legend, top=6)A 503 here is the service being full rather than anything wrong with your request. Back off and retry, don't rebuild the payload.
The flow token keeps coming back rejected.
It's expired or you already spent it. They live 120 seconds from iat and they're single use.
isVerified: truebut the queue still bounced me.
You stopped one step early. BotDeflector never issues an enqueue token, it hands you a verified session instead, so the sessionInfo off the verify has to go back into the enqueue call as scv, url encoded. That's the last step of softblock.py.
The softblock page has no
botDeflectorJwtTokenin it.
Botdeflector is not required on the page.
Do I need a proxy?
Not on botdeflector.eu. Yes on anything behind dual.challenge.queue-it.net, it rate limits IPs and it isn't shy about it. Use the same one your task runs on.
Can I just solve the icon and drive the rest myself?
That's exactly what icon_solve.py is for. Post the two images, get click coordinates back in about 70ms, feed them to whatever is holding the widget. Browser, extension, webview, it doesn't care.
I'm on a different BotDeflector site, do you support it?
Change the site_key and the account_id and you're most of the way there. Not sure about your target's flow? Hit us on Discord.
