They say money doesn’t grow on trees, but what about on treadmills?
For the 2025 NYC marathon weekend, Shopify partnered with Endorphins Running to build a gamified treadmill that rewards runners with in-store credit. We paired the treadmill with a POS UI extension to make redeeming credit seamless and secure.
This quirky project was ambitious—let’s explain it from the beginning.
The opportunity
With 25,000 global members, weekly runs in major cities, and highly anticipated product drops, Endorphins has built one of the world’s most engaged running communities. Known for their signature singlets and shoe collabs with New Balance, they’ve grown from a grassroots run club into a coveted lifestyle brand and training app.
As NYC Marathon weekend approached, Endorphins wanted to go beyond regular retail and create a new kind of customer experience. Their passionate community was on our radar, so we reached out asking to collab. Then we started dreaming.
The experience
We helped Endorphins build a gamified treadmill that rewards runners for keeping a steady pace—a nod to what running a real marathon is like.
Participants who opted-in selected their preferred pace, then had 30 seconds to try to run at that speed. The longer they stayed in the “zone” (more on this later), the more in-store credit they earned. We used a manual treadmill for this build, which meant participants had to actively control their own pace and respond to our pacing feedback in real-time.
When their session was over, runners received a personalized receipt with a unique barcode. They could scan this at checkout to apply their earned discount towards an in-store purchase at the Endorphins x Shopify marathon popup. Redeeming this code was seamless and secure because of a custom POS UI Extension we built for Shopify POS. The extension automatically applies the runner’s discount and creates a new customer record that syncs metadata from their treadmill session.

It was important to Endorphins that the experience provided an even playing field for all participants: running faster didn’t necessarily yield the best results. It was really about finding your own rhythm and being consistent.
The hardware
We initially prototyped this on a regular treadmill in a hotel gym using the Runn sensor. This sensor depends on small reflective 3M strips. As the strips go around the belt, the sensor detects their speed and sends Bluetooth FTMS data to a Python script that listened to events. This is a common protocol for smart fitness equipment.
This worked, but we came to a realization: the data we received was uniform and had no awareness of somebody actually being present on the treadmill. Regular treadmills automatically maintain a given pace; we wanted to reward runners for finding their pace and sticking to it.
To solve for this, we built a second prototype using a curved manual treadmill, the TrueForm Trainer. These treadmills use a series of rollers underneath the belt, arranged in an arc. They have no motor and are entirely powered by the runner pushing the belt over the rollers. This allows it to be immediately responsive to the runner changing pace—which is exactly what we needed.

This particular model also has a display capable of sending FTMS messages, so our server was able to receive the same messages we got from the Runn sensor. It also gave us the option of seamlessly switching sensors if we ever needed to.
Our other hardware included:
- POS hardware: a terminal and iPad running Shopify POS
- Receipt printer: we used the Epson TM-m30III to print in-store credit discount codes
- Displays: three 4K TVs showing real-time data for the audience
- Server: a single MacBook Pro powering everything
- iPads: one for our brand ambassador to manage the queue, and another as a secondary display attached to the treadmill for the runner to monitor their performance
The software
Our build included three major software components: the treadmill server, the local web server, and the POS UI extension. Cursor helped us implement the interfaces between these different systems.

