Castle Token Solving
Bypass Castle fraud and bot protection and generate valid request tokens for protected web applications. Our solver handles the Castle client fingerprinting pipeline and returns tokens that satisfy Castle server-side validation requirements.
Castle is a fraud protection and device intelligence platform used by fintech, e-commerce, and authentication-sensitive applications. It works by loading a JavaScript SDK on the client that collects device fingerprinting data, behavioral signals, and network characteristics. This telemetry is submitted to Castle's API to generate a request token. This token is then included in subsequent API requests, where Castle's server-side library validates it to determine whether the request originates from a legitimate browser session. Our solver replicates the full Castle SDK execution flow and returns a valid request token that passes server-side validation.
Captcha Type
Use the following captcha type identifier in your API requests:
"type": "CASTLE_TOKEN"
Request Format
Request Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
auth.token |
string | required | Your API key |
captcha.type |
string | required | Must be "CASTLE_TOKEN" |
captcha.metadata.siteUrl |
string | required | The full URL of the page where the Castle SDK is loaded |
captcha.metadata.siteKey |
string | optional | The Castle publishable key (found in the site's JavaScript configuration where Castle is initialized, e.g., Castle.configure({ pk: '...' })). |
captcha.payload.userAgent |
string | optional | Your browser user agent string. Castle embeds this in its telemetry payload. |
captcha.payload.proxy |
object | recommended | Proxy configuration object. Recommended for consistency — the Castle token carries IP reputation signals. |
captcha.payload.proxy.protocol |
string | required* | Proxy protocol: "http", "https", "socks4", or "socks5" |
captcha.payload.proxy.host |
string | required* | Proxy IP address or hostname |
captcha.payload.proxy.port |
number | required* | Proxy port number |
captcha.payload.proxy.username |
string | optional | Proxy authentication username |
captcha.payload.proxy.password |
string | optional | Proxy authentication password |
* Required only if the parent object (proxy) is provided
The Castle publishable key is typically set in the site's JavaScript as part of the Castle SDK
initialization call. Search the page source for Castle.configure or
_castle("setAppId" and the key will be the string value passed there.
It typically starts with a prefix like pk_ or is a UUID-style identifier.
Providing this value as siteKey improves solving accuracy.
Example Request
{
"auth": {
"token": "YOUR_API_KEY"
},
"context": {
"source": "api",
"version": "1.0.0"
},
"captcha": {
"type": "CASTLE_TOKEN",
"metadata": {
"siteUrl": "https://example.com/login",
"siteKey": "pk_AbCdEfGhIjKlMnOpQrStUvWxYz123456"
},
"payload": {
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
"proxy": {
"protocol": "http",
"host": "1.2.3.4",
"port": 8080,
"username": "proxyuser",
"password": "proxypass"
}
}
}
}
const response = await fetch('https://solver-api.mydisct.com/createTask', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'apikey': 'YOUR_API_KEY'
},
body: JSON.stringify({
auth: {
token: 'YOUR_API_KEY'
},
context: {
source: 'api',
version: '1.0.0'
},
captcha: {
type: 'CASTLE_TOKEN',
metadata: {
siteUrl: 'https://example.com/login',
siteKey: 'pk_AbCdEfGhIjKlMnOpQrStUvWxYz123456'
},
payload: {
userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
proxy: {
protocol: 'http',
host: '1.2.3.4',
port: 8080,
username: 'proxyuser',
password: 'proxypass'
}
}
}
})
});
const data = await response.json();
console.log('Task ID:', data.task.id);
import requests
response = requests.post(
'https://solver-api.mydisct.com/createTask',
headers={
'Content-Type': 'application/json',
'apikey': 'YOUR_API_KEY'
},
json={
'auth': {'token': 'YOUR_API_KEY'},
'context': {'source': 'api', 'version': '1.0.0'},
'captcha': {
'type': 'CASTLE_TOKEN',
'metadata': {
'siteUrl': 'https://example.com/login',
'siteKey': 'pk_AbCdEfGhIjKlMnOpQrStUvWxYz123456'
},
'payload': {
'userAgent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
'proxy': {
'protocol': 'http',
'host': '1.2.3.4',
'port': 8080,
'username': 'proxyuser',
'password': 'proxypass'
}
}
}
}
)
data = response.json()
print('Task ID:', data['task']['id'])
curl -X POST https://solver-api.mydisct.com/createTask \
-H "Content-Type: application/json" \
-H "apikey: YOUR_API_KEY" \
-d '{
"auth": {"token": "YOUR_API_KEY"},
"context": {"source": "api", "version": "1.0.0"},
"captcha": {
"type": "CASTLE_TOKEN",
"metadata": {
"siteUrl": "https://example.com/login",
"siteKey": "pk_AbCdEfGhIjKlMnOpQrStUvWxYz123456"
},
"payload": {
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
"proxy": {
"protocol": "http",
"host": "1.2.3.4",
"port": 8080,
"username": "proxyuser",
"password": "proxypass"
}
}
}
}'
Response Format
Create Task Response (Processing)
{
"success": true,
"service": "MyDisct Solver",
"message": "Captcha task created successfully",
"task": {
"id": "MyDisctSolver_abc123",
"status": "processing"
}
}
Fetch Result Response (Completed)
{
"success": true,
"service": "MyDisct Solver",
"message": "Castle challenge solved successfully",
"task": {
"id": "MyDisctSolver_abc123",
"status": "completed",
"result": {
"token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJjdHgiOiJ...",
"user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
"timestamp": "2025-10-10T12:00:00.000Z"
}
}
}
Response Fields
| Field | Type | Description |
|---|---|---|
result.token |
string | The Castle request token. This is the value returned by Castle.createRequestToken() in a legitimate browser session. Pass it as the X-Castle-Request-Token header. |
result.user_agent |
string | The exact user agent used during solving. Forward this in your requests to maintain consistency with the token payload. |
Castle tokens are submitted as the value of the X-Castle-Request-Token HTTP header
in API requests. The Castle server-side SDK validates this header by calling
castle.risk() or castle.filter() with the token value. If the token
is valid and the associated risk score is acceptable, the request proceeds normally. Always
send the token alongside the matching user agent.
Implementation Guide
Complete JavaScript Implementation
async function solveCastleToken(siteUrl, siteKey, options = {}) {
const createResponse = await fetch('https://solver-api.mydisct.com/createTask', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'apikey': 'YOUR_API_KEY'
},
body: JSON.stringify({
auth: { token: 'YOUR_API_KEY' },
context: { source: 'api', version: '1.0.0' },
captcha: {
type: 'CASTLE_TOKEN',
metadata: { siteUrl, siteKey },
payload: {
userAgent: options.userAgent || 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
proxy: options.proxy
}
}
})
});
const createData = await createResponse.json();
if (!createData.success) throw new Error(createData.error.message);
const taskId = createData.task.id;
while (true) {
await new Promise(resolve => setTimeout(resolve, 5000));
const resultResponse = await fetch('https://solver-api.mydisct.com/fetchResult', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'apikey': 'YOUR_API_KEY'
},
body: JSON.stringify({ taskId })
});
const resultData = await resultResponse.json();
if (resultData.task.status === 'completed') {
return resultData.task.result;
} else if (resultData.task.status === 'failed') {
throw new Error('Castle token solving failed');
}
}
}
async function loginWithCastleToken(credentials, castleResult) {
const response = await fetch('https://example.com/api/auth/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'User-Agent': castleResult.user_agent,
'X-Castle-Request-Token': castleResult.token,
'Accept': 'application/json'
},
body: JSON.stringify(credentials)
});
return await response.json();
}
const castleResult = await solveCastleToken(
'https://example.com/login',
'pk_AbCdEfGhIjKlMnOpQrStUvWxYz123456',
{
proxy: {
protocol: 'http',
host: '1.2.3.4',
port: 8080,
username: 'proxyuser',
password: 'proxypass'
}
}
);
console.log('Castle token:', castleResult.token.substring(0, 50) + '...');
const loginResult = await loginWithCastleToken(
{ email: '[email protected]', password: 'secretpassword' },
castleResult
);
console.log('Login result:', loginResult);
Python Implementation
import requests
import time
def solve_castle_token(site_url, site_key, api_key, user_agent=None, proxy_config=None):
create_response = requests.post(
'https://solver-api.mydisct.com/createTask',
headers={
'Content-Type': 'application/json',
'apikey': api_key
},
json={
'auth': {'token': api_key},
'context': {'source': 'api', 'version': '1.0.0'},
'captcha': {
'type': 'CASTLE_TOKEN',
'metadata': {
'siteUrl': site_url,
'siteKey': site_key
},
'payload': {
'userAgent': user_agent or 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
'proxy': proxy_config
}
}
}
)
create_data = create_response.json()
if not create_data['success']:
raise Exception(create_data['error']['message'])
task_id = create_data['task']['id']
print(f'Task created: {task_id}')
while True:
time.sleep(5)
result_response = requests.post(
'https://solver-api.mydisct.com/fetchResult',
headers={
'Content-Type': 'application/json',
'apikey': api_key
},
json={'taskId': task_id}
)
result_data = result_response.json()
if result_data['task']['status'] == 'completed':
return result_data['task']['result']
elif result_data['task']['status'] == 'failed':
raise Exception('Castle token solving failed')
print('Waiting for solution...')
castle_result = solve_castle_token(
site_url='https://example.com/login',
site_key='pk_AbCdEfGhIjKlMnOpQrStUvWxYz123456',
api_key='YOUR_API_KEY',
proxy_config={
'protocol': 'http',
'host': '1.2.3.4',
'port': 8080,
'username': 'proxyuser',
'password': 'proxypass'
}
)
print(f'Castle token: {castle_result["token"][:50]}...')
login_response = requests.post(
'https://example.com/api/auth/login',
headers={
'Content-Type': 'application/json',
'User-Agent': castle_result['user_agent'],
'X-Castle-Request-Token': castle_result['token'],
'Accept': 'application/json'
},
json={
'email': '[email protected]',
'password': 'secretpassword'
}
)
print(f'Login status: {login_response.status_code}')
print(f'Login result: {login_response.json()}')
Best Practices
- Provide the Publishable Key: Extract the Castle
siteKeyfrom the page source and provide it — this ensures the solver targets the correct Castle configuration for the site - Use the Correct Header: Pass the token as the
X-Castle-Request-Tokenheader — this is the standard Castle integration pattern - IP and User Agent Consistency: Castle embeds both IP and user agent signals in its tokens — use the same proxy and forward the exact
user_agentfrom the result - Residential Proxies: Castle scores IP reputation as part of its risk assessment — residential proxies produce better risk scores than datacenter IPs
- Token Freshness: Castle tokens are single-use in many implementations. Generate a new token for each protected request rather than reusing one
- Polling Interval: Use 5-second polling intervals — Castle token generation typically completes in 5 to 15 seconds
- Combine with Session Cookies: Some Castle-protected sites also require valid session cookies alongside the token — ensure your session is properly initialized before submitting
Common Issues
Solution: Ensure the token is being sent as the X-Castle-Request-Token
header. Also confirm that the same proxy IP and user agent used during solving are being forwarded
in the request.
Solution: Switch to a residential proxy. Castle's risk model penalizes datacenter and VPN IP ranges. Residential proxies provide the IP reputation signals that Castle uses to assign lower risk scores.
Solution: Castle tokens have a short validity window. Use the token within a few seconds of receiving it. Do not store tokens for later use — generate a fresh one immediately before each protected request.