The official Python SDK for Castle. Castle analyzes user behavior in web and mobile apps to stop fraud before it happens.
This package is a thin wrapper around the Castle HTTP API. It exposes risk assessment, event logging, Lists, Privacy (GDPR), Events (enterprise), and webhook verification. See the API reference for supported events and payload shapes.
- Python 3.9 or newer
- A Castle API secret
pip install castleimportosfromcastle.configurationimportconfigurationfromcastle.clientimportClientconfiguration.api_secret=os.environ['CASTLE_API_SECRET']
client=Client.from_request(request)
verdict=client.risk({
'event': '$login',
'status': '$succeeded',
'request_token': request.POST.get('castle_request_token'),
'user': {'id': '12345', 'email': 'user@example.com'},
})
action=verdict.get('policy', {}).get('action') orverdict.get('action')
ifaction=='deny':
# block the userpasselifaction=='challenge':
# send 2FA / additional verificationpasselse:
# allowpassClient.from_request builds request context (IP, headers, client id) from a framework request object. See Advanced configuration for header allow/deny lists and proxy chains.
The minimal, recommended setup:
importosfromcastle.configurationimportconfigurationconfiguration.api_secret=os.environ['CASTLE_API_SECRET']
# Behavior when Castle's API is unreachable or returns a 5xx.# One of: allow (default), deny, challenge, throwconfiguration.failover_strategy='allow'# Request timeout in milliseconds (default: 1000).# RequestError is raised on timeout.configuration.request_timeout=1000importloggingfromcastle.configurationimportconfigurationconfiguration.logger=logging.getLogger('castle')The logger only needs to respond to info. Each request and response is logged with sensitive values stripped.
Most apps only need the global configuration singleton, but you can also create standalone Configuration instances and pass them per call via APIRequest:
fromcastle.configurationimportConfigurationfromcastle.api_requestimportAPIRequestfromcastle.commands.riskimportCommandsRiskconfig=Configuration()
config.api_secret=os.environ['CASTLE_API_SECRET_TENANT_A']
APIRequest(config).call(CommandsRisk(context).call({
'event': '$login',
'status': '$succeeded',
'request_token': '<token>',
'user': {'id': '1234'},
}))See Castle documentation and the API reference for endpoint details, event types, and integration guides.
The defaults work for most deployments. The options below only matter if you have a non-trivial proxy chain or strict header policies.
By default the SDK sends every HTTP header except Cookie and Authorization. Castle uses these headers to fingerprint the request.
fromcastle.configurationimportconfiguration, DEFAULT_ALLOWLIST# Always-blocked headers (in addition to Cookie/Authorization).configuration.denylisted= ['HTTP-X-Internal-Header']
# Strict allow-list mode. Headers outside the list are scrubbed,# except User-Agent which is always preserved.configuration.allowlisted=DEFAULT_ALLOWLISTHeader names are case-insensitive and accept both _ and - as separators. A leading HTTP_ prefix is stripped automatically.
Castle needs the original client IP, not the IP of your proxy or load balancer. The SDK reads X-Forwarded-For and Remote-Addr by default; pick one of the strategies below:
fromcastle.configurationimportconfiguration, TRUSTED_PROXIES# 1. Custom header (e.g. Cloudflare's Cf-Connecting-Ip).configuration.ip_headers= ['Cf-Connecting-Ip']
# 2. Static, known proxy IPs (strings or regexes).configuration.trusted_proxies= ['10.0.0.1']
# 3. Ephemeral proxies but known chain depth.configuration.trusted_proxy_depth=2# 4. Last resort: trust the entire X-Forwarded-For chain.# Warning: vulnerable to header spoofing if a malicious proxy is in path.configuration.trust_proxy_chain=FalseUse eithertrusted_proxiesortrusted_proxy_depth, not both. Private/loopback ranges in TRUSTED_PROXIES are always considered trusted.
fromcastle.configurationimportconfiguration# Override the API base URL (default: https://api.castle.io/v1)# configuration.base_url = 'https://api.castle.io/v1'Secure mode signs user identifiers on the server:
fromcastle.secure_modeimportsignaturesignature(user_id)All exceptions inherit from CastleError. The most useful ones:
ConfigurationError— the SDK is misconfigured (missing API secret, invalid URL, etc.)RequestError— network failure or timeout reaching CastleInvalidRequestTokenError— the request token is missing or invalidInvalidParametersError— 422 response with validation detailsRateLimitError— 429 response; back off and retryUnauthorizedError— 401; bad API secretInternalServerError— 5xx response from CastleWebhookVerificationError— webhook signature did not match
The full list is in castle/errors.py.