Overview
Stage 1: Session creation
WhenPOST /api/v1/checkout_sessions is called:
- A unique derivation index is allocated and a deposit address is derived from the merchant seed (BIP-44 for EVM).
- A price quote (
ethPriceUsd) is frozen and the required on-chain amount is computed using fixed-point math — no floats. - The session is inserted with
status: pending. - A
session/createdInngest event is emitted.
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 stillpendingat expiry, it transitions toexpired.
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, thepaymentVerify Inngest workflow:
- Waits 60 seconds.
- Calls
verifyAndFinalize— checks the transaction receipt and counts confirmations. - If not enough confirmations yet, waits 30 seconds and retries (up to 10 times, ~5 min total).
- Reorg protection — if the receipt’s block doesn’t match what was recorded, the payment is reset and the session goes back to
pending.
≥ REQUIRED_CONFIRMATIONS:
- The payment is marked
finalized. - The session transitions to
paid(orpaid_lateif 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 markedpaid, 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).