Skip to main content

Troubleshooting

Solutions to common issues when integrating x402 payments.

Client Errors

User rejected the signature

Symptom: Error message contains “User rejected” or “User denied” Cause: User clicked “Reject” in their wallet when asked to sign Solution:
try {
  const response = await paidFetch(url);
} catch (err) {
  if (err.message?.includes('User rejected') ||
      err.message?.includes('User denied')) {
    // User cancelled - show friendly message
    showToast('Transaction cancelled. Try again when ready.');
    return;
  }
  throw err; // Re-throw other errors
}

insufficient_balance

Symptom: API returns { "reason": "insufficient_balance" } Cause: User doesn’t have enough of the selected token Solution:
import { checkBalance } from '@agentokratia/x402-escrow/client';

// Check balance before request
const canAfford = await checkBalance(
  publicClient,
  account.address,
  USDC,
  BigInt(requiredAmount)
);

if (!canAfford) {
  showToast('Insufficient balance');
}
Or use createBalanceSelector which automatically picks a token the user can afford.

quote_expired

Symptom: API returns { "reason": "quote_expired" } Cause: The DEX swap quote expired before settlement Solution: The SDK automatically retries with fresh quotes. If persisting:
  • Check network latency
  • Ensure system clock is accurate

Wrong network

Symptom: Error about network mismatch or chain ID Cause: Wallet is on different network than API expects Solution:
import { base, baseSepolia } from 'viem/chains';

// Check and switch network before making request
const currentChainId = await walletClient.getChainId();
const requiredChainId = base.id; // or baseSepolia.id for testnet

if (currentChainId !== requiredChainId) {
  await walletClient.switchChain({ id: requiredChainId });
}

Insufficient token balance

Symptom: Transaction fails or signature rejected Cause: Wallet doesn’t have enough of the selected token Solution:
import { formatUnits, erc20Abi } from 'viem';

const USDC_ADDRESS = '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913';

const balance = await publicClient.readContract({
  address: USDC_ADDRESS,
  abi: erc20Abi,
  functionName: 'balanceOf',
  args: [walletClient.account.address],
});

const requiredAmount = BigInt('1000000'); // $1
if (balance < requiredAmount) {
  const needed = formatUnits(requiredAmount - balance, 6);
  alert(`Need ${needed} more USDC`);
}

Server Errors

401 Unauthorized

Symptom: All requests return 401 Cause: Invalid or missing API key Solution:
  1. Check API key is set in environment:
    echo $X402_API_KEY
    
  2. Verify key format: should start with x402_
  3. Ensure Bearer prefix is included:
    Authorization: `Bearer ${process.env.X402_API_KEY}`
    

503 Service Unavailable

Symptom: Facilitator requests fail with 503 Cause: Facilitator service is temporarily unavailable Solution:
const server = new x402ResourceServer(facilitator)
  .register('eip155:8453', new ExactEvmScheme())
  .register('eip155:8453', escrow)
  .onError(async (ctx, error) => {
    if (error.status === 503) {
      console.error('Facilitator unavailable:', error);
      // Optionally serve request for free during outage
    }
  });

429 Rate Limited

Symptom: Requests return 429 Too Many Requests Cause: Exceeded rate limit (1000/min per API key) Solution:
  1. Check for request loops in your code
  2. Implement client-side rate limiting
  3. Contact support for higher limits if legitimately needed

Payment not detected

Symptom: 402 returned even after user paid Cause: Payment header not being forwarded Solution: Ensure headers are passed through proxies:
// Nginx
proxy_pass_request_headers on;

// Express proxy
app.use('/api', proxy({
  target: 'http://backend',
  changeOrigin: true,
  onProxyReq: (proxyReq, req) => {
    if (req.headers['payment-signature']) {
      proxyReq.setHeader('payment-signature', req.headers['payment-signature']);
    }
  },
}));

Debugging Tips

Check token balances

import { checkBalance } from '@agentokratia/x402-escrow/client';

const USDC = '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913';
const WETH = '0x4200000000000000000000000000000000000006';

const hasUSDC = await checkBalance(publicClient, address, USDC, amount);
const hasWETH = await checkBalance(publicClient, address, WETH, amount);

console.log('Can pay with USDC:', hasUSDC);
console.log('Can pay with WETH:', hasWETH);

Inspect HTTP headers

# See what headers are being sent
curl -v https://api.example.com/protected-endpoint \
  -H "Payment-Signature: base64encodedpayload..."

Verify facilitator connectivity

curl https://facilitator.agentokratia.com/api/supported
Should return supported networks and tokens.

Still Stuck?