System / 001
Everyday Car Auctions
Reverse-auction marketplace
Role
Full-Stack & Backend Engineering
Period
Jun 2025 – Present
Domains
4
Context
Everyday Car Auctions LTD (UK)
Primary Outcome
Designed transaction-safe bidding and auction lifecycle under concurrent load.
Last updated 2026-08-11
01 / Context
Everyday Car Auctions (ECA) is a UK-based digital car auction platform operating on a timed reverse-auction model — vehicle prices drop at set intervals while buyers place competing bids, with the highest qualifying bid winning the sale. Sellers can list via auction or receive an instant offer. Listings also syndicate to AutoTrader and MOTORS.
02 / Problem
A real-time auction engine is one of the harder problems in marketplace software: prices must drop on precise schedules, bids must be validated and ranked instantly, and the system must stay correct under concurrent bidders without race conditions or lost bids. Real money moves through the platform on top of that — winning bidders pay a deposit, sellers get paid out net of platform fees — and every payment event has to stay in sync with the auction outcome even when two bids land in the same millisecond.
03 / Constraints
Live production users from early on; real money in the loop from day one, so there was no tolerance for "fix it later" on payment state; third-party syndication to AutoTrader and MOTORS constrained how listing data could be modeled.
04 / Architecture
CLIENT | v API GATEWAY | v AUCTION SERVICE ----> REDIS | v BID / PRICE / STATE | v POSTGRESQL PAYMENT SERVICE ----> STRIPE CONNECT
The auction service owns bid/price/state and is the single writer of auction outcomes, backed by PostgreSQL for durable state and Redis for the scheduled price-drop clock and fast bid reads. The payment service is a separate consumer of auction outcomes rather than a participant in the bidding path, so a slow or failing payment call can never block a bid from being accepted.
05 / Engineering Decisions
Decision / 01
Bids can arrive concurrently for the same auction, and price drops happen on a schedule independent of bid traffic.
Chosen: Database-level locking on the auction row for bid acceptance, with price-drop scheduling handled separately in Redis.
Why: Guarantees a single consistent winner determination per auction without building a custom consensus layer.
Trade-off: Slightly higher write latency on hot auctions in exchange for correctness guarantees that don't depend on application-level coordination.
Decision / 02
Payment status has to match the auction outcome even when Stripe webhooks arrive late, out of order, or twice.
Chosen: Idempotent, webhook-driven payment reconciliation keyed on the event ID rather than a polling or timer-based sync.
Why: Keeps payment state a downstream, verifiable fact rather than something the auction service has to assume.
Trade-off: Requires every payment-affecting endpoint to be written idempotently from the start, which is more upfront design work than a naive integration.
06 / Reliability / Failure Modes
Scenario
Two bids for the same auction land within the same millisecond.
Expected
Exactly one bid wins; the other is rejected with a clear reason.
Protection
Row-level locking on the auction during bid acceptance.
Recovery
Rejected bidder is notified immediately and can re-bid against the new price.
Scenario
Stripe webhook for a deposit or payout is delivered twice.
Expected
Payment state changes exactly once.
Protection
Webhook handler checks the event ID against processed events before applying any state change.
Recovery
Duplicate delivery is a no-op; no reconciliation step required.
Scenario
A sale falls through after a deposit was captured.
Expected
Deposit is refunded and auction state reflects the failed sale.
Protection
Explicit refund-handling path tied to the sale-failure event, not a manual finance process.
Recovery
Refund is issued automatically and logged for admin visibility.
08 / Technology
BACKEND
NestJS, TypeScript, Node.js
DATA
PostgreSQL, Redis
INFRASTRUCTURE
AWS, Docker
REAL-TIME
WebSockets
PAYMENTS
Stripe Connect
07 / Contribution
My Contribution
- —Designed and built the core timed reverse-auction engine — price-drop scheduling, bid validation, and winner determination logic
- —Built seller-facing workflows for auction listings and instant-offer requests with condition-verification steps
- —Developed buyer-facing bidding flows with real-time price updates
- —Architected the backend to support concurrent bidding without race conditions using database-level locking
- —Built the payment flow — deposit capture, payout calculation net of the platform fee, and refund handling
- —Implemented idempotent, webhook-driven payment reconciliation
- —Set up vehicle listing syndication to AutoTrader and MOTORS
09 / Outcome
- —Transaction-safe bidding and auction lifecycle running under concurrent load
- —Payment and payout flow that reconciles correctly under concurrent bidding
- —Vehicle listing syndication live on AutoTrader and MOTORS
10 / Lessons
- Payment correctness is a concurrency problem as much as an integration problem — the hard part was never calling Stripe, it was making sure auction state and payment state could never disagree.
- Separating the auction write-path from the payment write-path made both easier to reason about independently.
- Idempotency has to be designed in from the first webhook handler, not retrofitted once a duplicate-event bug shows up in production.