Hue Lights
Change your Philips Hue lights when you receive a tip.
Hue Lights Integration
React to tips in real time by flashing and changing the color of your Philips Hue lights. Tip amounts map to colors: green for small tips, blue for medium, purple for large.
This integration uses the Philips Hue local bridge API — no cloud account required. Your Hue Bridge must be on the same local network as the machine running this script. This does not work on remote servers unless you expose your bridge (not recommended).
Prerequisites
- Philips Hue Bridge (v2 square bridge required for local API)
- A Tizemint API token (
tzmnt_...) from Settings → API Tokens - Your lights' group or individual bulb IDs
- Node.js 18+
Step 1: Find Your Hue Bridge IP
# Option 1: Check your router's connected devices list
# Option 2: Use the Hue discovery endpoint
curl https://discovery.meethue.com/
# Returns: [{"id":"...","internalipaddress":"192.168.1.x"}]Note the internalipaddress — you'll use this as HUE_BRIDGE_IP.
Step 2: Create a Hue API User
Press the physical button on your Hue Bridge, then within 30 seconds run:
curl -X POST http://YOUR_BRIDGE_IP/api \
-H "Content-Type: application/json" \
-d '{"devicetype":"tizemint#tipbot"}'
# Returns: [{"success":{"username":"abc123def456..."}}]Save the returned username as your HUE_USERNAME.
Step 3: Find Your Light or Group IDs
# List all lights
curl http://YOUR_BRIDGE_IP/api/YOUR_HUE_USERNAME/lights
# List all groups (rooms/zones)
curl http://YOUR_BRIDGE_IP/api/YOUR_HUE_USERNAME/groupsNote the numeric IDs for the lights or groups you want to control.
Step 4: Install Dependencies
npm init -y
npm install eventsourceStep 5: The Full Script
// tizemint-hue.js
import EventSource from "eventsource";
// --- Configuration ---
const TIZEMINT_TOKEN = process.env.TIZEMINT_TOKEN; // tzmnt_...
const HUE_BRIDGE_IP = process.env.HUE_BRIDGE_IP; // e.g. 192.168.1.50
const HUE_USERNAME = process.env.HUE_USERNAME; // From Step 2
// Comma-separated light IDs to control, e.g. "1,2,3"
const LIGHT_IDS = (process.env.HUE_LIGHT_IDS || "1").split(",").map(Number);
// Or use a group: set HUE_GROUP_ID=1 to control an entire room
const HUE_GROUP_ID = process.env.HUE_GROUP_ID ? Number(process.env.HUE_GROUP_ID) : null;
if (!TIZEMINT_TOKEN || !HUE_BRIDGE_IP || !HUE_USERNAME) {
console.error("Missing required env vars: TIZEMINT_TOKEN, HUE_BRIDGE_IP, HUE_USERNAME");
process.exit(1);
}
// --- Color mapping by tip amount (cents) ---
function getTipColor(amountCents) {
const dollars = amountCents / 100;
if (dollars >= 20) {
// Large tip: purple
return { hue: 48000, sat: 254, bri: 254 };
} else if (dollars >= 5) {
// Medium tip: blue
return { hue: 46920, sat: 254, bri: 200 };
} else {
// Small tip: green
return { hue: 25500, sat: 254, bri: 180 };
}
}
// --- Hue API helpers ---
const hueBase = `http://${HUE_BRIDGE_IP}/api/${HUE_USERNAME}`;
async function setLightState(lightId, state) {
const res = await fetch(`${hueBase}/lights/${lightId}/state`, {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(state),
});
return res.json();
}
async function setGroupState(groupId, state) {
const res = await fetch(`${hueBase}/groups/${groupId}/action`, {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(state),
});
return res.json();
}
async function setState(state) {
if (HUE_GROUP_ID !== null) {
return setGroupState(HUE_GROUP_ID, state);
}
return Promise.all(LIGHT_IDS.map((id) => setLightState(id, state)));
}
// --- Flash effect ---
// Saves current state, flashes the tip color, then restores
async function flashTipColor(amountCents) {
const color = getTipColor(amountCents);
// Flash on: set color, alert once
await setState({
on: true,
...color,
transitiontime: 2, // 0.2 seconds
alert: "select", // Single flash
});
// Hold the color for 3 seconds
await new Promise((r) => setTimeout(r, 3000));
// Restore to a neutral warm white
await setState({
on: true,
hue: 8000,
sat: 140,
bri: 200,
transitiontime: 10, // 1 second fade
});
}
// --- SSE connection ---
function connect() {
const url = `https://tizemint.com/api/tip-events/stream?token=${TIZEMINT_TOKEN}`;
const es = new EventSource(url);
es.onopen = () => {
console.log("[hue-bridge] Connected to Tizemint event stream");
};
es.onmessage = async (event) => {
let data;
try {
data = JSON.parse(event.data);
} catch {
return; // Ignore keep-alive pings
}
if (data.type === "tip:received") {
const dollars = (data.amount / 100).toFixed(2);
console.log(`[hue-bridge] Tip received: $${dollars} from ${data.displayName}`);
await flashTipColor(data.amount);
}
if (data.type === "tip:goal-completed") {
console.log(`[hue-bridge] Goal completed! Running celebration flash...`);
// Cycle through colors for goal completion
await celebrationFlash();
}
};
es.onerror = () => {
console.log("[hue-bridge] Connection lost, reconnecting in 5s...");
es.close();
setTimeout(connect, 5000);
};
}
// --- Celebration: cycle red → green → blue on goal complete ---
async function celebrationFlash() {
const colors = [
{ hue: 0, sat: 254, bri: 254 }, // Red
{ hue: 25500, sat: 254, bri: 254 }, // Green
{ hue: 46920, sat: 254, bri: 254 }, // Blue
{ hue: 48000, sat: 254, bri: 254 }, // Purple
];
for (const color of colors) {
await setState({ on: true, ...color, transitiontime: 3 });
await new Promise((r) => setTimeout(r, 400));
}
// Return to warm white
await setState({ on: true, hue: 8000, sat: 140, bri: 200, transitiontime: 10 });
}
connect();Step 6: Run It
TIZEMINT_TOKEN=tzmnt_your_token \
HUE_BRIDGE_IP=192.168.1.50 \
HUE_USERNAME=abc123def456 \
HUE_LIGHT_IDS=1,2 \
node tizemint-hue.jsTo control an entire room instead of individual bulbs:
# Find your group ID first: curl http://BRIDGE_IP/api/USERNAME/groups
TIZEMINT_TOKEN=tzmnt_your_token \
HUE_BRIDGE_IP=192.168.1.50 \
HUE_USERNAME=abc123def456 \
HUE_GROUP_ID=1 \
node tizemint-hue.jsColor Reference
| Tip Amount | Color | Hue Value |
|---|---|---|
| Under $5 | Green | 25500 |
| $5 – $19.99 | Blue | 46920 |
| $20 and above | Purple | 48000 |
| Goal completed | Cycle | — |
Hue values are on a 0–65535 scale. Adjust the color mapping function to match your preferences.
Running as a Background Service
npm install -g pm2
pm2 start tizemint-hue.js \
--name tizemint-hue \
--env TIZEMINT_TOKEN=tzmnt_... \
--env HUE_BRIDGE_IP=192.168.1.50 \
--env HUE_USERNAME=abc123 \
--env HUE_LIGHT_IDS=1,2
pm2 save
pm2 startupTroubleshooting
401 errors from the Hue bridge
Your HUE_USERNAME may be invalid or expired. Repeat Step 2 to create a new user.
Lights don't change color
Ensure the lights are Hue Color bulbs — Hue White bulbs only support brightness, not color. Check that your light IDs are correct with the /lights endpoint.
Script connects but nothing happens when I test with a tip Confirm the Tizemint token is valid by checking Settings → API Tokens. Also verify the bridge IP hasn't changed (DHCP leases can shift).
The script is on a server, not my local machine The Hue local API is only accessible on your LAN. You'd need to expose the bridge via a tunnel (not recommended for security reasons) or run the script on a device on the same network as your bridge — a Raspberry Pi or your stream PC works well.