TipFax - print your StreamElements tips on your receipt printer!

code hardware

Preface

If you have been around in the streaming space, you might know about tipping your favorite streamer using something like StreamElements tips. What if you could not only see them in your dashboard, but also print them out physically? Pointless? Maybe, but in a fully digital world I love to manifest something physical once in a while. That's where tipfax comes in - and it is easy to build yourself!

What are SE Tips?

StreamElements tips is a simple tipping product, kind of similar to StreamLabs donations or Ko-Fi. The cool thing about them is the StreamElements realtime API "Astro" allows you to capture these events, and do interesting things with them, such as printing them out using an off the shelf receipt printer.

SE Tipping page

Getting ready

First, we need to set up SE Tips in the SE dashboard, as well as get our JWT access token. It can be found in your channel settings. If you don't want to dox yourself to the world when people pay you, either:

  • Setup a business PayPal account
  • Enable SE.Pay if you are eligible

Once all of this is done, it's time to look at the hardware.

The printer

I use a generic Epson TM-T20II, which can often be found on Amazon and other stores. It has various connection options, but for the example we will be using the simple one, which is just as a USB device.

To run it 24/7, I hooked it up to a Raspberry Pi 4, but any other device should work, however I do recommend having a dedicated mini computer like the Pi for this.

The software

Finally, we need software that will actually listen for tip events for your channel, and translate them into something the printer understands and print the tips. Luckily for you, I already implemented this and the code is open source on Github. Let's take a closer look at how it works.

Tipfax architecture

The code

We need a couple of libraries to connect to everything

github.com/gorilla/websocket for connecting to the Astro WebSocket API

github.com/securityguy/escpos for interfacing with the receipt printer ESC/POS protocol (the industry standard for receipt printers and other POS hardware)

There are multiple examples of how to connect to Astro in the official SE docs.

A minimal example would look something like this:

Go
type Astro struct {
	cfg     *config.Config
	conn    *websocket.Conn
	printer *escpos.Escpos
}

func NewAstro(cfg *config.Config, printer *escpos.Escpos) *Astro {
	return &Astro{cfg: cfg, printer: printer}
}

func (a *Astro) Connect() error {
	u := url.URL{Scheme: "wss", Host: "astro.streamelements.com", Path: "/"}
	log.Printf("Connecting to %s", u.String())

	conn, _, err := websocket.DefaultDialer.Dial(u.String(), nil)
	if err != nil {
		log.Fatal("Error connecting:", err)
	}
	log.Println("Connected to Astro")

	a.conn = conn

	return nil
}

When we want to actually print a tip on the printer, we can do something like this with some basic formatting:

Go
if amt, ok := donation["amount"].(float64); ok {
			amount = fmt.Sprintf("%.2f", amt)
		}
		if curr, ok := donation["currency"].(string); ok {
			currency = curr
		}

		if msg, ok := donation["message"].(string); ok {
			message = msg
		}

		status := "unknown"
		provider := "unknown"
		if statusVal, ok := data["status"].(string); ok {
			status = statusVal
		}
		if providerVal, ok := data["provider"].(string); ok {
			provider = providerVal
		}

		log.Printf("💰 Tip from %s: %s %s (via %s)", username, amount, currency, provider)
		log.Printf("📊 Status: %s", status)
		if message != "" {
			log.Printf("💬 Message: %s", message)
		}

		// Print to thermal printer if available
		if a.printer != nil {
			a.printer.Write(fmt.Sprintf("Tip from %s: %s %s", username, amount, currency))
			a.printer.LineFeed()
			a.printer.Write(fmt.Sprintf("Status: %s", status))
			a.printer.LineFeed()
			if message != "" {
				a.printer.Write(fmt.Sprintf("Message: %s", message))
				a.printer.LineFeed()
			}
			a.printer.PrintAndCut()
		}
	} else {
		log.Println("Error: Could not find donation data in tip message")
		log.Printf("Raw data: %+v", data)
	}

Install the software, set the device path to your USB device, and you are ready to roll!

One note about Linux: you will most likely need to update your permissions. You might have to create an entry in /etc/udev/rules.d/99-escpos.rules with the following set:

SUBSYSTEM=="usb", ATTRS{idVendor}=="04b2", ATTRS{idProduct}=="0202", MODE="0664", GROUP="dialout"

You can get the product ID and the vendor ID using lsusb .

You should also add the user to the dialout group:

sudo usermod -a -G dialout pi && sudo usermod -a -G dialout root

If it still doesn't work, do

sudo chmod +777 /dev/usb/lp0

Reboot and try again.

These instructions are heavily inspired by this post by Andrew, thank you!

TipFax

TipFax is my simple implementation of the bridge between Astro and a generic Epson printer, that is designed to run on something like a Raspberry Pi, but can run technically on anything. Once you clone the repository, you will find there is a convenient install script, which will setup the systemd service for you on Linux (useful so you don't have to manually start and manage the service on reboots!).

Make sure you set your StreamElements JWT and device path, and you are ready to go!

Example of tipfax printing on receipt printer.