You are integrating the BlindPay wallet (the managed, BlindPay-custodied wallet) into my application: BlindPay creates and custodies a wallet per customer, and I move money through it with plain API calls, never with private keys or signatures on my side.
Before writing code, read these sources and follow them over any prior knowledge:
- https://blindpay.com/docs/llms.txt (read the managed wallets, payins, payouts, send, and webhooks pages)
- The OpenAPI spec: curl https://api.blindpay.com/doc
Build the flow:
1. Prerequisites: an approved customer (kyc_status "approved"). Managed wallets are in beta; support only the chains the docs list (Ethereum, Base, Polygon, Arbitrum, Solana) with USDC/USDT, and USDB on development testnets. Stellar and Tron are not supported for managed wallets; if I need those, fall back to blockchain wallets instead.
2. Create the wallet: POST /v1/instances/{instance_id}/customers/{customer_id}/wallets with network, name, and my database ID as external_id. BlindPay generates the address server-side. Persist both the id (bl_) and the address against my user record: payin quotes reference the id, payouts reference the address. A customer can hold up to 10 wallets; enforce that in my schema. Never confuse bl_ (managed wallet) with bw_ (blockchain wallet) when passing IDs into quotes or payouts.
3. Show the balance: GET /v1/instances/{instance_id}/customers/{customer_id}/wallets/{wallet_id}/balance. Treat this endpoint as the source of truth for available funds and check it before quoting any outbound movement.
4. Money in, two paths:
a. Fiat payin: POST /v1/instances/{instance_id}/payin-quotes with wallet_id (not blockchain_wallet_id), then POST /payins/evm with the resulting payin_quote_id. Stablecoins settle into the wallet; confirm with payin.complete and wallet.inbound webhooks.
b. Direct stablecoin deposit: share the wallet address; any USDC/USDT transfer on the matching network credits it and fires wallet.inbound. Warn users that deposits on the wrong chain are lost.
5. Money out, two paths:
a. Fiat payout: POST /v1/instances/{instance_id}/quotes for a payout quote, then POST /payouts/evm with quote_id and the managed wallet's address as sender_wallet_address before expires_at. Because BlindPay custodies the wallet there is no ERC-20 approve and no signature step; the payout is a single call.
b. Stablecoin transfer: POST /v1/instances/{instance_id}/transfer-quotes then POST /transfers to move funds to another wallet or external address. The transfer quote expires in 5 minutes, so quote and execute in one server-side sequence. Only USDC crosses chains (Circle CCTP v2 between Ethereum, Polygon, Base, Arbitrum); everything else needs the same network on both sides.
6. Drive state from Svix-verified webhooks (svix-id, svix-timestamp, svix-signature against the raw body): wallet.new on creation, wallet.inbound on deposits, payin.new/complete, payout.new/update/complete, transfer.new/complete. Reconcile every event back to my user via the bl_ ID or external_id.
7. Handle the unit mismatch explicitly: wallet.inbound reports amount scaled by 100 (100 means $1.00) while the balance endpoint reports the raw amount. Normalize both into one internal representation with a documented conversion, and never compare them directly.
8. Test end to end on a development instance: KYC auto-approves, USDB works on every dev testnet, and payout sentinel amounts verify all outcomes ($666.00 forces a failed payout, $777.00 forces a refunded one).
Constraints:
- API keys stay server-side; never expose instance or customer IDs in client code paths that don't need them.
- Amounts are integers in minor units everywhere; never use floating point for money.
- Idempotency on money movement: key payins, payouts, and transfers on my own transaction ID and check for an existing record before creating.
- wallet.inbound only fires for USDC and USDT; don't rely on it for other tokens.
Deliverables: wallet provisioning service with the bl_/address persistence, balance display wired to the balance endpoint, payin-to-wallet and payout-from-wallet flows, the transfer path with the 15-second quote handled, webhook handlers with unit normalization, and a passing end-to-end test on the development instance covering deposit, payout, failed, and refunded cases.