Skip to main content

Overview

Each checkout session gets a unique deposit address. The customer sends funds to that address. My Crypto Server detects the transfer, waits for the required confirmations, marks the session paid, and sweeps the funds to your payout wallet. The entire pipeline is idempotent and crash-resumable — webhook retries, manual checks, and Inngest replays can all fire more than once without double-crediting or double-sweeping.

Stage 1: Session creation

When POST /api/v1/checkout_sessions is called:
  1. A unique derivation index is allocated and a deposit address is derived from the merchant seed (BIP-44 for EVM).
  2. A price quote (ethPriceUsd) is frozen and the required on-chain amount is computed using fixed-point math — no floats.
  3. The session is inserted with status: pending.
  4. A session/created Inngest event is emitted.
The sessionLifecycle Inngest function then:
  • Subscribes the deposit address to Alchemy’s Address Activity webhook so incoming transfers trigger an immediate notification.
  • Sleeps durably until expiresAt. If the session is still pending at expiry, it transitions to expired.

Stage 2: Detecting the transfer

Two independent paths feed the same ingestion pipeline: Primary — Alchemy webhook (push): Alchemy detects the transfer and POSTs to /api/webhooks/alchemy. The HMAC-SHA256 signature is verified, the transfer is ingested, and payment/detected is emitted to Inngest. Fallback — merchant check (pull): The dashboard’s “Check payment” button hits the on-chain scanner directly, scanning the chain for transfers to the session’s address. Customer fallback (pull): While the customer has the checkout page open, the browser polls after a delay (2 min for EVM/Solana, 4 min for Bitcoin). This closes the gap between webhook delivery and user experience. All paths converge on the same ingestTransfer function, which is idempotent via a unique index on (txHash, toAddress, vout).

Amount classification


Stage 3: Verifying finality

After detection, the paymentVerify Inngest workflow:
  1. Waits 60 seconds.
  2. Calls verifyAndFinalize — checks the transaction receipt and counts confirmations.
  3. If not enough confirmations yet, waits 30 seconds and retries (up to 10 times, ~5 min total).
  4. Reorg protection — if the receipt’s block doesn’t match what was recorded, the payment is reset and the session goes back to pending.
Once the transaction has ≥ REQUIRED_CONFIRMATIONS:
  • The payment is marked finalized.
  • The session transitions to paid (or paid_late if past expiry but within the grace window).
  • The deposit address is removed from Alchemy’s watchlist.
  • The sweep runs inline.
REQUIRED_CONFIRMATIONS is set via the env var. Default is 3; mainnet ETH should use 12+.

Stage 4: Session status machine


Stage 5: Webhook delivery

Once the session is marked paid, a session.paid event is enqueued. The webhookDeliver Inngest function POSTs it to each of your registered webhook endpoints. Failures are retried up to 6 times with exponential backoff (~31h total).

Recovery paths