Order numbers in, nothing counted by hand.
A Thai e-commerce brand ran a two-week top spender campaign: fans buy the campaign product, the biggest spenders win prizes. Their store could take the orders but could not run the race. We built the platform that verified every order, kept the score, and gave the client one panel to run it all.
The problem
The client sells through a storefront platform that is good at taking orders and nothing else. Their fan campaign needed more: fans submit order numbers to prove their purchases, verified spend accumulates on a leaderboard, and the top spenders win experience prizes at the close. Run by hand, that means checking every claimed order against store records, all campaign long, and hoping nobody claims an order that is not theirs.
The brief: registration with proper consent, order verification fans can trust, a leaderboard the client can act on, and an admin panel for everything from the schedule to the exports. All of it bilingual, Thai and English, and all of it live before a fixed campaign start date about two weeks out.
Decisions and trade-offs
We synced the orders instead of asking for them one by one. The first design verified each submission with a live call to the store's API. It worked, but the client could not see orders before anyone claimed them, and every check spent scarce API budget. We pivoted to a local order ledger, kept fresh by a five-minute sync and a real-time webhook, with the live call kept only as a fallback for very fresh orders.
The store's webhook signatures failed 98% of the time. The storefront platform signed its notifications one way and sent them another, so our receiver rejected 673 real deliveries before we cracked the encoding quirk. Because every rejection was logged with evidence, the bug was eventually solvable, and after the fix the failures went to zero and stayed there. Because the sync ran as a backstop, not a single order was lost while the bug lived.
A sync that refuses to pretend it succeeded. Our first sync advanced its bookmark even when part of a run failed, which silently skipped orders. We replaced it with a cursor that only moves after a fully clean run. The lesson was earned: one malformed order date from the store once silenced the whole sync for days, and we found it only when the client asked where a real order was.
First claim wins, and we say so. The store never tells us who bought an order, so the platform cannot check that a submitted order number belongs to the fan submitting it. Rather than fake certainty, the design states the rule: the first valid claim on an order wins, enforced by the database, backed by rate limits and the campaign terms. An honest constraint stated up front beats security theater we could not deliver.
An unscheduled campaign is a closed campaign. Early placeholder dates once let test orders leak into the real ledger. So the shipped system fails closed: until real dates are set, nothing is ingested, the site reads as closed, and the admin panel refuses to activate.
The leaderboard is hard to fake, even for us. Admins cannot type a score. When real operations needed an escape hatch for edge cases, we added a manual path where amounts are always derived from verified purchase counts and every action is logged with the acting admin's name. It was used four times in the whole campaign.
Measured results
Measured 2026-08-04 on the production system, mid-campaign, so the totals were still climbing. Figures rounded. The client stays anonymous by agreement; the platform and its numbers are real.
What we would do differently
Verify third-party signatures against a captured real delivery, not the documentation. The webhook scheme was confirmed on paper and still rejected nearly every real notification for days, while the sync backstop quietly hid the damage. The log-everything receiver that eventually cracked it should have come first, go-live second.
The single-campaign shape will not stretch. One campaign, its product and price set in code: exactly what this client needed, live in sixteen days. But a second campaign today would mean careful hand-run SQL. Campaign creation in the admin panel is the first thing we would build next.
Bilingual copy became a system only after it drifted. Thai and English strings now live in typed dictionaries the build enforces, with a glossary that keeps register, submit and claim as three distinct verbs. Next time that governance ships on day one, not after the first inconsistency.
Next
The other case study on file is the site you are reading right now. Read how this site was built →