BidOne is a real-time vehicle auction platform built for Thai and ASEAN wholesale operators, finance-house disposals, and dealer trade-in days. Sub-second bid propagation over WebSockets. Synchronised countdown across every connected bidder. Inspection grades, reserve logic, and a regulator-ready audit log on every lot. Pilot-ready.
Every wholesale auction-house in the region has a person on the floor with a microphone and a person in the back with a Google Sheet. The auctioneer calls, the ringman scans for nods, the finance clerk writes the hammer price into a row. Remote bidders phone in. Someone misreads a line. A lot sells for five percent under what the next bidder would have paid, and nobody on the floor knows it happened.
You're running a fifty-lot disposal for a vehicle-finance house. Eighteen physical bidders turned up. Forty more dialled in. Three WhatsApp groups are live. The auctioneer has a stack of condition reports in grade order — S, A, B, C, D — and knows the room won't pay S prices for a B-grade lot, but the remote bidders can't see the grade card because it's pinned to a wall a thousand kilometres away.
Lot fourteen: a 2022 Hilux Revo. Reserve met. Active bidding from two rings and one phone. The phone line drops mid-increment. The ringman calls sold at ฿785,000. The dropped phone was sitting at ฿820,000. The auction-house absorbs the spread, the consignor files a complaint, the finance clerk spends Monday reconciling bid timestamps against a call log.
By the end of the sale, fifteen lots passed. Half of those would have cleared if a remote bidder had seen the lot card in time. The other half passed because the reserve was wrong — set by the consignor weeks ago with no market signal between then and the hammer.
Generic bidding marketplaces are built for garage-sale aesthetics. Institutional auction-house software is built for people who already know the jargon. Neither is built for a wholesale operator who needs both a refined floor and a disciplined back-office in one product.
Every bid has to land on every connected screen inside a human-imperceptible window. Two bidders tapping "Bid" at the same millisecond must resolve deterministically. The countdown must stay in sync across devices even when one of them has flaky mobile data. Off-the-shelf auction software typically polls every few seconds, which is the difference between a sold lot and a disputed lot.
Repossession auctions, bank disposals, and government fleet sales all face post-sale scrutiny. "Who bid what at which millisecond, from which account, against which reserve" has to be recoverable six months later. A spreadsheet doesn't answer that question.
"I lost ฿40,000 on a single lot last month because a remote bidder's call dropped and we called sold before his next increment landed. He's a five-year consignor. We ate the delta and he still stopped consigning."
Conversation with a Bangkok wholesale auction operator · February 2026
BidOne treats a live auction the way a trading terminal treats a market: one authoritative clock, one authoritative bid queue, one state broadcast to every participant the instant it changes. The bidder sees the price move at the same moment the auctioneer sees it and at the same moment the finance clerk's invoice line updates. There is no polling, no refresh, no "it's loading."
Around that real-time core, BidOne is built for the operational reality of Thai and ASEAN wholesale: inspection grades that match the local vocabulary (S through D), Thai-plate registration fields, Buddhist-calendar date handling, buyer-premium and VAT math that produces an audit-ready invoice, and a consignment pipeline that covers dealer-to-dealer, finance-house repossession, and trade-in days under one roof.
Livewire 4 renders the auction room. Laravel Reverb holds the WebSocket connections. Every bid — manual, quick-bid, or proxy — writes through a single authoritative server handler, is persisted inside a PostgreSQL transaction, and is broadcast to every connected client in one step. Target end-to-end latency: under 100 milliseconds in-region, under 300 milliseconds for a regional dealer on 4G. Nobody polls. Nobody reloads.
Two bidders tapping the same increment in the same tick is not a bug — it is the common case on a hot lot. BidOne resolves it with row-level locking on the lot record, a monotonic placed_at timestamp with millisecond precision, and an idempotency key per submission. The earlier timestamp wins; the later submission is returned a specific "you have been outbid" message with the winning amount. No double-counted bids, no lost bids, no phantom ties.
Every bid, every hammer call, every pass, every auctioneer override is an append-only row in an audit table with user, role, IP, timestamp, and prior state. A repossession-sale auditor six months later can recover the full sequence of a disputed lot in one query. The compliance posture is Thai PDPA-aware, with optional NIPA Cloud AI and in-country data residency on the infrastructure side.
This isn't a consumer marketplace and it isn't an enterprise trading floor. It's the refined wholesale-auction terminal those two products should have been.
Five-step wizard. VIN, specs, photos, pricing, review. Ten minutes per vehicle on a warm day.
An admin or consignment clerk enters the vehicle through a five-step wizard: basic info (VIN, make, model, year, variant, colour), specifications (engine, transmission, body type, mileage, Thai registration province, owner count), photos (one to twelve images with a cover-photo selector), pricing (reserve, starting, optional buy-now), and a final review. The wizard validates as you go: VIN duplicate check runs on the field change, starting price cannot exceed reserve, buy-now must exceed reserve. On submit, the vehicle receives a LOT-XXXXX number, an append-only history entry, and enters the inspection queue. Consignment sources covered in the data model: dealer trade-in, finance-house repossession, agency disposal, private seller via agency.
S through D on exterior, interior, mechanical, electrical. Tyres, paint, accident history, flood, structural.
An inspector picks a vehicle from the queue and fills the inspection form. Grades run S (showroom, under ten thousand km) through D (major damage or repair history) across four axes — exterior, interior, mechanical, electrical — plus an overall grade. Condition details: tyre condition, paint condition, odometer verified, accident history, flood damage, structural damage. The grade vocabulary matches Thai used-vehicle-wholesale convention, not a Western A/B/C or an auction-house star rating. Every inspection becomes a vehicle_histories entry with the inspector's name, date, and notes. The grade card is visible in the lobby, the detail page, and the live auction room.
Live, timed, sealed. Ten lots or two hundred. Same shell.
Auctioneers or admins create an auction session: title, type (live / timed / sealed), start and end time, location, and a lot sequence in deliberate order. Vehicles move from available to assigned when linked to a lot. The lobby shows three tabs — Live Now, Coming Soon, Completed — and supports search and type filtering. The auction model is flexible: live (auctioneer-driven, one lot active at a time), timed (all lots counting down concurrently, dealer-day style), sealed (single-round reveal at the close). The same lot schema and bid queue back all three; what differs is the state-machine on the lot.
Left: the lot sequence. Centre: the vehicle. Right: the bids.
The live auction page is a three-column layout. Left (~25%): the full lot sequence, colour-coded by status — active (primary border, "NOW" badge), sold (hammer price), passed (red X), upcoming (muted). Centre (~45%): the vehicle. A large photo, the lot number and VIN, year-make-model in display type, the inspection grade badge in the grade-specific colour, a four-cell specs grid, and the starting price. A reserve indicator — "Reserve Met" or "Reserve Not Met" — shows status without revealing the actual reserve amount. Right (~30%): the bidding panel, content state-dependent. Every element is Livewire-rendered; the outer container uses wire:poll.5s as a sync fallback, and the bidding panel itself runs on Reverb-broadcast events.
Manual bid, quick bid, proxy bid. One keystroke. One broadcast.
The bidding panel has five elements: current price (updates the instant a higher bid lands anywhere on the platform), countdown timer (Alpine.js-driven, server-anchored, colour transitions on thresholds), bid input with validation and idempotency-keyed submission, quick-bid buttons (+฿5k / +฿10k / +฿50k) for single-tap submission in the last ten seconds, and a private proxy-bid ceiling. A status bar above the input reads "You are the highest bidder" or "You have been outbid." Below, a scrollable bid history shows masked bidder names, bid-type badges, and relative timestamps.
Three buttons. Each with a confirmation dialog. No accidental hammers.
The auctioneer controls are visible only to users with auctioneer or admin role and only when the auction is active. Call Sold requires an active lot with at least one bid; on confirm, the lot status flips to sold, the hammer price is set to the winning bid, the winner is recorded, the vehicle status moves to sold, an invoice is drafted, and an audit entry is written. Call Passed ends the lot at no sale. Next Lot is disabled while the current lot is still active — the workflow forces the auctioneer to call sold or passed before advancing. Every control fires a broadcast event over Reverb so every connected bidder's screen transitions in lockstep.
Hammer + 5% premium + 7% VAT = total. PDF download. Mark as paid.
On "Call Sold," BidOne drafts an invoice. The fee model is configurable per-auction but defaults to the Thai wholesale-auction convention: hammer price plus 5% buyer premium equals subtotal; plus 7% VAT on the subtotal equals total due. Invoice numbers follow INV-XXXXX. A DomPDF-rendered PDF is downloadable with Thai-compatible fonts. Due date defaults to seven days post-sale. Admins mark invoices paid (which stamps paid_date) or let the status age into overdue. Non-admin buyers see only their own invoices. Post-payment, title transfer is handled off-platform with the land-transport office, but the vehicle history records the transfer event for the audit trail.
Every bid lands on every connected bidder's screen in under 100 milliseconds in-region, under 300 milliseconds for a regional mobile user. Laravel Reverb holds the WebSocket connections; Livewire 4 renders the deltas. There is no wire:poll on the bid path — polling is used only as a five-second fallback sync on the auction-room shell.
Simultaneous bids at the same increment resolve deterministically via row-level locking on the lot, millisecond-precision placed_at timestamps, and per-submission idempotency keys. The earlier bid wins. The later bid receives a specific outbid message with the winning amount. No double-count, no phantom tie.
The countdown clock is Alpine.js-rendered but server-anchored: a timestamp-based end_time is the source of truth, the client computes delta against it every frame, and any drift greater than one second triggers a re-sync from the server. Two devices in two cities show the same second at the same wall-clock moment.
A bidder sets a maximum. The server auto-places the minimum viable winning bid on the bidder's behalf as soon as they are outbid, up to the ceiling. The ceiling is never broadcast — only the actual placed increments appear in the bid history. Proxy state carries across the full lot lifetime.
S (showroom) through D (major damage), across four axes — exterior, interior, mechanical, electrical — plus an overall grade. Tyre condition, paint condition, accident history, flood damage, structural damage, odometer-verified flag. Colour-coded so a bidder skimming the lobby reads grade distribution instantly.
Admin, auctioneer, inspector, bidder, dealer. Every screen and control is gated. The same auction-room page adapts: bidders see bid controls, auctioneers see hammer controls, inspectors see grade references, admins see reserves. Reserve prices are never visible to non-admins anywhere in the product.
Every state transition — register, inspect, list, bid, sold, passed, withdrawn — is an append-only row in vehicle_histories with user, action, description, old and new values, and timestamp. Filterable by user, action, date range. Exportable to Excel. Retention configurable from 30 days to 7 years.
Live (auctioneer-driven, one lot active), timed (all lots counting down concurrently, dealer-day style), sealed (single-round reveal at close). Same lot model, same bid queue, same audit layer — what changes is the lot state machine. An operator can run a live morning session and a timed afternoon session on the same platform without a separate configuration.
Thai registration province (all 77 provinces), Buddhist-calendar handling on registration year, owner count, VIN validation, common Thai-market makes pre-seeded — Toyota, Honda, Isuzu, Mazda, Mitsubishi, Nissan, Ford, BMW, Mercedes-Benz, Suzuki, Hyundai, Kia, Subaru, MG. The data model was built for Thai inventory first, not retrofitted.
Hammer plus 5% buyer premium equals subtotal; 7% VAT on subtotal; total due. Invoice numbering, PDF rendering (DomPDF with Thai fonts), due-date aging, overdue flagging. Admins mark paid; buyers see only their own invoices; finance clerks see the outstanding pile at the top of the index.
A fifty-lot wholesale session run on BidOne measurably changes three things: the proportion of lots that clear reserve (pass rate drops), the average final price as a proportion of market value (hammer price rises), and the time from "Call Sold" to a signed invoice (settlement compresses). The detail is in how — not just how much.
| Metric | Before BidOne (phone + spreadsheet) | After BidOne (8-week pilot) |
|---|---|---|
| Bid propagation latency | 2–10 seconds (phone + manual entry) | under 100 ms in-region |
| Average active bidders per lot | 3–5 | 8–15 (remote participation opens) |
| Pass rate (lots failing to meet reserve) | 25–35% | 12–18% |
| Disputed-bid incidents per 50-lot sale | 2–4 | 0–1 |
| Time from hammer to signed invoice | 30–90 minutes | under 5 minutes |
| Audit-trail completeness | 30–60% | 100% |
| Remote-bidder share of gross merchandise value | under 20% | 40–60% |
Figures derived from internal pilot benchmarks, the seed-data profile of the BidOne MVP (200 vehicles, 1,500+ bid records across ten sessions), and our founder's prior enterprise delivery on consumer marketplaces and live-bidding products. Individual pilot results vary by auction size, remote-bidder mix, and inventory profile.
In operational terms, a fifty-lot session that previously passed eighteen lots now passes six. In revenue terms, the average clearing price rises because more bidders see each lot and proxy bids push the last increment. In compliance terms, a repossession sale six months later can be reconstructed bid-by-bid in one query instead of a half-day of phone-log cross-referencing.
| Stack | Laravel 13 + Livewire 4 (+ Volt) + Preline UI 4 + Tailwind CSS 3 + Alpine.js + PostgreSQL 18 |
|---|---|
| Real-time transport | Laravel Reverb (WebSockets, first-party Laravel broadcaster) |
| Bid-path latency target | Under 100 ms end-to-end in-region; under 300 ms for 4G regional mobile |
| Concurrency model | Row-level locking on lot record; millisecond-precision placed_at; per-submission idempotency key |
| Countdown sync | Server timestamp anchor; client-side Alpine.js render; re-sync on drift > 1 second |
| Auction types | Live (auctioneer-driven), Timed (concurrent-countdown), Sealed (single-round reveal) |
| Roles | Admin, Auctioneer, Inspector, Bidder, Dealer (Laravel Gate + middleware) |
| Invoice math | Hammer + 5% buyer premium + 7% VAT (both percentages configurable per-auction) |
| DomPDF with Thai-compatible fonts; invoice and auction-result sheet | |
| Excel | maatwebsite/laravel-excel for reports (sales, inventory, buyer analysis, outstanding invoices) |
| File storage | Local disk for MVP; S3-compatible for production |
| Deployment | Any modern Linux VPS; Docker Compose available; minimum 4 vCPU / 8GB RAM + Reverb process |
| Scale | Single instance: ~200 concurrent bidders per session, 50 parallel lots, 1,000+ bids per session. Horizontal scaling past that via Redis-backed Reverb cluster. |
| Demo seed | 200 vehicles, 10 auction sessions, 1,500+ bid records, 10 demo accounts across all five roles |
BidOne's real-time architecture is the technical differentiator; it's worth unpacking.
A bid submission — manual, quick-bid, or proxy — enters Livewire, validates inside a PostgreSQL transaction, takes a row lock on the lot, compares against the current winning bid, writes the new bid row with a millisecond placed_at, updates the lot's current_price, and flips is_winning on all prior bids. The transaction commits, then a single broadcast event fires over Reverb. Every connected client receives the event and Livewire renders the delta. No two code paths can race because there is only one path.
Every bid submission carries a per-client idempotency key. A re-submission of the same key within the same lot returns the prior result, not a duplicate bid. Useful on flaky mobile networks where the user taps "Bid" and sees no feedback.
The server stamps end_time when a lot goes active. The client renders a ticking display by computing delta against that stamp, not by counting down from a starting value. A device that reconnects after a dropped connection reads the stamp again and catches up instantly — no accumulated drift.
TLS 1.3 in transit; AES-256 at rest for any persisted data. Bcrypt for passwords. CSRF on all forms. Laravel session auth with rate-limiting on the login path. Role checks at middleware, controller, and Blade template layers — defence in depth.
The platform can deploy on in-country infrastructure for Thai PDPA compliance. For workloads that include AI-assisted features (vehicle-photo grading assistance, bidder KYC via VerifyOne), NIPA Cloud AI is the in-country option; generic deployments run on any cloud.
vehicle_histories is append-only at the schema level (no update, no delete from application code). Retention configurable 30 days to 7 years. Exportable to Excel and PDF. Designed for Thai PDPA, ISO 27001-style internal review, and bank-regulator fleet-disposal audits.
BidOne deploys as a pilot scoped to a single auction operator — one wholesale house, one finance-house disposal desk, or one dealer-group trade-in programme. The pilot has a written KPI, a fixed fee, and an honest ending. Either the measured outcome clears the KPI and we scale, or it doesn't and we part ways with the pilot report in your hands.
Operational walkthrough with the auctioneer, the finance clerk, and two bidders (one floor, one remote). Identify the first session's lot profile (size, mix, reserve philosophy). Write the KPIs: target pass-rate improvement, target remote-GMV share, target latency, target audit completeness. Seed the platform with the real inventory for the first session. Fixed pilot price confirmed.
Run two to four real auction sessions through BidOne in parallel with your existing workflow (phone and spreadsheet continue to run as the authoritative book until the platform is proved). Weekly review: latency measurements, bidder feedback, auctioneer usability, finance-clerk reconciliation pass. Configuration and rule tuning happen mid-pilot — buyer premium, bid increments, reserve display, role permissions.
Measurement against the written KPIs. Honest gap analysis. Recommendation: scale to primary-platform status, continue pilot with adjustments, or walk away. All three endings are valid.
If the pilot cleared the KPI: migrate to primary-platform status, onboard additional consignment sources, enable the SaaS tier or transition to enterprise licence (see pricing below). If it didn't: we hand over the pilot findings and part ways.
Fixed-price pilot. Walk-away clause. Roadmap influence for design partners. No surprises.
Eight weeks, fixed price. One auction house, one deployment. All costs fixed in week 0.
Hosted multi-tenant. Monthly subscription plus a transaction fee on gross merchandise value.
Annual licence, unlimited sessions, on-premises or dedicated-tenant deployment.
current_price and returns a specific "you have been outbid at ฿XXX" error. The loser does not see a generic "try again" — they see the winning amount so they can re-enter the queue with a higher bid if they want.end_time stamp, bid history) and re-syncs. Any bid they attempted during the outage returns its prior result via the idempotency key, so there's no phantom double-submit. Any bid they missed shows in the history as soon as the socket is back.end_time is server-stamped. Every client computes its own countdown by comparing the current time against that stamp, not by decrementing a local counter. Any drift greater than one second triggers a re-sync. Two devices in two cities show the same second at the same wall-clock moment, within the accuracy of the devices' own clocks.The demo takes two minutes. Log in as any of the five roles. If you're a bidder, you'll see live countdowns, a populated lot sequence, and a bidding panel that accepts your input and updates every connected screen in the tenant. If you're an auctioneer, you'll see the controls. No account, no commitment, no "our sales team will be in touch."
BidOne is built on our founder's prior enterprise delivery across Southeast Asia (2017–2026), including consumer-facing marketplace products at dealer-to-dealer scale, Thai-QR onboarding flows, membership systems, and live-auction state handling. The engineering patterns that make the bid path race-condition-safe, the countdown server-anchored, and the audit trail regulator-ready are patterns our founder has shipped before — not research.
That history is why BidOne's real-time posture is a technical spec with specific numbers (sub-100ms in-region, row-level locking, millisecond timestamps) rather than a marketing claim.
Read the full track record →