curl --request POST \
--url https://api.vane.xyz/api/auth/siwx/verify \
--header 'Content-Type: application/json' \
--header 'x-tenant-api-key: <api-key>' \
--data '
{
"data": {
"chainId": "1",
"accountAddress": "0xa1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0"
},
"message": "Nonce: b3c1a9e2-4f6d-4a8b-9c2e-1d5f7a3b8c4d",
"signature": "0x4f3c2b1a9e8d7c6b5a4f3e2d1c0b9a8f7e6d5c4b3a2f1e0d9c8b7a6f5e4d3c2b1a0f9e8d7c6b5a4f3e2d1c0b9a8f7e6d5c4b3a2f1e0d9c8b7a6f5e4d3c2b1b"
}
'const options = {
method: 'POST',
headers: {'x-tenant-api-key': '<api-key>', 'Content-Type': 'application/json'},
body: JSON.stringify({
data: {chainId: '1', accountAddress: '0xa1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0'},
message: 'Nonce: b3c1a9e2-4f6d-4a8b-9c2e-1d5f7a3b8c4d',
signature: '0x4f3c2b1a9e8d7c6b5a4f3e2d1c0b9a8f7e6d5c4b3a2f1e0d9c8b7a6f5e4d3c2b1a0f9e8d7c6b5a4f3e2d1c0b9a8f7e6d5c4b3a2f1e0d9c8b7a6f5e4d3c2b1b'
})
};
fetch('https://api.vane.xyz/api/auth/siwx/verify', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));import requests
url = "https://api.vane.xyz/api/auth/siwx/verify"
payload = {
"data": {
"chainId": "1",
"accountAddress": "0xa1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0"
},
"message": "Nonce: b3c1a9e2-4f6d-4a8b-9c2e-1d5f7a3b8c4d",
"signature": "0x4f3c2b1a9e8d7c6b5a4f3e2d1c0b9a8f7e6d5c4b3a2f1e0d9c8b7a6f5e4d3c2b1a0f9e8d7c6b5a4f3e2d1c0b9a8f7e6d5c4b3a2f1e0d9c8b7a6f5e4d3c2b1b"
}
headers = {
"x-tenant-api-key": "<api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text){
"accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI3YzllNjY3OSJ9.example-access-token",
"refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI3YzllNjY3OSJ9.example-refresh-token"
}Verify wallet signature
Verifies a wallet signature over the challenge nonce and returns a JWT token pair with HTTP 201. The signed message must contain the line Nonce: <nonce> using the nonce from GET /auth/siwx/challenge; both the minimal single-line format and a fuller EIP-4361 style sign-in message are accepted. A 201 consumes the nonce; a failed verify does not, so a retry may reuse it.
The tenant API key is required here too. A correctly signed request without the x-tenant-api-key header fails with a generic 500. A request whose key value is wrong fails with 401 “x-tenant-api-key not present”.
Tokens are also delivered as HttpOnly, Secure, SameSite=None cookies: access_token with Max-Age 900 (15 minutes, Path=/) and refresh_token with Max-Age 2592000 (30 days, Path=/api/auth). The cookie values are server-signed strings, not the raw JWTs.
Every verification failure the API can name returns 401 with a message that says why: “EVM signature verification failed: Signature does not match the expected address.” when the signature recovers to a different wallet than data.accountAddress, “EVM signature verification failed: invalid raw signature length (…)” when the signature is not a valid encoding, “Nonce not found in the message.” when message has no Nonce: line, and “Invalid or expired nonce.” when the nonce is unknown or already used. Only two inputs return the generic 500 {"statusCode": 500, "message": "Internal server error"}: a missing x-tenant-api-key header and a body missing required fields.
curl --request POST \
--url https://api.vane.xyz/api/auth/siwx/verify \
--header 'Content-Type: application/json' \
--header 'x-tenant-api-key: <api-key>' \
--data '
{
"data": {
"chainId": "1",
"accountAddress": "0xa1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0"
},
"message": "Nonce: b3c1a9e2-4f6d-4a8b-9c2e-1d5f7a3b8c4d",
"signature": "0x4f3c2b1a9e8d7c6b5a4f3e2d1c0b9a8f7e6d5c4b3a2f1e0d9c8b7a6f5e4d3c2b1a0f9e8d7c6b5a4f3e2d1c0b9a8f7e6d5c4b3a2f1e0d9c8b7a6f5e4d3c2b1b"
}
'const options = {
method: 'POST',
headers: {'x-tenant-api-key': '<api-key>', 'Content-Type': 'application/json'},
body: JSON.stringify({
data: {chainId: '1', accountAddress: '0xa1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0'},
message: 'Nonce: b3c1a9e2-4f6d-4a8b-9c2e-1d5f7a3b8c4d',
signature: '0x4f3c2b1a9e8d7c6b5a4f3e2d1c0b9a8f7e6d5c4b3a2f1e0d9c8b7a6f5e4d3c2b1a0f9e8d7c6b5a4f3e2d1c0b9a8f7e6d5c4b3a2f1e0d9c8b7a6f5e4d3c2b1b'
})
};
fetch('https://api.vane.xyz/api/auth/siwx/verify', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));import requests
url = "https://api.vane.xyz/api/auth/siwx/verify"
payload = {
"data": {
"chainId": "1",
"accountAddress": "0xa1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0"
},
"message": "Nonce: b3c1a9e2-4f6d-4a8b-9c2e-1d5f7a3b8c4d",
"signature": "0x4f3c2b1a9e8d7c6b5a4f3e2d1c0b9a8f7e6d5c4b3a2f1e0d9c8b7a6f5e4d3c2b1a0f9e8d7c6b5a4f3e2d1c0b9a8f7e6d5c4b3a2f1e0d9c8b7a6f5e4d3c2b1b"
}
headers = {
"x-tenant-api-key": "<api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text){
"accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI3YzllNjY3OSJ9.example-access-token",
"refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI3YzllNjY3OSJ9.example-refresh-token"
}Authorizations
Vane's shared tenant key. Contact support for a dedicated key if you need custom parameters or separate order tracking.
Body
SIWX verification payload.
The signing account.
Show child attributes
Show child attributes
The exact message that was signed, containing the line Nonce: <nonce> with the nonce from GET /auth/siwx/challenge. Both the minimal single-line Nonce: <nonce> format and a fuller EIP-4361 style sign-in message are accepted.
Wallet signature over message (EVM: personal_sign / EIP-191). Must recover to data.accountAddress.
Optional referral code to credit the referrer; see Points and referrals.
Response
Signature accepted. The status is 201, and the JWT token pair is also set as HttpOnly cookies.
JWT token pair. Also delivered as HttpOnly, Secure, SameSite=None cookies: access_token (15 min, Path=/) and refresh_token (30 days, Path=/api/auth). The cookie values are server-signed strings, not the raw JWTs, so non-browser clients should use the JSON accessToken as a Bearer header.

