Go SDK
The official Go SDK is built for the processes that live below the browser: background workers, daemons, CLI tools, and job executors running at the OS level. Where the TypeScript SDK targets browsers and serverless functions, the Go SDK targets long-running programs that must stay connected for days — with auto-reconnect, heartbeat monitoring, and an optional end-to-end encrypted envelope baked in.
Installation
Requires Go 1.25 or newer. The SDK ships two packages: pushflo (subscriber client and REST publisher) and pushflo/envelope (transport-agnostic encryption layer).
go get github.com/PushFlo/pushflo-goHands-on: a subscriber worker
The canonical Go SDK program is a worker that connects once and listens forever. Grab your publishable key from the console and you have a complete, production-shaped worker in about thirty lines:
package main
import (
"context"
"log"
"os"
"os/signal"
"syscall"
pushflo "github.com/PushFlo/pushflo-go"
)
func main() {
ctx, stop := signal.NotifyContext(context.Background(),
syscall.SIGINT, syscall.SIGTERM)
defer stop()
client, err := pushflo.NewClient(ctx, pushflo.ClientOptions{
PublishKey: "pub_xxxxxxxxxxxxx",
})
if err != nil {
log.Fatal(err)
}
defer client.Destroy()
client.OnConnectionChange(func(s pushflo.ConnectionState) {
log.Println("connection:", s)
})
if err := client.Connect(ctx); err != nil {
log.Fatal(err)
}
sub, err := client.Subscribe("orders-user-123", pushflo.SubscriptionHandlers{
OnMessage: func(m pushflo.Message) {
log.Printf("[%s] %s", m.EventType, string(m.Content))
},
})
if err != nil {
log.Fatal(err)
}
defer sub.Unsubscribe()
<-ctx.Done() // run until SIGINT/SIGTERM
}That's the whole lifecycle: connect, subscribe, block until the OS asks you to stop. Reconnection, heartbeats, and backoff are handled for you — see the defaults below.
Hands-on: publishing
Publishing goes over REST with your secret key — same as the TypeScript server client, so Go workers and JS backends can publish to the same channels interchangeably.
pub, err := pushflo.NewPublisher(pushflo.PublisherOptions{
SecretKey: "sec_xxxxxxxxxxxxx",
})
if err != nil {
log.Fatal(err)
}
err = pub.Publish(ctx, "orders", map[string]any{"status": "shipped"},
pushflo.WithEventType("order.shipped"))Connection resilience
Every option has a production-sensible default; a bare ClientOptions with just a key is a valid long-running configuration.
Auto-reconnect
- AutoReconnect — on by default
- MaxReconnectAttempts — 0 means retry forever
- ReconnectDelay / MaxReconnectDelay — jittered exponential backoff
Dropped connections re-establish themselves with jittered exponential backoff, so a fleet of workers won't stampede the gateway after a network blip.
Heartbeat monitoring
- HeartbeatInterval — configurable ping cadence
- Two missed heartbeats — connection declared dead, reconnect begins
Half-open TCP connections — the classic silent failure mode for long-lived daemons — are detected and replaced instead of hanging forever.
Typed errors
- ConnectionError
- AuthenticationError
- NetworkError
- ValidationError
Errors are typed so your worker can decide programmatically: retry on NetworkError, page a human on AuthenticationError.
Encrypted envelope
Exclusive to the Go SDK: the envelope package treats PushFlo as untrusted transport. Payloads are sealed to the recipient's X25519 key and signed with the sender's Ed25519 key — the relay (us included) sees only ciphertext. Generate key material with go run ./examples/keygen.
The sender seals a payload and publishes it like any other message:
import "github.com/PushFlo/pushflo-go/envelope"
sealer, err := envelope.NewSealer(envelope.SealerConfig{
RecipientPublic: executorPub, // X25519 — who may read it
SignerPrivate: cloudSignPriv, // Ed25519 — proves who sent it
SignerKeyID: "cloud-1",
})
// Seal and publish in one call:
err = pub.PublishSealed(ctx, "executor-1", sealer, job, envelope.Meta{})The recipient opens it inside its message handler. A single OpenJSON call verifies the signature, checks freshness and replay, and decrypts:
opener, err := envelope.NewOpener(envelope.OpenerConfig{
RecipientPublic: executorPub,
RecipientPrivate: executorPriv,
Signers: map[string]ed25519.PublicKey{
"cloud-1": cloudSignPub, // only these senders are trusted
},
MaxClockSkew: 2 * time.Minute,
Replay: envelope.NewMemoryReplayCache(10 * time.Minute),
})
OnMessage: func(m pushflo.Message) {
env, err := envelope.Parse(m.Content)
if err != nil {
return
}
var job Job
if err := opener.OpenJSON(env, &job); err != nil {
// ErrBadSignature / ErrReplay / ErrExpired /
// ErrDecrypt / ErrUnknownSigner
log.Println("rejected envelope:", err)
return
}
process(job)
},Guarantees
- Confidentiality — only the recipient's X25519 key decrypts
- Authenticity & integrity — Ed25519 signature over all fields
- Replay protection — message ID + timestamp with a replay cache
A captured envelope can't be read, forged, tampered with, or re-delivered later. Rejections come back as distinct sentinel errors so you can log exactly why a message was refused.
Reference executor & testing
The repository ships a reference worker at examples/executor — it receives sealed jobs, executes HTTP checks with per-phase timing (DNS, connect, TLS, TTFB), blocks egress to private addresses by default, and publishes sealed results back. It's the recommended starting point for building your own executor.
To run the SDK's integration tests against the live service with your own keys:
PUSHFLO_PUBLISH_KEY=pub_... PUSHFLO_SECRET_KEY=sec_... \
go test -run Integration -v .The SDK is currently v0: the wire protocol is verified against the official TypeScript SDK v1.1.0 and the live service, but the public Go API may still evolve before v1. Source and issues live on GitHub.
