Sticky vs rotating in a checkout bot: where each one wins a drop
2026-10-08 · Bazyl

A checkout bot runs two kinds of task, and each wants a different port. Anything that asks the same question over and over, a restock monitor, a queue poller, a stock checker, belongs on port 4242 and gets a new IP every request. The one task that has to look like one visitor from cart to confirmation belongs on port 4243 with a single session id. After this post you can make that call per task and size the session to fit.
The mistake I see most is one port for everything. Usually it's sticky, with a fresh session id minted on every request, which is rotating with extra steps and none of the benefit.
Rotating wins everything that repeats
Port 4242 hands out a new IP on every request, and that is exactly what a monitor wants: it polls the same product page every few seconds, and no single address carries the load. There is nothing to configure. The string is the credentials, a country token and the port:
http://USER-country-us:[email protected]:4242
That one line serves every monitor task you run. Ten monitors on ten pages use the same string, and each request they make leaves from somewhere else. Stock checkers, price watchers and the "is the page up yet" loop before a drop all live here.
Sticky wins the one task that must hold still
A checkout is a sequence: cart, shipping, payment, confirmation. If the IP changes halfway, the shop's session sees the address change at the payment step, and that is the step you least want to repeat. Port 4243 plus two tokens holds one IP for the whole sequence:
http://USER-country-us-session-m2p8zq4r-lifetime-30:[email protected]:4243
-session-m2p8zq4r names the session, and the same id keeps the same IP.
-lifetime-30 is how long that id is allowed to hold it, in minutes. Any value from 1
to 1440 works, and 1,440 minutes, a full 24 hours, is the ceiling.
Use it for the checkout task and for nothing else. A monitor on a sticky string sits on one address for the whole run, which is the opposite of what a monitor is for.
Size the lifetime to the task, plus a margin
The lifetime is the task's length plus a little. A checkout that takes ten minutes gets 15 or 20. A queue that can hold you for an hour gets 90. The lifetime is what keeps the id on its IP, so a value shorter than the task means the id stops holding it in the middle of the one step you needed it for.
Longer buys nothing. The ceiling is 24 hours whatever you set, so there is no prize for always using the maximum and no prize for being stingy. Set it to what the task needs and move on. For a Pokémon Center drop where the queue runs long, that is a lifetime in hours, not minutes.
One id per task, generated once
The id is eight lowercase letters or digits, and the rule is short: generate it when the task starts, store it on the task, use it for every request the task makes. Not per request, because a new id per request is rotating. Not shared between tasks, because two tasks on one id share one IP and look like one visitor doing two checkouts.
Changing the id is your restart button. A task that needs a fresh IP mints a new id and
starts over; the old session is never used again. In the code below restart() is that
button, and it is the only place an id is created.
IP churn is normal, so handle it instead of fighting it
A residential exit is a real device on a real connection, and real connections drop. When one does, the address your session was pinned to can go with it. Treat that as weather. A bot that assumes the IP is stable for the whole lifetime will lose a checkout to it eventually; a bot that checks is fine.
Two habits cover it. First, check the exit before the step you can't afford to lose: one
request to https://ipinfo.io/json through the sticky string, compare the IP with the
one you saw at cart, and only then submit the payment. Second, when the IP has changed,
restart the task on a new id instead of pushing on with a session that now looks like
two visitors. Afterwards, the dashboard's statistics page shows success rate, latency
and a per-domain table of your top targets, which tells you whether a bad night was one
target or the whole run.
What we don't do is promise an outcome on any site. We answer for the proxy being live and reachable. Whether a given shop accepts the checkout is between your bot and the shop.
The gateway is not the exit
Pick eu-, us- or ap-residential2.basilproxies.com by where the bot runs, not by
where you want to exit. A bot on a server in Frankfurt checking out from US addresses
wants the eu- door and -country-us on the username. The door is latency; the token is
the country. The code below uses the us- gateway because that is where the bot lives.
Both strings, one id per task
import secrets
import string
import requests
GATEWAY = "us-residential2.basilproxies.com" # nearest to the bot; the exit comes from the token
USER, PASS = "USER", "PASS"
ALPHABET = string.ascii_lowercase + string.digits
def rotating(country="us"):
"""Port 4242: a new IP on every request. Monitors, checkers, pollers."""
return f"http://{USER}-country-{country}:{PASS}@{GATEWAY}:4242"
def sticky(session_id, lifetime, country="us"):
"""Port 4243: one IP for one task. 1 to 1440 minutes; 1440 (24 h) is the ceiling."""
assert 1 <= lifetime <= 1440, "lifetime is 1 to 1440 minutes"
return f"http://{USER}-country-{country}-session-{session_id}-lifetime-{lifetime}:{PASS}@{GATEWAY}:4243"
def new_session_id():
"""8 lowercase letters or digits. Once per task, never per request."""
return "".join(secrets.choice(ALPHABET) for _ in range(8))
def exit_ip(proxy):
"""What the target sees. Check it before the step you can't afford to lose."""
r = requests.get("https://ipinfo.io/json", proxies={"http": proxy, "https": proxy}, timeout=10)
return r.json()["ip"]
class CheckoutTask:
def __init__(self, minutes):
self.minutes = minutes
self.restart()
def restart(self):
self.session_id = new_session_id() # a new id = a fresh IP
self.proxy = sticky(self.session_id, self.minutes + 5) # the task, plus a margin
self.ip = None
def pin(self):
"""Call at cart, call again before payment, compare."""
self.ip = exit_ip(self.proxy)
return self.ip
monitor = rotating() # every monitor request leaves from a different IP
task = CheckoutTask(minutes=15) # one id, generated once, lifetime 20
first = task.pin() # at cart
# ... add to cart, shipping ...
if task.pin() != first: # the exit moved under the task
task.restart() # fresh id, fresh IP, start the task again
Everything else about the string, the token order, city and ASN targeting and the three output formats, is in the parser post.
Build the monitor string first, then the checkout task, and check both against the restock strings on the sneakers and retail page.
Common questions
- Which port does a restock monitor use?
- Port 4242, rotating. Every request exits from a different IP, so a monitor polling the same page never leans on one address. Nothing to configure and no ids to manage.
- How long should a sticky session's lifetime be?
- A little longer than the task it serves: a 10-minute checkout gets 15 or 20. Any value from 1 to 1,440 minutes works, and 1,440 minutes (24 hours) is the ceiling.
- What happens if two tasks share one session id?
- They share one IP. Generate one id per task when the task starts, keep it for every request of that task, and mint a new one only when you want a fresh IP.
- Does the gateway I pick change my exit country?
- No. The eu-, us- and ap- gateways are three doors into one pool; pick the nearest for latency. The exit country comes from the -country- token on the username.