Introducing the Go SDK: End-to-End Encryption and Workers That Don't Fall Over
PushFlo now has an official Go SDK — built for OS-level workers and daemons, not browsers. It ships two things the JS SDK doesn't: an end-to-end encrypted envelope and connection handling designed to survive weeks of uptime.
PushFlo has an official Go SDK: github.com/PushFlo/pushflo-go.
go get github.com/PushFlo/pushflo-go
Before you ask — no, it's not a port of the TypeScript SDK. It's a different tool for a different job, and that difference is the interesting part.
Different audience, different SDK
The TypeScript SDK lives where JavaScript lives: browser tabs, React components, serverless functions. Its world is short-lived — a tab that's open for twenty minutes, a Lambda that runs for two hundred milliseconds.
The Go SDK lives at the OS level. Its world is the background worker, the deploy agent, the monitoring daemon, the job executor — a process started by systemd or a container runtime that's expected to hold a connection for weeks. Nobody is watching it. Nobody presses F5 when it wedges.
That changes what the SDK has to be good at. A browser client that loses its connection gets rescued by the next page load. A daemon that loses its connection at 3am on a Saturday either rescues itself or delivers nothing until someone notices on Monday.
So we built the Go SDK around two things: connections that repair themselves, and a payload format that doesn't require trusting the transport — including us.
Feature one: the encrypted envelope
This is the headline, and it's exclusive to the Go SDK for now: the envelope package gives you true end-to-end encryption over PushFlo channels.
The design premise is blunt: the relay is treated as untrusted transport. Every payload is sealed to the recipient's X25519 public key and signed with the sender's Ed25519 key. What travels through PushFlo — and what sits in message history — is ciphertext. We can't read it. Neither can anyone who compromises a key with publish access to your channel.
Sealing on the sender side:
sealer, _ := envelope.NewSealer(envelope.SealerConfig{
RecipientPublic: executorPub, // who may read it
SignerPrivate: cloudSignPriv, // proves who sent it
SignerKeyID: "cloud-1",
})
// Seal and publish in one call
pub.PublishSealed(ctx, "executor-1", sealer, job, envelope.Meta{})
Opening on the recipient side:
opener, _ := envelope.NewOpener(envelope.OpenerConfig{
RecipientPublic: executorPub,
RecipientPrivate: executorPriv,
Signers: map[string]ed25519.PublicKey{"cloud-1": cloudSignPub},
MaxClockSkew: 2 * time.Minute,
Replay: envelope.NewMemoryReplayCache(10 * time.Minute),
})
var job Job
if err := opener.OpenJSON(env, &job); err != nil {
// ErrBadSignature / ErrReplay / ErrExpired / ErrDecrypt / ErrUnknownSigner
return
}
One OpenJSON call gets you three guarantees:
- Confidentiality — only the holder of the recipient's private key can decrypt.
- Authenticity and integrity — the Ed25519 signature covers every field. A tampered envelope fails, and an envelope from a signer you didn't list in
Signersfails. - Replay protection — message ID plus timestamp, checked against a replay cache. Capturing a valid "run this job" message and re-sending it an hour later gets you
ErrReplay, not a second execution.
And when something is rejected, you get a sentinel error telling you why — ErrBadSignature is a very different operational event than ErrExpired, and your logs will say which one happened.
Why does this matter for the worker use case specifically? Because the canonical Go SDK pattern is command and control: a cloud service dispatching jobs to executors running in environments you don't fully control — customer VPCs, edge boxes, CI runners. In that architecture "the messaging relay can read and forge jobs" is not an acceptable line in your threat model. With the envelope, PushFlo carries your messages the way the postal service carries a sealed letter: reliably, without reading it.
Feature two: rock-solid by default
The unglamorous half of the SDK is the part we spent the most time on. Long-running connection handling is a genre of code where everyone's first version is wrong, so the SDK's defaults are the production configuration:
- Auto-reconnect with jittered exponential backoff. On by default, retries forever (
MaxReconnectAttempts: 0), with jitter so a fleet of a hundred workers doesn't reconnect-stampede the gateway after a network blip. Subscriptions are re-established automatically after reconnect. - Heartbeat monitoring. The failure mode that actually kills daemons isn't the dropped connection — it's the half-open one, where TCP still looks alive but nothing flows. The client pings on a fixed interval, and two silent heartbeats means the connection is declared dead and rebuilt. No hanging forever on a socket that will never speak again.
- Typed errors.
ConnectionError,AuthenticationError,NetworkError,ValidationError— so your process can make real decisions: retry onNetworkError, exit loudly onAuthenticationErrorinstead of retrying a key that will never work. - Context-first API. Every blocking call takes a
context.Context. Wire it tosignal.NotifyContextand your worker shuts down cleanly on SIGTERM, the way an OS-level process should.
A complete production-shaped worker is about thirty lines — connect, subscribe, block on ctx.Done(). The full walkthrough is in the Go SDK docs.
We also want to be precise about maturity, because "rock solid" earns skepticism from a v0: the wire protocol is verified against the official TypeScript SDK v1.1.0 and against the live service in integration tests. What may still shift before v1 is the Go API surface — names and signatures, not behavior on the wire.
A reference executor, included
The repo ships a working reference at examples/executor — an HTTP-check worker that receives sealed jobs, executes them with per-phase timing (DNS, connect, TLS, time-to-first-byte), refuses to talk to private IP ranges by default, and publishes sealed results back. There's also examples/keygen for generating the X25519/Ed25519 key material, so nobody has to hand-roll key generation.
If you're building anything shaped like "cloud dispatches work, agents execute it," that example is the intended starting point.
Which SDK do I use?
Simple rule:
| You're building | Use |
|---|---|
| Browser UI, React app, notifications in a web page | TypeScript SDK (npm) |
| Serverless function that publishes | Either — both publish over REST |
| Worker, daemon, CLI, agent, executor | Go SDK |
| Anything where the relay must not read payloads | Go SDK with the envelope |
Same channels, same wire protocol, same pricing — a Go worker and a Next.js frontend can talk to each other through one PushFlo project today. Docs are at /docs/go-sdk, source is on GitHub, and if you hit anything rough in the v0, an issue on the repo goes straight to the person who wrote it.
Ship a worker that stays up
Grab your free API keys, go get the SDK, and have a self-healing, end-to-end encrypted Go worker running before your coffee cools.
Written by Marek
Indie developer building PushFlo — real-time messaging for serverless apps. Every post comes from running the thing in production. More about PushFlo →
Related Articles
We Raised the Free Tier — and Shipped Pro & Business Plans
The free tier now includes 100 connections, 100 channels, and double the publish rate — and Pro ($49) and Business ($149) plans cover apps that outgrew Starter. Plus: what soft limits actually mean.
Introducing Projects: Organize Your Real-time Apps Without Changing a Line of Code
PushFlo now has projects — isolated namespaces for channels and API keys. Group apps and environments, monitor them separately, and switch by swapping one API key.
Real-time Features Every SaaS Dashboard Needs in 2026
A practical guide to the real-time features that modern SaaS users expect, with implementation patterns and code examples for serverless applications.
