The server wallet authentication flow is designed to be idempotent — calling authenticate() multiple times with the same identity is safe and will reuse existing credentials when possible.
How It Works
authenticate()
│
├─ Token already in store? ──► Reuse it (no network call)
│
└─ No token?
│
├─ Derive public key from accessKey
├─ POST /auth/create (register identity)
├─ POST /auth/exchange (get access token)
└─ Store token ──► Return session
On the first call, the SDK:
- Generates or accepts an
accessKey (64-char hex string)
- Derives a public key from it
- Calls
create to register the identity with the backend
- Calls
exchange to obtain an access token
- Stores the token for future use
On subsequent calls with the same identity, the stored token is returned immediately.
Usage
userIdentity is your permanent wallet ID. Use the exact same string on every call — including after restarts and deploys. A different value creates a new wallet.
import { ServerSignerClient } from '@abstraxn/server-signer';
const client = new ServerSignerClient({
apiKey: process.env.ABSTRAXN_API_KEY!,
});
// First run: omit accessKey
// Later runs: pass the accessKey saved from the first authenticate()
const session = await client.authenticate({
userIdentity: 'merchant-backend-user-001',
accessKey: process.env.SERVER_WALLET_ACCESS_KEY,
});
userIdentity + accessKey are a matched pair. Reusing an identity with a different access key returns 409 CONFLICT.
Parameters
| Parameter | Type | Required | Description |
|---|
userIdentity | string | Yes | Permanent ID for this wallet — must be the same string used at creation |
accessKey | string | No | 64-char hex key from first authenticate(). Omit on first run |
userName | string | No | Display name for the wallet user |
userEmail | string | No | Email for the wallet user |
Return Value
| Field | Type | Description |
|---|
didCreate | boolean | true if this was a new identity, false if reusing existing token |
accessToken | string | Current JWT access token |
accessKey | string | The 64-char hex access key (generated or provided) |
targetPublicKey | string | Public key derived from the access key |
organizationId | string | Organization ID for this wallet |
walletAddress | string | On-chain wallet address |
Access Key Management
The accessKey is the root secret for a server wallet identity. It is used to derive the public key that identifies the wallet on-chain.
If you lose the access key, you cannot recover the same wallet identity. Always persist it in a secure secrets manager (AWS Secrets Manager, HashiCorp Vault, GCP Secret Manager, etc.).
First run — omit accessKey, save the returned key:
const session = await client.authenticate({
userIdentity: 'merchant-backend-user-001', // pick once — keep forever
});
await secretsManager.set('SERVER_WALLET_ACCESS_KEY', session.accessKey);
Later runs — same userIdentity, provide the saved key:
const session = await client.authenticate({
userIdentity: 'merchant-backend-user-001',
accessKey: process.env.SERVER_WALLET_ACCESS_KEY,
});
Automatic Token Refresh
Authenticated API calls automatically handle expired tokens:
- A call receives a
401 Unauthorized response
- The SDK sends
POST /auth/refresh with session credentials
- If the refresh succeeds, the access token is updated and the original call is retried
- If the refresh fails, the error is thrown to the caller
No configuration is needed — this behavior is built into every authenticated method (whoami, signTransaction, exportPrivateKey, etc.).
Identity Conflicts
If you call authenticate with a userIdentity that already exists but provide a different accessKey, the backend returns a 409 CONFLICT error:
ConflictError: userIdentity 'merchant-backend-user-001'
already exists with a different access key
This protects against accidental identity collisions. To resolve:
- Use the original access key for this identity, or
- Choose a different
userIdentity string
Next Steps