Skip to content

Security & Key Management

The WaaS security model is built around industry-standard HD wallet cryptography with layered API permissions for sensitive operations.


HD Wallet Architecture

Each customer wallet is a BIP39 HD wallet:

  • BIP39 — Mnemonic phrase (12 or 24 words) is the wallet root
  • BIP32 — Hierarchical deterministic key tree derived from the mnemonic
  • BIP44 — Standardised derivation paths per network (m/44'/coin'/account'/change/index)

A single mnemonic deterministically generates all addresses across all networks. This means:

  • One backup recovers all addresses and funds
  • Addresses are derived offline — no network call needed
  • The same customer ID always produces the same wallet root

Key Storage

  • Mnemonics are encrypted at rest using AES-256-GCM
  • Private keys are derived on-demand from the encrypted mnemonic — they are not stored separately
  • The mnemonic is returned only once at wallet creation
  • After creation, retrieval requires the wallet:reveal_seed API permission

API Key Permissions

Sensitive WaaS operations require explicit permissions on the API key. Grant only what your integration needs.

PermissionRequired For
(none / default)Creating wallets, deriving addresses, listing addresses, balance queries
wallet:transferPOST /wallet/{id}/transfer — sending crypto
wallet:reveal_seedGET /wallet/{id}/seed — retrieving the mnemonic
wallet:export_keyPOST /wallet/{id}/export-key — exporting raw private keys

Configure permissions in your API key settings in the dashboard.


Secure Mnemonic Handling

When you create a customer wallet, the mnemonic is returned in the response. You must:

  1. Store it immediately in a secrets manager (AWS Secrets Manager, GCP Secret Manager, HashiCorp Vault, etc.)
  2. Never log it — disable request/response logging for the wallet creation endpoint
  3. Encrypt in transit — always use HTTPS
  4. Never expose to the frontend — this is a server-side operation only
  5. Rotate access — revoke API keys with wallet:reveal_seed permission if compromised
javascript
// GOOD — store before doing anything else
const wallet = await rach.wallet.create({ customerId: userId });
await secretsManager.put(`wallet/${userId}/mnemonic`, wallet.mnemonic);

// BAD
console.log('Wallet created:', wallet); // ← logs mnemonic
res.json(wallet); // ← exposes mnemonic to frontend

Private Key Export

The POST /wallet/{id}/export-key endpoint is intended for customer-initiated wallet exports (e.g., when a customer wants to import their wallet into MetaMask or another external wallet).

Best practices:

  • Require re-authentication or 2FA before exporting
  • Only export when the customer explicitly requests it
  • Never store the exported private key — it's shown once
  • Log the export event for audit purposes (but not the key itself)
  • Do not implement auto-export or background key access

Deposit Security — Flash Loans, Fake Transactions & Testnet Isolation

Flash Loans Cannot Trigger Deposit Webhooks

Flash loans borrow and repay within a single atomic on-chain transaction. The net balance change on your monitored address is zero. Rach's monitor detects deposits by comparing currentBalance - previousBalance on each poll cycle. Because a flash loan leaves no net balance change, it is invisible to the monitor and will never fire a webhook.

Testnet Transactions Cannot Affect Mainnet

Testnet and mainnet are separate blockchains with entirely separate state. A transaction on a test network cannot change the balance of a mainnet address. Rach enforces this at the infrastructure level: test_sk_* API keys query testnet RPCs, live_sk_* keys query mainnet RPCs. There is no possible cross-contamination.

EVM 0-Confirmation Safety

On EVM chains (ETH, BSC, POL), eth_getBalance returns the confirmed on-chain balance only — pending mempool transactions are not reflected. By the time Rach detects a balance increase, the transaction is already included in a mined block. A "confirmations": 0 value in wallet.deposit.detected means the deposit is confirmed on-chain but block depth has not yet been measured — it is not a mempool transaction.

The safe_to_credit Field Is Your Credit Gate

Every deposit webhook payload contains an explicit safe_to_credit boolean:

  • wallet.deposit.detected"safe_to_credit": false — show pending status only
  • wallet.deposit.confirmed"safe_to_credit": true — safe to credit customer

This field accounts for all networks, all confirmation methods, and all edge cases. Your integration should only credit when safe_to_credit === true, regardless of the confirmations count or status string.

javascript
// Correct
if (event === 'wallet.deposit.confirmed' && data.safe_to_credit === true) {
  await wallet.credit(data.customer_id, data.amount, data.currency);
}

// Dangerous — do not do this
if (event === 'wallet.deposit.detected') {
  await wallet.credit(data.customer_id, data.amount, data.currency); // ❌
}

Webhook Signature Verification

Always verify the X-Webhook-Signature header on every incoming webhook before processing:

javascript
const crypto = require('crypto');

function verifyWebhook(rawBody, signatureHeader, secret) {
  const expected = crypto
    .createHmac('sha256', secret)
    .update(rawBody) // must be raw Buffer — not parsed JSON
    .digest('hex');

  // Use timing-safe comparison to prevent timing attacks
  return crypto.timingSafeEqual(
    Buffer.from(signatureHeader),
    Buffer.from(expected)
  );
}

Testnet vs Mainnet Isolation

  • Test keys (test_sk_*) always derive testnet addresses — they cannot interact with mainnet
  • Live keys (live_sk_*) always derive mainnet addresses
  • A session/address created with one key type cannot be accessed with the other
  • Never use live keys in development or CI environments

Rate Limiting & Abuse Prevention

  • All API endpoints are rate-limited per API key
  • The wallet:transfer endpoint has stricter limits than read-only operations
  • Implement your own rate limiting on customer-facing operations before they reach the API

Compliance Considerations

  • Rach is not a custodian — you are responsible for the wallets created via WaaS
  • Implement your own AML/KYC checks before allowing large deposits or withdrawals
  • Maintain your own audit trail of all wallet.deposit.confirmed events
  • Consider jurisdiction-specific regulations on custodying crypto assets on behalf of customers

Rach Payments API