Treadmill server
The whole experience was powered by a single MacBook Pro. 🤯 On this device, we had a Python script running indefinitely and paired to our treadmill, receiving FTMS messages and relaying them to our Socket.IO server. The Python script looks something like this.
Web app
The displays and control panel were both powered by a Remix app, running on the same MacBook Pro as the Python script. This made it really fast—we were sending and receiving all Socket.IO messages on a local network.
The Remix app did several things:
- It received Socket.IO events, processed them accordingly (like updating the current runner’s game state in the database), then emitted the processed event to all other clients listening.
- The displays received the events and rendered graphics accordingly, including the speedometer, time remaining, and runner metadata.
- The control panel controlled the game state, managing details like who is currently running, when to enter the warm-up phase, and printing the receipt.
Our Remix app used an Express server, with a series of WebSocket handlers that responded to the various events relevant to the experience. They looked like this:
Note that this receives the treadmill_data event relayed by the Python scripts from the treadmill, and then broadcasts that data to the displays and control panel. This is how all the different systems stay in sync.
The parent route managed all of the socket events and passed it down to multiple child routes through Outlet context. Here is a small example of how our displays looked:
The downstream displays would access it like this:
You might have noticed the checkEarned function above; this is the core logic for how we translated speed ➡️ pace ➡️ in-store credit earned. What we did was relatively simple: define a target pace and add a padding on either side to build the “zone.” For every second the user was in that zone, they accumulated in-store credit.
We had to account for the fact that FTMS messages from the treadmill are not sent on a fixed schedule. We normalized the intervals by comparing the last interval timestamp to the newest one as a percentage of a second:
In this case, the runner earned 1/30th of $75 for every second they were in the zone.
Next, the control panel controlled the flow of the game. Its core responsibility was to manage this state machine:

Once a runner was loaded from the database, the operator (the person in charge of the experience) would press a button to toggle the next state. This would send a WebSocket event with the type state_update, which the other clients received and responded to.
This was also an opportunity for the operator to modify or discard sessions at their discretion. The other important responsibility of the control panel was updating the user record in the database and creating the discount in the Shopify admin.
We added customer metafields with some of the session data—this is completely optional but it’s a nice way to persist some interesting customer data for building segments later.
This takes place in our Remix route’s action:
The last job of the control panel is to coordinate with the receipt printer. The goal was to print a receipt that encoded the user ID in the barcode, which could then be scanned in the POS UI extension to apply the unique discount code and customer data shown above.
We created a <Receipt/> component that took the finished user data as a parameter and rendered a receipt object using the Epson ePOS JavaScript SDK. This is specific to the printer we used, however the same concept would apply to most popular receipt printers. You could also omit this part and generate a digital document with a scannable QR code.
This is roughly how it worked with our setup:
The usePrinter hook is a customer hook we built that simply wraps the printer object in Context so we can access it and its status from anywhere in our Remix application without having to re-instantiate.
At a high level, it looked like this (again, very specific to the printer you use):
POS UI extension
The final piece of the puzzle: what do we do with the receipt?
We built a POS UI extension that was accessible through our custom “Pay with Pace” tile, which launches a barcode scanner.

When a barcode is detected on a receipt, the payload includes user ID, which we use to do the following:
- Fetch user from our database, particularly their email and discount code
- Add customer to cart
- Add discount to cart
This workflow has a few benefits. Not only is it quick and seamless for the retail associate to scan and apply the discount code, it also automatically creates or updates the customer’s record, enabling Endorphins to grow their community and better personalize their customer experience:
We intentionally decided to host this on our live production server as opposed to the local server powering the treadmill. This de-coupled the treadmill experience from the discount redemption. We didn’t want to block checkout if we had to debug the treadmill for some reason.
Unserious exploration in action
We constantly challenge ourselves to play with technology to see what’s possible, even if it seems too quirky, niche, or unshippable. We call it unserious exploration, and it’s how we keep ourselves curious and sharp. This creative collab with Endorphins definitely checked those boxes.
By combining a smart treadmill, a lightweight Remix application, and POS UI extensions, we created a super memorable retail experience that delighted runners and expanded Endorphins’ community: 63% of all participants were new customers!
The best part is that the platform is flexible enough that you could build new experiences and re-use a lot of the same patterns: different hardware, new game modes, other types of incentives, etc. Almost any in-store activity you can measure with a sensor or trigger can be translated into exclusive products, gifts with purchase, or discounts.
This project was for the runners, those in pursuit of mastery, and the builders like us who are willing to try some whacky shit.
If you’re interested in joining us on our mission to make commerce better for everyone, check out our careers page.
Niko Draca is a Staff Engineer on the Tech Flexes team.
