Payments

x402 + USDC

LettersByLetter implements HTTP-native x402 v2 concepts. Protected resources return 402 with a base64 JSON PAYMENT-REQUIRED header. Clients retry with PAYMENT-SIGNATURE. Success includes PAYMENT-RESPONSE.

Flow

1. POST /api/v1/credits  { "amountCents": 5000 }
2. 402 Payment Required
   Header PAYMENT-REQUIRED: base64(PaymentRequired)
   accepts[] includes:
     - scheme: exact
     - network: eip155:8453 (USDC on Base)
     - network: solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp
3. Client signs EIP-3009 transferWithAuthorization (or Solana exact payload)
4. Retry the same URL with PAYMENT-SIGNATURE: base64(PaymentPayload)
5. 200 OK + PAYMENT-RESPONSE: base64(SettlementResponse)
   Credits are added 1:1 with USD cents.

curl

curl -i -X POST "$HOST/api/v1/credits" \
  -H "Authorization: Bearer lbl_your_key" \
  -H "Content-Type: application/json" \
  -d '{"amountCents":1000}'

# Decode PAYMENT-REQUIRED, sign, then:
curl -i -X POST "$HOST/api/v1/credits" \
  -H "Authorization: Bearer lbl_your_key" \
  -H "PAYMENT-SIGNATURE: $SIGNED_PAYLOAD" \
  -H "Content-Type: application/json" \
  -d '{"amountCents":1000}'

Facilitator

Set X402_FACILITATOR_URL to a Coinbase CDP-style facilitator exposing /verify and /settle. If unset, a mock facilitator accepts well-formed demo signatures so the 402 → pay → retry path can be tested locally.

Merchant wallets come from X402_PAY_TO (Base) and X402_PAY_TO_SOLANA. Never commit those keys; only public receive addresses belong in env.

The dashboard Billing page can mint a demo PAYMENT-SIGNATURE and settle against the same path — useful when a wallet is not connected.