Overview
SIWX solves two key problems in x402:- Repeat access to purchased content: Without SIWX, clients must pay every time they request a resource. With SIWX, sign in with your wallet to access content you’ve already paid for.
- Auth-only routes: Protect resources with wallet authentication alone, without requiring payment.
- For Buyers: Sign in with your wallet to access content you’ve already paid for, or authenticate to access wallet-gated resources
- For Sellers: Grant access to returning customers without requiring repayment, or create auth-only routes that require wallet signatures but no payment
- Chain-Agnostic: Works with EVM (Ethereum, Base, etc.) and Solana wallets
- Standards-Based: Built on CAIP-122, EIP-4361 (SIWE), and Sign-In-With-Solana
How It Works
- Server returns 402 with
sign-in-with-xextension containing challenge parameters - Client signs the CAIP-122 message with their wallet
- Client sends signed proof in
SIGN-IN-WITH-XHTTP header - Server verifies signature and grants access either because:
- The route is auth-only (requires signature but no payment), or
- The wallet has previously paid for the resource
Server Usage
Recommended: Extension Factories
The easiest way to implement SIWX is registering the provided extension factories:- TypeScript
- Go
- Python
network from accepts, derives domain/uri from the configured origin and request path, refreshes nonce/timestamps per request, records successful payments, and validates SIWX proofs for declared HTTP routes.
Auth-only routes (declared with accepts: []) grant access based on a valid SIWX signature alone, without requiring payment. This is useful for wallet-gated content that doesn’t need micropayments.
Configured origin (required)
SIWX proofs bind to the public origin configured when creating the server extension or request hook. Challenge issuance and validation both use this value — never request headers such asHost, X-Forwarded-Host, or X-Forwarded-Proto.
- TypeScript
- Go
- Python
origin to the browser-visible URL (for example https://api.example.com), not the upstream listener address (for example http://127.0.0.1:4021). The request path is rebased onto the configured origin for each challenge.
Smart Wallet Support (EIP-1271 / EIP-6492)
By default, only EOA (Externally Owned Account) signatures are verified. To support smart contract wallets (like Coinbase Smart Wallet, Safe, etc.), pass a verifier viaverifyOptions:
- TypeScript
- Go
- Python
- EIP-1271: Verification of deployed smart contract wallets
- EIP-6492: Verification of counterfactual (not-yet-deployed) wallets
Using Hook Adapters Directly
If you need more control over the integration, you can use the individual hook adapter functions instead ofcreateSIWxResourceServerExtension. This is useful when you want to attach SIWX behavior to an existing server setup:
- TypeScript
createSIWxClientHook for a single signer, or attach it directly to an HTTP client:
- TypeScript
Manual Usage (Advanced)
For custom implementations, you can use the low-level functions directly:- TypeScript
Client Usage
Recommended: Client Extension
The easiest way to use SIWX as a client is with the provided client extension:- TypeScript
- Go
- Python
- Detects SIWX support in 402 responses
- Matches your wallet’s chain with server’s
supportedChains - Signs and sends the authentication proof
- Falls back to payment if SIWX auth fails
Manual Usage (Advanced)
For custom implementations:- TypeScript
Multi-Chain Support
Servers can support multiple chains (e.g., both EVM and Solana) by including multiple entries insupportedChains:
- TypeScript
chainId against supportedChains and use the first matching entry. The same nonce is shared across all chains, preventing replay attacks when authenticating with different wallets.
Supported Chains
EVM (Ethereum, Base, Polygon, etc.)
- Chain ID Format:
eip155:*(e.g.,eip155:8453for Base) - Signature Type:
eip191 - Signature Schemes:
eip191(EOA - default)eip1271(smart contract wallet)eip6492(counterfactual wallet)
- Message Format: EIP-4361 (SIWE)
Solana
- Chain ID Format:
solana:*(e.g.,solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdpfor mainnet) - Signature Type:
ed25519 - Signature Scheme:
siws - Message Format: Sign-In With Solana
API Reference
declareSIWxExtension(options?)
Creates the extension declaration for servers to include in PaymentRequired. Domain and URI are derived from the configured origin passed to the server extension factory (createSIWxResourceServerExtension in TypeScript/Python, CreateResourceServerExtension in Go). Do not set domain or resource_uri/ResourceURI in route declarations.
DeclareExtension(DeclareOptions{Statement, Version, Networks, ExpirationSeconds})
Python: declare_siwx_extension(DeclareSIWxOptions(...))
Note for auth-only routes: When using accepts: [], the network parameter cannot be inferred from payment requirements and must be provided explicitly.
createSIWxResourceServerExtension(options) / CreateResourceServerExtension / create_siwx_resource_server_extension
Creates the server extension that enriches SIWX challenges, records successful payments, and verifies HTTP SIWX proofs for declared routes. Requires origin / Origin. Internally uses settle and request hooks.
Go: CreateResourceServerExtension(ServerOptions{Storage, Origin, VerifyOptions, OnEvent})
Python: create_siwx_resource_server_extension(CreateSIWxHookOptions(storage=..., origin=...))
createSIWxSettleHook(options)
Creates an onAfterSettle hook that records payments for SIWX. Use this when you want to attach SIWX payment recording to an existing x402ResourceServer without registering the full extension.
createSIWxRequestHook(options)
Creates an onProtectedRequest hook that validates SIWX proofs on incoming HTTP requests. For paid routes, grants access when the signature is valid and the address has paid. For auth-only routes (accepts: []), grants access on a valid signature alone.
createSIWxClientHook(signer)
Creates an onPaymentRequired hook for a single wallet signer. Matches the signer type (EVM or Solana) to a compatible chain in the server’s supportedChains and signs the SIWX challenge.
createSIWxClientExtension({ signers })
Creates the client extension that signs compatible SIWX challenges before falling back to payment. Accepts multiple signers (tried in order until one succeeds).
parseSIWxHeader(header)
Parses a base64-encoded SIGN-IN-WITH-X header into a payload object.
validateSIWxMessage(payload, expectedOrigin, options?) / ValidateMessage / validate_siwx_message
Validates message fields (expiry, domain binding, nonce, etc.).
ValidateMessage(payload, *url.URL, ValidationOptions)
Python: validate_siwx_message(message, expected_origin: str, options=None)
verifySIWxSignature(payload, options?)
Verifies the cryptographic signature and recovers the signer address.
createSIWxPayload(serverInfo, signer, requestUrl)
Client helper that creates and signs a complete payload. requestUrl is the final URL of the 402 response (after redirects) and is required — signing is refused when the challenge domain or uri origin does not match that URL’s origin.
encodeSIWxHeader(payload)
Encodes a payload as base64 for the SIGN-IN-WITH-X header.
SIWxHookEvent
Event type emitted by SIWX hooks when onEvent is configured. Useful for logging and debugging:
Storage Interface
ImplementSIWxStorage to track which wallets have paid:
InMemorySIWxStorage for development. For production, implement persistent storage (database, Redis, etc.).
Optionally implement hasUsedNonce and recordNonce to prevent replay attacks where an intercepted SIWX header could be reused. Both methods must be implemented together — implementing only one will throw an error at startup.
Security Considerations
- Origin Binding: The configured
originprevents signature reuse across different services. Validation comparesmessage.domainand the origin ofmessage.uriagainst this operator-defined value, not request headers. - Nonce Uniqueness: Each challenge MUST have a unique nonce to prevent replay attacks
- Temporal Bounds: The
issuedAt,expirationTime, andnotBeforefields constrain signature validity windows - Chain-Specific Verification: Signatures are verified using chain-appropriate algorithms, preventing cross-chain signature reuse
- Smart Wallet Support: EIP-1271 and EIP-6492 verification requires an RPC call to the wallet contract
Troubleshooting
Signature Verification Fails
Problem:verifySIWxSignature returns { isValid: false }.
Check invalidReason for the specific failure code (e.g., invalid_siwx_signature, invalid_siwx_unsupported_chain).
Solutions:
- Ensure the message was signed with the correct wallet
- Check that the signature scheme matches the wallet type
- For smart wallets, enable
evmVerifieroption with a viem public client - Verify the chain ID matches between client and server
Message Validation Fails
Problem:validateSIWxMessage returns { isValid: false }.
Check invalidReason for the specific failure code (e.g., invalid_siwx_expired, invalid_siwx_domain_mismatch).
Solutions:
- Check that
issuedAtis recent (withinmaxAge, default 5 minutes) - Verify
expirationTimehasn’t passed - Ensure
domainmatches the configured origin host - Confirm
message.uriorigin exactly matches the configured origin
Client Hook Not Working
Problem: SIWX authentication not being attempted. Solutions:- Verify server is declaring SIWX extension in 402 response
- Check that client’s wallet chain matches one of the
supportedChains - Ensure signer is properly configured for the wallet type
Related Resources
- CAIP-122 Specification - Sign-In-With-X standard
- EIP-4361 (SIWE) - Sign-In With Ethereum
- Sign-In With Solana
- x402 Core Package
- Lifecycle Hooks - Custom payment flow logic