curl --request POST \
--url https://test-api.shodai.network/v0/siwe/verify \
--header 'Content-Type: application/json' \
--header 'X-API-Key: <api-key>' \
--data '
{
"address": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266",
"message": "api.example.com wants you to sign in with your Ethereum account:\n0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266\n\nProve control of this wallet to Shodai.\n\nURI: https://api.example.com\nVersion: 1\nChain ID: 59141\nNonce: 0123456789abcdef0123456789abcdef\nIssued At: 2026-08-07T20:00:00.000Z",
"signature": "0x1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111"
}
'const options = {
method: 'POST',
headers: {'X-API-Key': '<api-key>', 'Content-Type': 'application/json'},
body: JSON.stringify({
address: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266',
message: 'api.example.com wants you to sign in with your Ethereum account:\n0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266\n\nProve control of this wallet to Shodai.\n\nURI: https://api.example.com\nVersion: 1\nChain ID: 59141\nNonce: 0123456789abcdef0123456789abcdef\nIssued At: 2026-08-07T20:00:00.000Z',
signature: '0x1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111'
})
};
fetch('https://test-api.shodai.network/v0/siwe/verify', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));{
"data": {
"wallet": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266",
"walletBinding": "verified_via_siwe",
"verifiedAt": "2026-08-07T20:01:00.000Z"
},
"meta": {
"apiVersion": "v0",
"requestId": "req_123"
}
}{
"error": {
"code": "unauthorized",
"message": "Missing API credential",
"requestId": "<string>",
"details": "<unknown>"
}
}{
"error": {
"code": "unauthorized",
"message": "Missing API credential",
"requestId": "<string>",
"details": "<unknown>"
}
}{
"error": {
"code": "unauthorized",
"message": "Missing API credential",
"requestId": "<string>",
"details": "<unknown>"
}
}{
"error": {
"code": "unauthorized",
"message": "Missing API credential",
"requestId": "<string>",
"details": "<unknown>"
}
}{
"error": {
"code": "unauthorized",
"message": "Missing API credential",
"requestId": "<string>",
"details": "<unknown>"
}
}Verify and link wallet
Verifies a signed EIP-4361 message and links the wallet to the authenticated Shodai account. OAuth bearer tokens require agreements.write; API keys require the equivalent account entitlement. Verification proves control only: it does not transfer custody or authorize Shodai to sign agreement inputs.
curl --request POST \
--url https://test-api.shodai.network/v0/siwe/verify \
--header 'Content-Type: application/json' \
--header 'X-API-Key: <api-key>' \
--data '
{
"address": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266",
"message": "api.example.com wants you to sign in with your Ethereum account:\n0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266\n\nProve control of this wallet to Shodai.\n\nURI: https://api.example.com\nVersion: 1\nChain ID: 59141\nNonce: 0123456789abcdef0123456789abcdef\nIssued At: 2026-08-07T20:00:00.000Z",
"signature": "0x1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111"
}
'const options = {
method: 'POST',
headers: {'X-API-Key': '<api-key>', 'Content-Type': 'application/json'},
body: JSON.stringify({
address: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266',
message: 'api.example.com wants you to sign in with your Ethereum account:\n0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266\n\nProve control of this wallet to Shodai.\n\nURI: https://api.example.com\nVersion: 1\nChain ID: 59141\nNonce: 0123456789abcdef0123456789abcdef\nIssued At: 2026-08-07T20:00:00.000Z',
signature: '0x1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111'
})
};
fetch('https://test-api.shodai.network/v0/siwe/verify', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));{
"data": {
"wallet": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266",
"walletBinding": "verified_via_siwe",
"verifiedAt": "2026-08-07T20:01:00.000Z"
},
"meta": {
"apiVersion": "v0",
"requestId": "req_123"
}
}{
"error": {
"code": "unauthorized",
"message": "Missing API credential",
"requestId": "<string>",
"details": "<unknown>"
}
}{
"error": {
"code": "unauthorized",
"message": "Missing API credential",
"requestId": "<string>",
"details": "<unknown>"
}
}{
"error": {
"code": "unauthorized",
"message": "Missing API credential",
"requestId": "<string>",
"details": "<unknown>"
}
}{
"error": {
"code": "unauthorized",
"message": "Missing API credential",
"requestId": "<string>",
"details": "<unknown>"
}
}{
"error": {
"code": "unauthorized",
"message": "Missing API credential",
"requestId": "<string>",
"details": "<unknown>"
}
}agreements.write scope, or provide X-API-Key as the alternative credential. Verification proves control and links the wallet to the authenticated Shodai account; it does not transfer custody or authorize Shodai to sign agreement inputs. See Authentication for delegated OAuth access and Link a wallet and access agreements for the creator and participant access model.
TypeScript with viem
import { createSiweMessage } from 'viem/siwe';
const baseUrl = process.env.SHODAI_API_BASE_URL!.replace(/\/+$/, '');
const apiUrl = new URL(baseUrl);
const accessToken = process.env.SHODAI_ACCESS_TOKEN!;
const headers = {
Authorization: `Bearer ${accessToken}`,
'Content-Type': 'application/json',
};
const address = walletClient.account.address;
const nonceResponse = await fetch(`${baseUrl}/v0/siwe/nonce`, {
method: 'POST',
headers,
body: JSON.stringify({ address }),
});
const { data: challenge } = await nonceResponse.json();
const message = createSiweMessage({
domain: apiUrl.host,
address,
statement: 'Prove control of this wallet to Shodai.',
uri: apiUrl.origin,
version: '1',
chainId: 59141,
nonce: challenge.nonce,
issuedAt: new Date(challenge.issuedAt),
});
const signature = await walletClient.signMessage({
account: walletClient.account,
message,
});
const verification = await fetch(`${baseUrl}/v0/siwe/verify`, {
method: 'POST',
headers,
body: JSON.stringify({
address,
message,
signature,
domain: apiUrl.host,
chainId: 59141,
}),
});
console.log(await verification.json());
// X-API-Key: YOUR_API_KEY is the alternative to Authorization.
Authorizations
Canonical API-key credential. Send X-API-Key: cns_pk_..., or Authorization: Bearer cns_pk_... only as an API-key compatibility alias.
Body
Wallet address claimed by the EIP-4361 message.
^0x[0-9a-fA-F]{40}$Complete EIP-4361 message containing the issued nonce, wallet, domain, URI, chain ID, and issued-at timestamp.
Hexadecimal signature over the complete EIP-4361 message.
^0x[0-9a-fA-F]+$Optional assertion that must exactly match the domain in the signed message.
Optional assertion that must match the chain ID in the signed message.
Was this page helpful?