POST /v1/auth/wallet/verify
https://api.triport.io/v1/auth/wallet/verifyVerify a wallet signature against a previously issued challenge nonce, then open an authenticated console session.
This is the second and final step of the wallet sign-in flow. First call
POST /v1/auth/wallet/challenge to obtain a single-use
nonce and the exact human-readable message the wallet must sign. Have the
user sign that message with their wallet, then post the nonce, address,
chain, and resulting signature here.
The server reloads the challenge by nonce, confirms it was issued for the same
address and chain, cryptographically verifies the signature, and — on
success — links or creates the user account for that wallet identity and issues
a console session. The session is delivered as cookies, not as a token: the
response body contains only the user object.
Signature handling differs by chain:
- EVM (
chain: "evm") — a 65-bytesecp256k1signature inR‖S‖Vorder, supplied as hex (with or without a0xprefix).Vmay be27/28or0/1. The message is hashed with the EIP-191personal_signenvelope (keccak256("\x19Ethereum Signed Message:\n" + len(msg) + msg)), the public key is recovered, and the derived address must match the submitted address (case-insensitive — EVM addresses are not EIP-55 checksum-validated). - Solana (
chain: "solana") — a 64-byteed25519signature. It is accepted as hex, base58 (Phantom default), or base64 (standard or URL-safe, padded or unpadded). The address is the base58-encoded 32-byte public key, and the signature is verified directly against the challenge message.
Gotchas:
- The
nonceis single-use. It is burned on the first successful verify, so a retry with the samenoncereturnsinvalid_nonce. Request a fresh challenge to try again. - The challenge expires 5 minutes after it is issued; an expired or unknown
nonce returns
invalid_nonce. - The
address/chainyou send here must match the values used in the challenge request, or you getaddress_mismatch.
Parameters
Request body (JSON). All fields are required.
noncestring (UUID)requiredPOST /v1/auth/wallet/challenge.addressstringrequired0x + 40 hex chars. Solana: base58 of the 32-byte public key.chainstringrequired"evm" or "solana". Must match the chain used for the challenge.signaturestringrequiredmessage. EVM: 65-byte hex. Solana: 64-byte hex/base58/base64.Response
200 OK — the wallet was verified and a session was opened. The session and
CSRF cookies are returned via Set-Cookie; the body carries the user record.
Response headers include:
Set-Cookie: nl_session=<opaque>; Path=/; HttpOnly; Secure; SameSite=Lax
Set-Cookie: nl_csrf=<opaque>; Path=/; Secure; SameSite=LaxFor wallet sign-ups, email is a synthetic placeholder of the form
<address>@<chain>.wallet and display_name is the shortened address. Both can
be updated later from the console.
user.idstring (UUID)user.emailstring<address>@<chain>.wallet placeholder.user.display_namestringuser.avatar_urlstringuser.billing_customer_idstringuser.created_atstring (RFC 3339)user.updated_atstring (RFC 3339)Errors
Errors use the shared envelope { "error": "<code>" }. See errors.md
for the full envelope and conventions.
| Code | HTTP | When it happens |
|---|---|---|
invalid_nonce | 400 | nonce is not a valid UUID, or no matching challenge exists (unknown, already consumed, or expired). |
invalid_address | 400 | address is malformed for the given chain, or chain is not evm/solana. |
address_mismatch | 400 | The address/chain submitted do not match the challenge the nonce was issued for. |
invalid_signature | 400 | Signature is the wrong length/encoding, or it does not verify to the submitted address. |
invalid_json | 400 | Request body is not valid JSON (or exceeds the 8 KiB limit). |
session_issue_failed | 500 | Signature verified, but the server could not mint the session. |
method_not_allowed | 405 | A method other than POST was used. |
internal | 500 | Unexpected server-side failure (challenge lookup, nonce consume, or user upsert). |
Examples
JavaScript (fetch)
async function walletVerify({ nonce, address, chain, signature }) {
const res = await fetch("https://api.triport.io/v1/auth/wallet/verify", {
method: "POST",
headers: { "Content-Type": "application/json" },
credentials: "include", // required so nl_session / nl_csrf cookies are stored
body: JSON.stringify({ nonce, address, chain, signature }),
});
if (!res.ok) {
const { error } = await res.json();
throw new Error(error);
}
const { user } = await res.json();
return user;
}TypeScript SDK (@triport/sdk)
import { TriportConsole } from "@triport/sdk";
const console = new TriportConsole({ baseUrl: "https://api.triport.io" });
// 1. Request a challenge.
const { nonce, message } = await console.auth.wallet.challenge({
address,
chain: "solana",
});
// 2. Sign `message` with the connected wallet (e.g. Phantom).
const signature = await wallet.signMessage(message); // base58 / base64 / hex
// 3. Verify — session cookies are persisted by the SDK client.
const { user } = await console.auth.wallet.verify({
nonce,
address,
chain: "solana",
signature,
});Python (triport-sdk)
from triport import ConsoleClient
client = ConsoleClient(base_url="https://api.triport.io")
# 1. Request a challenge.
challenge = client.auth.wallet.challenge(address=address, chain="evm")
# 2. Sign challenge.message with the user's wallet (65-byte secp256k1, hex).
signature = sign_personal_message(challenge.message, private_key)
# 3. Verify; the client session keeps the nl_session / nl_csrf cookies.
result = client.auth.wallet.verify(
nonce=challenge.nonce,
address=address,
chain="evm",
signature=signature,
)
print(result.user.id)Notes
- Two-step flow. Always pair this call with
POST /v1/auth/wallet/challenge; thenonceand themessageyou sign both come from there. - Sign the exact message. The wallet must sign the challenge
messagebyte-for-byte. Re-deriving or altering it (extra whitespace, different line endings) will fail signature verification. - Cookie session, not a bearer token. This endpoint returns no token. Reuse
the
nl_sessioncookie on subsequent requests, and echo thenl_csrfvalue in theX-CSRF-Tokenheader on mutating calls. See the authentication overview. - Check who you are with
GET /v1/auth/me, and end the session withPOST /v1/auth/logout.