POST /api/embed/siws/nonce
Starts a wallet submission. The server mints a short-lived nonce, binds it to {trackingPageId, walletAddress, API key}, and returns the exact message the visitor's wallet must sign.
Used together with POST /submissions. Full end-to-end explanation in SIWS signing.
Request
POST /api/embed/siws/nonce HTTP/1.1
Host: walletsleuth.app
Authorization: Bearer ws_pub_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Origin: https://example.com
Content-Type: application/json
{
"trackingPageId": "abc123",
"walletAddress": "7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU"
}
Headers
| Header | Required | Notes |
|---|---|---|
Authorization | yes | Bearer <api_key>. |
Origin | yes for public keys | Must be on the key's allowlist. |
Content-Type | yes | application/json. |
Body
| Field | Type | Required | Notes |
|---|---|---|---|
trackingPageId | string | yes | Tracking page ID. Must match the key. |
walletAddress | string | yes | Base58 Solana address. Validated via @solana/web3.js PublicKey. |
Success response
200 OK:
{
"nonceId": "6f7a1e4a-5b3f-4b3e-8a2c-e2d8c1b4f7a1",
"message": "Wallet Sleuth Verification\n… lines …\nThis signature does not authorize any transaction."
}
| Field | Type | Notes |
|---|---|---|
nonceId | string | Pass this back in the submission body. |
message | string | Exactly the bytes the wallet must sign (UTF-8). Treat as opaque; do not reconstruct client-side. |
The nonce is short-lived and single-use: the first successful call to POST /submissions consumes it. If you wait too long, request a fresh nonce.
Error responses
| Status | error | Meaning |
|---|---|---|
| 400 | invalid_json | Request body wasn't valid JSON. |
| 400 | missing_fields | trackingPageId or walletAddress missing / wrong type. |
| 400 | invalid_wallet_address | walletAddress isn't a valid base58 Solana address. |
| 401 | missing_key | No Authorization: Bearer …. |
| 401 | invalid_key / revoked_key | See Authentication. |
| 401 | missing_origin | Public key with no Origin/Referer. |
| 403 | origin_not_allowed | Origin not on the key's allowlist. |
| 429 | rate_limit_key / rate_limit_ip | Global rate limits. Retry-After included. |
| 429 | rate_limit_session | Session nonce limit hit. Retry-After included. |
Session nonce limit
Sessions (set by the iframe route via the embed_session cookie) have a cap on nonce issuance. This prevents a bad actor from spamming nonce issuance.
If the caller has no session cookie (e.g. a custom UI that never loaded the iframe), the session-specific limit is skipped — but per-key and per-IP limits still apply.
What to do with the response
- Take
messageand convert it to UTF-8 bytes. - Ask the visitor's wallet to sign those bytes.
- Base64-encode the 64-byte Ed25519 signature.
- POST it back to POST /submissions with the
nonceId.
Example (fetch)
const nonceRes = await fetch(
'https://walletsleuth.app/api/embed/siws/nonce',
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${publicKey}`,
},
credentials: 'include',
body: JSON.stringify({ trackingPageId, walletAddress }),
},
);
const { nonceId, message } = await nonceRes.json();
// Sign via wallet adapter
const signatureBytes = await wallet.signMessage(new TextEncoder().encode(message));
const signature = Buffer.from(signatureBytes).toString('base64');
Preflight (OPTIONS)
Returns 204 with:
Access-Control-Allow-Origin: <request origin>
Access-Control-Allow-Methods: POST, OPTIONS
Access-Control-Allow-Headers: Authorization, Content-Type
Access-Control-Allow-Credentials: true
Access-Control-Max-Age: 600
Vary: Origin
Related
- POST /submissions — the follow-up call.
- SIWS signing — protocol-level walkthrough.