Sticky sessions keep the same IP for a set duration. Rotating sessions assign a new IP per request. Choosing the wrong one inflates CAPTCHA rates and wastes API credits.
How Each Mode Works
STICKY SESSION:
Request 1 ──▶ IP: 192.168.1.50 ──▶ site.com/login
Request 2 ──▶ IP: 192.168.1.50 ──▶ site.com/dashboard
Request 3 ──▶ IP: 192.168.1.50 ──▶ site.com/account
(Same IP for all requests in the session window)
ROTATING SESSION:
Request 1 ──▶ IP: 192.168.1.50 ──▶ site.com/page1
Request 2 ──▶ IP: 10.0.0.77 ──▶ site.com/page2
Request 3 ──▶ IP: 172.16.5.22 ──▶ site.com/page3
(Different IP per request)
Head-to-Head Comparison
| Factor | Sticky | Rotating |
|---|---|---|
| IP per request | Same for 1-30 min | New each request |
| Session consistency | High | None |
| CAPTCHA rate (multi-step flows) | Low (5-10%) | High (30-50%) |
| CAPTCHA rate (single pages) | Medium (10-20%) | Low (5-10%) |
| Token validity | Higher — same IP | Risk of mismatch |
| Speed | Fast (reuses connection) | Slower (new connection) |
| Ban risk | Higher if flagged | Low — new IP each time |
| Cost | Same or slightly more | Same |
| Best for | Login, checkout, auth | Bulk scraping, search |
When Sticky Sessions Win
Multi-Step Workflows
CAPTCHAs validate tokens against the requesting IP. If your IP changes between solving and submitting, the token may be rejected.
import requests
import time
CAPTCHAAI_KEY = "YOUR_API_KEY"
CAPTCHAAI_URL = "https://ocr.captchaai.com"
# Sticky session: same IP for the entire login flow
STICKY_PROXY = {
"http": "http://user-session-abc123:pass@proxy.example.com:5000",
"https": "http://user-session-abc123:pass@proxy.example.com:5000",
}
session = requests.Session()
session.proxies = STICKY_PROXY
session.headers.update({
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
"AppleWebKit/537.36 Chrome/126.0.0.0 Safari/537.36",
})
def login_with_captcha(url, sitekey, username, password):
"""Full login flow — MUST use same IP throughout."""
# Step 1: Load login page (IP: 192.168.1.50)
session.get(url)
# Step 2: Solve CAPTCHA (solved against IP: 192.168.1.50)
token = solve_recaptcha(sitekey, url)
# Step 3: Submit login (MUST be IP: 192.168.1.50)
resp = session.post(url, data={
"username": username,
"password": password,
"g-recaptcha-response": token,
})
return resp.status_code == 200
def solve_recaptcha(sitekey, pageurl):
resp = requests.post(f"{CAPTCHAAI_URL}/in.php", data={
"key": CAPTCHAAI_KEY,
"method": "userrecaptcha",
"googlekey": sitekey,
"pageurl": pageurl,
"json": 1,
})
task_id = resp.json()["request"]
for _ in range(60):
time.sleep(5)
result = requests.get(f"{CAPTCHAAI_URL}/res.php", params={
"key": CAPTCHAAI_KEY, "action": "get",
"id": task_id, "json": 1,
})
data = result.json()
if data["request"] != "CAPCHA_NOT_READY":
return data["request"]
raise TimeoutError("CAPTCHA solve timeout")
Why IP Consistency Matters
Sticky session:
1. Browser loads page → IP: 5.5.5.5
2. CAPTCHA solved → Token bound to session
3. Form submitted → IP: 5.5.5.5 ✅ Token accepted
Rotating session:
1. Browser loads page → IP: 5.5.5.5
2. CAPTCHA solved → Token bound to session
3. Form submitted → IP: 9.9.9.9 ❌ Token may be rejected
When Rotating Sessions Win
Bulk Page Scraping
Each page is independent. Rotating IPs spreads requests across a larger pool, reducing per-IP detection.
import concurrent.futures
ROTATING_PROXY = {
"http": "http://user:pass@rotating.proxy.example.com:5000",
"https": "http://user:pass@rotating.proxy.example.com:5000",
}
def scrape_page(url):
"""Each request gets a fresh IP automatically."""
resp = requests.get(
url,
proxies=ROTATING_PROXY,
headers={"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)"},
timeout=30,
)
if resp.status_code == 200:
return url, resp.text
return url, None
urls = [f"https://example.com/products?page={i}" for i in range(1, 501)]
with concurrent.futures.ThreadPoolExecutor(max_workers=10) as pool:
results = list(pool.map(scrape_page, urls))
success = sum(1 for _, html in results if html)
print(f"Scraped: {success}/{len(urls)}")
Search Engine Scraping
Search engines track query volume per IP. Rotating avoids rate limits:
def scrape_search(query, pages=10):
results = []
for page in range(pages):
# Each request → new IP
resp = requests.get(
"https://search-engine.example.com/search",
params={"q": query, "start": page * 10},
proxies=ROTATING_PROXY,
timeout=20,
)
results.append(resp.text)
time.sleep(2)
return results
Hybrid Strategy
Use both modes in the same pipeline:
class HybridProxyManager:
"""Sticky for multi-step flows, rotating for single requests."""
def __init__(self, provider_host, username, password, port=5000):
self.host = provider_host
self.username = username
self.password = password
self.port = port
def get_rotating_proxy(self):
return {
"http": f"http://{self.username}:{self.password}@{self.host}:{self.port}",
"https": f"http://{self.username}:{self.password}@{self.host}:{self.port}",
}
def get_sticky_proxy(self, session_id, duration_min=10):
sticky_user = f"{self.username}-session-{session_id}-ttl-{duration_min}"
return {
"http": f"http://{sticky_user}:{self.password}@{self.host}:{self.port}",
"https": f"http://{sticky_user}:{self.password}@{self.host}:{self.port}",
}
proxy_mgr = HybridProxyManager("proxy.example.com", "user", "pass")
def scrape_with_auto_strategy(url, needs_login=False):
if needs_login:
# Multi-step → sticky
import uuid
session_id = uuid.uuid4().hex[:8]
proxy = proxy_mgr.get_sticky_proxy(session_id)
else:
# Single page → rotating
proxy = proxy_mgr.get_rotating_proxy()
return requests.get(url, proxies=proxy, timeout=30)
Node.js Implementation
const axios = require("axios");
const CAPTCHAAI_KEY = "YOUR_API_KEY";
const PROXY_HOST = "proxy.example.com";
const PROXY_PORT = 5000;
function getProxy(mode, sessionId = null) {
const user =
mode === "sticky" ? `user-session-${sessionId}` : "user";
return {
proxy: {
host: PROXY_HOST,
port: PROXY_PORT,
auth: { username: user, password: "pass" },
},
};
}
// Rotating: bulk scrape
async function scrapePages(urls) {
const results = [];
for (const url of urls) {
const config = getProxy("rotating");
const resp = await axios.get(url, { ...config, timeout: 30000 });
results.push({ url, data: resp.data });
}
return results;
}
// Sticky: login flow
async function loginFlow(loginUrl, sitekey, credentials) {
const sessionId = Date.now().toString(36);
const config = getProxy("sticky", sessionId);
// Step 1: Load page
await axios.get(loginUrl, config);
// Step 2: Solve CAPTCHA
const submitResp = await axios.post(
"https://ocr.captchaai.com/in.php",
null,
{
params: {
key: CAPTCHAAI_KEY,
method: "userrecaptcha",
googlekey: sitekey,
pageurl: loginUrl,
json: 1,
},
}
);
const taskId = submitResp.data.request;
let token;
for (let i = 0; i < 60; i++) {
await new Promise((r) => setTimeout(r, 5000));
const res = await axios.get("https://ocr.captchaai.com/res.php", {
params: { key: CAPTCHAAI_KEY, action: "get", id: taskId, json: 1 },
});
if (res.data.request !== "CAPCHA_NOT_READY") {
token = res.data.request;
break;
}
}
// Step 3: Submit with same IP
return axios.post(
loginUrl,
{
...credentials,
"g-recaptcha-response": token,
},
config
);
}
Decision Matrix
| Workflow | Recommended | Why |
|---|---|---|
| Login / signup | Sticky | Token bound to IP |
| Checkout / payment | Sticky | Session cookies need same IP |
| Bulk page scraping | Rotating | Distributes load |
| Search engine scraping | Rotating | Avoids per-IP rate limits |
| API polling (same endpoint) | Rotating | Reduces per-IP fingerprint |
| Multi-page navigation | Sticky | Cookies tracked by IP |
| Price monitoring | Rotating | Many independent requests |
| Account management | Sticky | Session integrity |
| Form submission with CAPTCHA | Sticky | Token–IP binding |
| Ad verification | Rotating | Simulate diverse users |
Sticky Session Duration
| Duration | Best For | Risk |
|---|---|---|
| 1 min | Quick form submits | May expire mid-flow |
| 5 min | Standard login + actions | Good balance |
| 10 min | Complex multi-page flows | Moderate ban risk |
| 30 min | Extended browsing sessions | Higher ban risk |
| 60 min | Long account operations | Highest ban risk |
Rule of thumb: Set sticky duration to 2x your expected flow time.
Troubleshooting
| Issue | Cause | Fix |
|---|---|---|
| CAPTCHA token rejected after solve | IP changed between solve and submit | Switch to sticky session |
| High CAPTCHA rate on single pages | Same sticky IP hitting too many pages | Switch to rotating |
| Session cookies lost | Sticky session expired | Increase TTL duration |
| Slow rotating performance | Connection overhead per new IP | Use connection pooling |
| Banned IP in sticky session | All requests share the flagged IP | Reduce session TTL or add delays |
FAQ
Can I mix sticky and rotating in one project?
Yes. Use the hybrid approach — sticky for authentication flows, rotating for data collection. Most proxy providers support both modes on the same plan.
Does sticky session guarantee the same IP?
Most providers guarantee it within the TTL window. If the IP becomes unavailable (carrier reassignment, pool rotation), a new IP is assigned.
Which mode is cheaper?
Same price — you pay per GB or per request regardless of mode. The cost difference comes from CAPTCHA rates: wrong mode → more CAPTCHAs → higher CaptchaAI spend.
How do I set sticky sessions with my proxy provider?
Most use a session ID in the username: user-session-abc123. Check your provider's docs for the exact format. We cover Bright Data, Smartproxy, and Oxylabs in dedicated guides.
Related Guides
Match your proxy strategy to your workflow — get your CaptchaAI key to handle CAPTCHAs in any session mode.
Discussions (0)
Join the conversation
Sign in to share your opinion.
Sign InNo comments yet.