7.6 KiB
Steam Account Registration — Platform-Specific Techniques
Extends the bulk-account-registration pipeline with Steam-specific flows.
Covers the full lifecycle: form submission → reCAPTCHA v2 → email verification → Steam Guard activation.
Key Differences from Generic Registration
Steam is NOT a typical REST API registration. It's a multi-step web form with:
- reCAPTCHA v2 (not Turnstile — use
RecaptchaV2TaskProxylesson 2captcha) - AJAX email availability check before form submission (
/join/ajaxverifyemail) - RSA-encrypted password submission for login (not for registration itself)
- Email verification code (5-char alphanumeric, NOT numeric)
- Optional Steam Guard mobile authenticator activation via
ITwoFactorService
Flow Overview
GET /join/ → extract sessionid cookie
↓
2captcha: solve reCAPTCHA v2 (sitekey: 6LdIFr0Z...)
↓
POST /join/ajaxverifyemail → check email availability
↓
POST /join/createaccount → submit form with g-recaptcha-response
↓
Poll email (IMAP/GuerrillaMail) → extract 5-char Steam Guard code
↓
(Optional) POST /steamguard/add_ajax → activate mobile authenticator
↓
(Optional) ITwoFactorService/AddAuthenticator → extract shared_secret
reCAPTCHA v2 on Steam
Steam uses reCAPTCHA v2 (checkbox), NOT Turnstile and NOT invisible reCAPTCHA.
Sitekey: 6LdIFr0ZAAAAAO3vz0O0OQOyjusTBucR2cwXTO4n
Page URL: https://store.steampowered.com/join/
2captcha solve (via createTask API, not legacy in.php):
payload = {
"clientKey": "YOUR_2CAPTCHA_KEY",
"task": {
"type": "RecaptchaV2TaskProxyless",
"websiteURL": "https://store.steampowered.com/join/",
"websiteKey": "6LdIFr0ZAAAAAO3vz0O0OQOyjusTBucR2cwXTO4n",
"isInvisible": False,
}
}
# POST https://api.2captcha.com/createTask → poll /getTaskResult
# Solution field: solution.gRecaptchaResponse
Difference from Turnstile: Turnstile uses method=turnstile on legacy API with sitekey and pageurl params. reCAPTCHA v2 uses RecaptchaV2TaskProxyless on the createTask JSON API. Do NOT use Turnstile method for Steam — it's reCAPTCHA.
Form Submission Details
Email Availability Check
POST https://store.steampowered.com/join/ajaxverifyemail
Data: email=user@domain.com&sessionid=<sessionid>
Headers: X-Requested-With: XMLHttpRequest, Referer: .../join/
Response: {"bEmailAvailable": true} or false
Account Creation
POST https://store.steampowered.com/join/createaccount
Data:
accountname=<username>
password=<password>
email=<email>
email_confirm=<email>
captchagid=-1
captcha_text=
iAgree=1
ticket=
g-recaptcha-response=<2captcha_token>
count=7 # country code (7=US)
l=english
creation_sessionid=<sessionid>
No RSA encryption needed for registration — that's only for login.
RSA Password Encryption (Login Only)
When logging into an existing account, Steam requires RSA-encrypted passwords:
# 1. Get RSA key
POST https://store.steampowered.com/login/getrsakey/
Data: username=<username>&donotcache=<timestamp_ms>
Response: {"success": true, "publickey_mod": "<hex>", "publickey_exp": "<hex>", "timestamp": "<ts>"}
# 2. Encrypt password (PKCS1 v1.5)
mod = int(publickey_mod, 16)
exp = int(publickey_exp, 16)
pw_int = int.from_bytes(password.encode("utf-8"), "big")
encrypted_int = pow(pw_int, exp, mod)
encrypted_b64 = base64.b64encode(encrypted_int.to_bytes(...))
# 3. Submit login
POST https://store.steampowered.com/login/dologin/
Data: username=...&password=<encrypted_b64>&rsatimestamp=<ts>&emailauth=<code>...
Email Verification Code Extraction
Steam sends a 5-character alphanumeric code (uppercase letters + digits). Pattern:
re.search(r'\b([A-Z0-9]{5})\b', email_body)
The email comes from noreply@steampowered.com with subject containing "Steam Guard".
IMAP search:
client.search(["FROM", "noreply@steampowered.com"])
client.search(["SUBJECT", "Steam Guard"])
GuerrillaMail: Subject line and body both contain the code. The _extract_steam_code() function handles HTML and plain text.
Steam Guard Mobile Authenticator (TOTP)
Steam uses a custom TOTP implementation:
- Algorithm: HMAC-SHA1
- Period: 30 seconds
- Digits: 5
- Alphabet:
23456789BCDFGHJKMNPQRTVWXY(no 0, 1, A, E, I, O, L, S, U, Z — avoids ambiguous characters)
TOTP Generation
import hmac, hashlib, base64, struct, time
def generate_steam_totp(shared_secret_b64: str, timestamp: int = None) -> str:
if timestamp is None:
timestamp = int(time.time())
secret = [REDACTED])
time_bytes = struct.pack(">Q", timestamp // 30)
hmac_result = hmac.new(secret, time_bytes, hashlib.sha1).digest()
offset = hmac_result[-1] & 0x0F
code_int = struct.unpack(">I", hmac_result[offset:offset + 4])[0] & 0x7FFFFFFF
steam_alphabet = "23456789BCDFGHJKMNPQRTVWXY"
code = ""
for _ in range(5):
code += steam_alphabet[code_int % len(steam_alphabet)]
code_int //= len(steam_alphabet)
return code
Activating Steam Guard (extracting shared_secret)
Endpoint 1 (community page):
POST https://steamcommunity.com/steamguard/add_ajax
Data: sessionid=...&op=add_authenticator&device_identifier=android:<uuid>&sms_phone_id=1&access_token=
[REDACTED] {"success": true, "shared_secret": "<base64>", "identity_secret": "<base64>", ...}
Endpoint 2 (API — more reliable):
POST https://api.steampowered.com/ITwoFactorService/AddAuthenticator/v0001
Data: steamid=<steamid64>&authenticator_time=<server_time>&authenticator_type=1&device_identifier=<device_id>&sms_phone_id=1
# Then finalize:
POST https://api.steampowered.com/ITwoFactorService/FinalizeAddAuthenticator/v0001
Data: steamid=<steamid64>&authenticator_code=<generated_totp>&authenticator_time=<server_time>&activation_code=
Device ID format: android:XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX (lowercase hex UUID).
Getting server time:
POST https://api.steampowered.com/ITwoFactorService/QueryTime/v0001
Response: {"response": {"server_time": "1719600000"}}
SteamID64 Retrieval
After login, SteamID64 appears in the URL: https://steamcommunity.com/profiles/7656119XXXXXXXXXX/
Or resolve custom URL:
GET https://api.steampowered.com/ISteamUser/ResolveVanityURL/v0001/?key=&vanityurl=<custom>
Response: {"response": {"steamid": "7656119XXXXXXXXXX", "success": 1}}
Pitfalls
- Don't use Turnstile method for Steam — it's reCAPTCHA v2. Wrong method = wasted 2captcha balance.
- Steam Guard code is 5 chars, NOT 6 — unlike most OTP systems. Regex
\d{6}will miss it. - RSA encryption is for LOGIN, not registration — registration submits plaintext password.
sms_phone_id=1skips SMS — use this to avoid phone verification requirement.- GuerrillaMail domains are blocked by some platforms but NOT Steam — Steam accepts
@guerrillamail.comaddresses. - Session cookies expire quickly — complete the full flow (register → verify → guard) within 10 minutes or the session dies.
- Steam rate-limits registrations per IP — use rotating proxies for batch creation. Minimum 30-90s between accounts from same IP.
- Email availability check is mandatory — Steam won't accept the create form without a prior successful ajaxverifyemail call in the same session.
See Also
bulk-account-registration— general IMAP, proxy rotation, browser automation patternsreferences/multi-provider-imap-servers.md— IMAP server routing for reading Steam Guard emailsC:\Users\User\steam_registrar\steam_register.py— complete working implementation