Working notes (uncommitted). Goal: let a brand-new user who already holds common crypto pay to bootstrap their first API key, breaking the Unicity chicken-and-egg. Research → analysis → plan only; no implementation yet.
Sourced from a multi-agent web-research pass (6 finder dimensions, 32 options, ~95 primary sources fetched) + a codebase-fit analysis. As of mid-2026.
| Rank | Approach | Why |
|---|---|---|
| Primary | Self-hosted HD-wallet acceptance of USDC on an L2 (Base/Arbitrum), detected via an Alchemy/QuickNode address-activity webhook, credited through one new Jetty Handler.Abstract endpoint |
Genuinely non-custodial (funds land in operator-derived addresses; the provider only watches), reuses the existing payment_sessions design almost 1:1, stablecoin = trivial fixed $1–50 price, L2 = sub-cent payer fee, zero prior relationship needed. Backend code ≈ an HMAC-verified JSON webhook + a couple of REST calls. |
| Runner-up (purest) | Same HD-wallet design but poll eth_getLogs with web3j instead of a webhook |
No third party in the payment path at all, no public endpoint to secure (outbound-only). Cost: you own a poller (confirmation depth, reorg, last-scanned-block persistence). |
| Most elegant bet | x402 (HTTP-402 + USDC on Base) via a facilitator | Conceptually the cleanest fit for an API gateway — answer the bootstrap request with 402, verify+settle synchronously, mint the key. No webhook, no watcher. But the Java SDK is 0.1.0-SNAPSHOT and servlet-filter-shaped (not Jetty), and it leans on a Coinbase facilitator in the settle path. Great strategic direction, slightly early to bet the only purchase path on. |
| Hosted fallback | NOWPayments (email-only signup, no merchant KYC for crypto-only, REST + HMAC-SHA512 IPN, 200+ assets) | If you'd rather someone else watch every chain. Honest cost: hybrid-custodial (funds transit their wallet), which departs from this project's non-custodial ethos. |
| Do NOT adopt | Coinbase Commerce | Its non-custodial product shut down March 31 2026 outside US/Singapore. A cautionary tale for betting the bootstrap path on one hosted vendor's roadmap. |
Reframing that raises the stakes: the native Unicity completePayment() is currently disabled (/api/payment/complete → 501) pending the SDK-v2 migration. So an external rail isn't merely a bootstrap on-ramp right now — it's effectively the only working purchase path. Design it to stand alone, not just to hand off into the native flow.
The current payment_sessions + PaymentService design already implements ~every pattern a crypto rail needs. An external USDC rail mostly reuses it:
| Pattern the research says you need | Already in this repo | What changes for a USDC rail |
|---|---|---|
| Unique receive address per session | payment_sessions derives a masked address from serverSecret + receiverNonce |
Derive an EVM address per session instead (web3j BIP-32 from the same seed). EVM has no memo field, so per-session address is the right matching strategy. |
| Exactly-once activation / double-credit guard | unique request_id index + activation in one DB tx |
Swap the dedup key to UNIQUE(chain, tx_hash, log_index); keep the insert in the same tx as the key mint. |
| Serialize concurrent completers | FOR UPDATE NOWAIT, lock order api_key → session |
Keep verbatim. |
| New-key purchase | should_create_key, api_key nullable, ApiKeyService mints sk_…, ApiKeyUtils sets 30-day expiry |
Reuse the entire mint/activate path unchanged — it's the back half of the disabled completePayment(). |
| Quote/expiry window | 15-min session expiry | Keep (for USDC it's an anti-stale-address measure, not FX protection — stablecoin drift ≈ 0). |
| Amount validation | validateIncomingCoins rejects over and under |
|
| Manual reconciliation | AdminHandler payment-session views |
Extend with the new rail's sessions. |
Net new pieces: (1) EVM address derivation (web3j, one-time per session), (2) a detection mechanism (webhook handler or poller), (3) a per-(chain,asset) confirmation-depth state machine (PENDING → CONFIRMED), (4) a tolerance band on amount matching, (5) USDC→treasury sweep ops.
| Option | Custody | Assets/Chains | Integration | Java/Jetty fit | Ops | Fees | Settle latency | KYC (op / payer) | Bootstrap | Maturity 2026 |
|---|---|---|---|---|---|---|---|---|---|---|
| HD-wallet + Alchemy webhook | Non-custodial | USDC/USDT/ETH on Base/Arb/OP/Polygon | Webhook push + REST addr-reg | High (JSON+HMAC; web3j for derivation only) | Low | 0% processor; payer sub-cent gas | Real-time, ~secs | Dev acct (email) / none | Excellent | Mature |
| HD-wallet + eth_getLogs poll (web3j) | Non-custodial | USDC/ETH on L2s | Poller (outbound only) | Strong (web3j; no public endpoint) | Medium | 0%; payer gas | ~secs + poll lag | none / none | Excellent | Mature pattern |
| x402 (CDP facilitator) | Non-custodial | USDC on Base/Polygon/Arb/Solana, EURC | HTTP-402 sync verify+settle | Medium (Java SDK SNAPSHOT, not Jetty) | Low | Free ≤1k tx/mo, then $0.001/tx; gasless payer | sub-sec–secs | Free CDP acct / none | Excellent | Strong; Java SDK early |
| x402 self-hosted facilitator | Non-custodial | USDC EVM L2 | HTTP-402 + own relayer | Heavier (web3j + relayer key/gas) | High | no fee; you fund gas | as above | none / none | Excellent | Bespoke in Java |
| NOWPayments | Hybrid | 200–300+ incl USDC Base/Solana/Tron | Webhook (IPN) + REST | High (REST + HMAC-SHA512) | Low | 0.5% same-coin | on-chain (~secs–1min L2) | email only / none | Excellent | Mature |
| Helio (MoonPay Commerce) | Non-custodial | USDC/SOL/ETH/BTC (Solana-centric) | Webhook + Pay Link | High | Low | 2% (1% w/ HelioX) | near-instant Solana | light / none | Excellent | Mature (rebrand flux) |
| Stripe Stablecoin (Bridge) | Custodial | USDC Eth/Sol/Polygon/Base | Webhook + hosted redirect | Best — official stripe-java v33, Java 21 |
Low | flat 1.5% | secs–mins | Stripe KYB / none | Strong (if user holds USDC) | GA US-only, EU beta |
| Circle Programmable Wallets | Hybrid | USDC 19+ chains, EURC | Webhook + REST (Circle watches) | Medium (no Java SDK, REST) | Medium | usage-tiered, sales-gated | on-chain finality | Circle KYB / none | Good | Mature |
| BTCPay Server | Non-custodial | BTC on-chain + Lightning; altcoins via plugin | Webhook (BTCPay-Sig) + own node | High (thin REST + HMAC) | High | 0% (network only) | LN ~secs / on-chain ~10 min | none / none | Excellent (BTC) | Very mature |
| OpenNode | Custodial | BTC + Lightning | Webhook + REST | High | Low | 1% | LN instant / chain ~10 min | full KYB / none | Good (payer) | Established |
| CoinGate / BitPay | Custodial | 70–100+, fiat settle | Webhook + REST | High (BitPay Java lib) | Med/High | ~1%+ stacked | chain + fiat next-day | full KYC/KYB / none | Fine (payer) | Mature, regulated |
| Transak / MoonPay on-ramp | Non-custodial delivery | card/bank → 100+ assets | Hosted widget + webhook | Medium | Medium | ~1–4.5% card + min | mins + KYC | offloaded / user KYC | Solves no-crypto user only | Mature |
1. HD-wallet + provider address-activity webhook (USDC on Base) — recommended default
Best balance of non-custodial purity and minimal code on this stack. The provider (Alchemy/QuickNode) only watches addresses you register; USDC lands directly in operator-derived addresses. Integration ≈ a single Handler.Abstract webhook that constant-time-verifies X-Alchemy-Signature + timestamp, dedups on txHash:logIndex, and calls the existing mint path. Watch-outs: the public webhook is a free-key forgery vector if signature/timestamp/replay checks are sloppy; vendor delivery hiccups can silently stall payments (many teams add a polling reconciliation sweep — which erodes the simplicity edge slightly).
2. HD-wallet + eth_getLogs polling (web3j) — purest, zero-vendor
The closest match to the existing philosophy (server secret → derived address → verify on-chain). Outbound-only, no public endpoint, no processor account. web3j gives first-class Java HD derivation + ERC-20 Transfer log filtering. Watch-outs: you write and operate the poller — confirmation depth, reorg handling, last-scanned-block persistence across restarts, few-second detection lag. (A free RPC tier is fine.)
3. x402 (USDC on Base) — most elegant, slightly early
The HTTP-402 + retry shape is the natural way for an API gateway to charge: emit 402 with the required USDC amount, the client signs a gasless EIP-3009 authorization, you verify+settle via a facilitator (synchronous — no webhook, no watcher, no reconciliation table), then mint the key. Truly non-custodial; gasless for a brand-new payer (needs only USDC, no ETH). 2026 adoption is strong (Base/Polygon/Arbitrum/Solana; AWS exposes it at CloudFront/WAF). Watch-outs: the official Java SDK is 0.1.0-SNAPSHOT and servlet-filter oriented — on Jetty 12 you'd write a thin REST integration to the facilitator, not drop-in. Hosted facilitator = a trusted third party in settlement.
4. NOWPayments — pragmatic hosted fallback
Fastest go-live: email-only signup, no merchant KYC for crypto-only, plain REST + HMAC-SHA512 IPN that mirrors the existing PaymentHandler, 200+ assets so the payer brings whatever they hold. Watch-outs: hybrid-custodial (funds touch their wallet before forwarding) and the IPN signature requires exact alphabetical JSON key-sorting (easy to get subtly wrong).
5. BTCPay Server — BTC-first non-custodial gold standard Funds go straight to the operator's xpub, 0% platform fee, no KYC, signed webhooks. Native on-chain BTC means a new user with BTC anywhere can pay. Watch-outs: highest ops burden (run a Bitcoin full node ± Lightning), ~10-min on-chain confirmation hurts one-shot bootstrap UX, and BTC volatility forces per-invoice re-quotes against a fixed USD price.
- Relax strict-exact-amount → tolerance band. Current
validateIncomingCoinsrejects over and under. Exchange withdrawals deduct the network fee from the sent amount, so "exact" rarely arrives — strict matching would hard-fail most first payments and defeat the whole goal. Accept≥ required − ε; decide an overpayment policy (credit-forward vs manual refund). - Confirmation depth vs reorg. Don't credit on 0-conf. Use a per-(chain,asset)
PENDING → CONFIRMEDthreshold. Circle's production thresholds: Ethereum 12 blocks (~3 min), Base/OP 12 (~3–9 min), Arbitrum 12 (~4–6 min), Polygon 2–3 (~8 s), Solana 1 (~0.4 s). Optimistic-rollup true finality is 7 days; crediting on L2 soft-confirm for a $1–50 key is the accepted, documented compromise. - Idempotency. Keep the existing shape: deterministic dedup key (
txHash:logIndex) under a UNIQUE constraint, inserted in the same DB tx as the mint; webhooks/watchers are at-least-once. - Webhook security (if not polling): HMAC over raw bytes before JSON parse, constant-time compare (
MessageDigest.isEqual— already used inCryptoUtils), signed-timestamp freshness (~5 min), persisted event-id replay guard. - Irreversibility / refunds. Crypto is final — no chargebacks (a feature), but state "payments are irreversible, this one-off key is non-refundable" before pay. Any refund is a new outbound, possibly-regulated, irreversible transfer.
- Sweep/treasury. USDC can't pay its own gas; you must fund native gas to dust deposit addresses to sweep to treasury (or use account abstraction). Decide who funds it and on what schedule.
- Sanctions/AML. Cheapest posture: accept only mainstream USDC-on-L2 to minimize exposure; optionally screen source addresses at credit time (Chainalysis/TRM/Elliptic/AMLBot). Money-transmitter/Travel-Rule scope is jurisdiction-specific — legal review, not an engineering call.
- Asset/chain — commit to one first (recommendation: USDC on Base). This determines web3j-vs-LN, the provider, and the confirmation table.
- Custody stance — is hybrid (NOWPayments) or custodial (Stripe) acceptable as a fallback, or is strictly non-custodial a hard requirement (rules out all hosted processors)?
- Operator jurisdiction & legal posture — drives money-transmission/VASP/Travel-Rule exposure and whether US-only rails (Stripe Stablecoin, Coinbase Business) are even available.
- Amount-matching policy — tolerance band
ε, and overpayment handling (credit-forward vs refund-owed ledger). - Confirmation depth per chain — credit at L2 soft-confirm (~secs) or wait deeper?
- Detection — webhook (least code, public attack surface, vendor in loop) vs polling (outbound-only, you own the watcher) vs both (webhook + reconciliation sweep).
- Persistent key vs per-request metering — mint a key on first payment (matches current model) or also support x402-style per-call gating later?
- No-crypto users — if a meaningful share hold no crypto, none of these serve them; you'd add a card on-ramp (Transak/MoonPay, user KYC, poor small-ticket economics) separately.
- Native-rail timing — since
/completeis 501 today, should the external rail be self-standing (the only purchase path) rather than a hand-off into the native flow?
- Phase 0 — Decide. Resolve the decisions above, especially asset/chain, custody, jurisdiction. Without these the schema and provider choice are premature.
- Phase 1 — Model. Extend
payment_sessions(or a sibling table) withrail/chain/asset, the derived EVMdeposit_address, aPENDING/CONFIRMED/FAILEDstate, and aprocessed_chain_events UNIQUE(chain, tx_hash, log_index)dedup table. New FlywayV2x__…. Reuseshould_create_key+ the nullableapi_key. - Phase 2 — Address derivation. web3j BIP-32 from the existing server seed → one deterministic EVM address per session (mirrors
receiverNonce). Pure unit-testable, no network. - Phase 3 — Detection. Implement one of: (a) a new
Handler.Abstractwebhook (HMAC + timestamp + replay, raw-bytes verify) in the combined chain, or (b) a web3j poller with persisted last-scanned block. Spike both behind an interface; pick per the Phase-0 decision. - Phase 4 — Activation. On
CONFIRMED, run the existing mint/activate path (the back half ofcompletePayment()) inside one tx with the dedup insert and theapi_key → sessionlock order. Apply the tolerance band. - Phase 5 — Admin & ops. Extend
AdminHandlerviews for the new rail; build the USDC→treasury sweep job and gas funding; add reconciliation (and, if webhook-primary, a polling safety sweep). - Phase 6 — Harden. Sanctions screening (if in scope), metrics (
gateway_payment_*), alerting on stuckPENDING, runbook for stuck/over/under/wrong-chain deposits, and the irreversibility/refund policy copy.
- x402: https://github.com/coinbase/x402 · https://docs.cdp.coinbase.com/x402/welcome · https://docs.cdp.coinbase.com/x402/network-support · Java SDK https://github.com/coinbase/x402/tree/main/java · https://www.coinbase.com/developer-platform/discover/launches/x402
- Self-hosted EVM: Alchemy address-activity webhook https://www.alchemy.com/docs/reference/address-activity-webhook · web3j BIP-32 https://javadoc.io/static/org.web3j/crypto/4.5.5/org/web3j/crypto/Bip32ECKeyPair.html · Base finality https://docs.base.org/base-chain/network-information/transaction-finality · USDC on Base https://basescan.org/token/0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913 · AWS digital-asset payments https://aws.amazon.com/blogs/web3/processing-digital-asset-payments-on-aws/
- Confirmation thresholds: Circle https://developers.circle.com/circle-mint/blockchain-confirmations · finality https://blog.trailofbits.com/2023/08/23/the-engineers-guide-to-blockchain-finality/
- NOWPayments: https://nowpayments.io/crypto-payment-gateway · IPN https://nowpayments.zendesk.com/hc/en-us/articles/21395546303389-IPN-and-how-to-setup
- BTCPay: https://docs.btcpayserver.org/API/Greenfield/v1/ · https://github.com/btcpayserver/btcpayserver
- Stripe stablecoin: https://docs.stripe.com/payments/stablecoin-payments ·
stripe-javahttps://github.com/stripe/stripe-java - Coinbase Commerce shutdown: https://www.moonpay.com/newsroom/coinbase-commerce-shutdown-guide-for-merchants
- Patterns: webhook best practices https://apidog.com/blog/payment-webhook-best-practices/ · crypto AML https://www.elliptic.co/blockchain-basics/what-is-crypto-aml-compliance · Travel Rule https://hacken.io/discover/crypto-travel-rule/