Your Shopify store as a Tamagotchi, living on a $30 M5StickC Plus2 on your desk.
- An order comes in: the pet dances, coins rain down, a chiptune cha-ching plays, the daily total updates.
- No orders for 3 hours: it falls asleep.
- A product is nearly out of stock: it gets hungry and tells you what to restock.
- Unshipped orders pile up: it gets buried in little pixel boxes.
- Store health drops below 50: it gets sick, thermometer and all.
- Quiet spell: the screen dims to save power, then wakes on the next order.
Total cost to run: $0/month. Everything fits in the free tiers of Cloudflare Workers and HiveMQ Cloud. Your Shopify token never touches the device.
Captured from the device's actual framebuffer:
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
Shopify webhooks ──> Cloudflare Worker (free) ──> MQTT over TLS ──> M5StickC Plus2
orders/create verifies HMAC HiveMQ Cloud the pet
fulfillments/create polls Admin API (free tier)
every 5 min (cron)
The Worker is the only thing that holds your Shopify Admin token. The device only knows an MQTT username and password scoped to your topic prefix. Live events (orders, fulfillments) are pushed instantly; a cron publishes a retained state snapshot (revenue, order count, unshipped orders, low stock, health) every 5 minutes so the pet has a correct picture even after a reboot.
- M5StickC Plus2 (the Plus1 also works; the original StickC screen is smaller but works too)
- A Shopify store with admin access
- Free accounts: Cloudflare and HiveMQ Cloud
- PlatformIO in VS Code to flash the firmware
- Create a free Serverless cluster at console.hivemq.cloud.
- Note the cluster URL, something like
abc123.s1.eu.hivemq.cloud(port 8883). - Under Access Management, create credentials with publish and subscribe permission. If your plan lets you restrict topics, limit them to
shopagotchi/#.
- In your Shopify admin: Settings > Apps and sales channels > Develop apps > Create an app. Name it "Shopagotchi".
- Configure Admin API scopes:
read_orders,read_products,read_inventory,read_fulfillments. - Install the app, then copy two values from API credentials:
- the Admin API access token (
shpat_...) - the API secret key (this signs webhooks)
- the Admin API access token (
Custom apps you create in your own admin have full access to your own order data, so there is no app review involved.
cd relay
npm installEdit wrangler.toml: set SHOPIFY_SHOP, MQTT_HOST, MQTT_USERNAME, and pick a TOPIC_PREFIX (any string, e.g. shopagotchi/mystore).
npx wrangler login
npx wrangler secret put SHOPIFY_ADMIN_TOKEN # shpat_...
npx wrangler secret put SHOPIFY_API_SECRET # API secret key
npx wrangler secret put MQTT_PASSWORD # HiveMQ credential password
npx wrangler secret put RELAY_TOKEN # any long random string
npx wrangler deployNote the deployed URL (like https://shopagotchi-relay.<you>.workers.dev), then register the webhooks and push the first state snapshot:
export RELAY=https://shopagotchi-relay.<you>.workers.dev
export TOKEN="the RELAY_TOKEN you set"
curl -X POST $RELAY/setup -H "Authorization: Bearer $TOKEN" # register webhooks
curl -X POST $RELAY/state -H "Authorization: Bearer $TOKEN" # publish state now
curl -X POST $RELAY/test -H "Authorization: Bearer $TOKEN" # fake a $42 order- Open
firmware/in VS Code with PlatformIO and plug in the stick over USB-C. (If the serial port does not appear, install the CH9102 driver.) - Click Upload (or
pio run -t upload). - On first boot the stick opens a WiFi access point. On your phone, join
Shopagotchi-Setup(passwordgotchi123), openhttp://192.168.4.1, and enter your WiFi plus the MQTT host, port8883, username, password, and the same topic prefix you set inwrangler.toml. - The pet appears. Run the
/testcurl above and watch it dance.
| It looks like | It means |
|---|---|
| Dancing in coin rain | An order just came in |
| Asleep with zzz | No orders for 3 hours |
| Chomping, "Feed me: ..." | A tracked variant is at low stock |
| Buried under boxes | 5+ open orders are unshipped |
| Pale with a thermometer | Health score below 50 |
Buttons: click A to cycle stats pages (today / fulfillment / network), hold A for 2.5s to fire a demo order, click B to mute, hold B for 5s to factory reset. Hold A while powering on to reopen the setup portal.
Energy saver: the display dims after 10 minutes without an order, shipment, or button press, and pops back to full brightness on the next one. Tune DIM_AFTER_MIN, BRIGHTNESS_ACTIVE, and BRIGHTNESS_DIM in firmware/src/config.h.
By default the relay computes a structural health score from unshipped orders and low stock. But the fun part: anything can POST a real score to the relay, and the pet gets sick when your store actually has problems.
curl -X POST $RELAY/health \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"score": 38, "note": "3 critical issues found"}'Wire it to uptime checks, Lighthouse runs, or a store audit tool. I feed mine from Conversion Guard (the store audit app I build), so when the pet catches a fever it means my store actually needs attention, and fixing the findings literally heals it. Send {"clear": true} to drop the override and fall back to the built-in structural score.
Thresholds (sleepy delay, sick threshold, buried threshold, celebration length) are in firmware/src/config.h. The pet itself is ASCII art in firmware/src/pet_frames.h, so you can redesign your gotchi in a text editor. The low stock threshold is LOW_STOCK_THRESHOLD in wrangler.toml.
- Cloudflare Workers free: 100k requests/day. The cron uses 288/day; the rest is your webhook volume. TCP sockets and cron triggers are included.
- HiveMQ Cloud free: 100 connections, 10 GB/month traffic. This project uses 1 device connection plus short-lived relay connections, and a few KB per message.
- If your store does more than ~700 orders a day, first of all congratulations, and second, everything still fits.
The firmware dumps its framebuffer over USB when it receives the letter s on serial. tools/screenshot.py automates it:
pip install pyserial pillow
python3 tools/screenshot.py -o shot.png # -n 4 grabs a burst of framesClose the serial monitor first (only one program can hold the port). The pet freezes for the ~10 seconds a dump takes.
The same serial console can stage any state without touching your store, which is how the screenshots above were taken. Type a letter in the serial monitor:
| Key | Effect |
|---|---|
s |
dump a screenshot |
o / p |
fake order / fake shipment |
k / h |
make it sick / heal it |
l / b / z / d |
low stock / buried / sleepy / big-day demo states |
f / g |
freeze live MQTT while staging / go live again |
0 |
reset to a clean idle |
- The device connects with TLS but skips certificate chain validation (
setInsecure()) to keep a desk toy zero-fuss. Pin the ISRG Root X1 CA innet.cppif you want strict TLS. - The retained state snapshot lags up to 5 minutes; the device applies live order events on top and reconciles when the snapshot catches up.
- Today's revenue is capped at 300 orders/day of pagination in the relay; live events keep counting past that.
- Stretch goal: wire a micro servo to the Grove port (G32/G33) and have it strike a real brass desk bell on every order. The event topic already carries everything you need.
MIT. Build one, remix the pet, make it weirder.





