A step-by-step guide to building a WhatsApp messaging client in Go using the whatsmeow library—covering setup, QR authentication, and sending messages programmatically.
Originally published on Medium.
WhatsApp automation in Go is surprisingly approachable once you understand the whatsmeow library. This article walks through building a working client from scratch—covering QR-code authentication, session persistence, and sending text messages to any number.
Why whatsmeow?
whatsmeow is an open-source Go library that implements the WhatsApp Web protocol. Unlike the official Business API, it gives you direct control over the connection and works without a Meta developer account.
Key capabilities:
- QR-code login with session persistence (no re-scan on restart)
- Send and receive messages, media, and reactions
- Handle group events and presence updates
- Lightweight—runs on a single binary with no external runtime
Core flow
┌─────────────────────────────────────────────────────┐
│ 1. Init SQLite store │
│ 2. Create whatsmeow client │
│ 3. QR scan (first run) / restore session │
│ 4. Connect to WhatsApp servers │
│ 5. Send message via client.SendMessage() │
└─────────────────────────────────────────────────────┘
Setup
go get go.mau.fi/whatsmeow
go get go.mau.fi/whatsmeow/store/sqlstoreInitialise the store
whatsmeow stores session keys in an SQL database. SQLite works fine for single-instance services:
container, err := sqlstore.New("sqlite3", "file:session.db?_foreign_keys=on", nil)
if err != nil {
log.Fatal(err)
}
deviceStore, err := container.GetFirstDevice()
if err != nil {
log.Fatal(err)
}Create the client and handle QR
client := whatsmeow.NewClient(deviceStore, nil)
if client.Store.ID == nil {
// First login — show QR code
qrChan, _ := client.GetQRChannel(context.Background())
if err := client.Connect(); err != nil {
log.Fatal(err)
}
for evt := range qrChan {
if evt.Event == "code" {
qrterminal.GenerateHalfBlock(evt.Code, qrterminal.L, os.Stdout)
}
}
} else {
// Session already exists
if err := client.Connect(); err != nil {
log.Fatal(err)
}
}Send a message
recipient, _ := types.ParseJID("628123456789@s.whatsapp.net")
msg := &waProto.Message{
Conversation: proto.String("Hello from Go!"),
}
_, err = client.SendMessage(context.Background(), recipient, msg)
if err != nil {
log.Printf("send error: %v", err)
}The JID format for individual numbers is <country_code><number>@s.whatsapp.net (no +, no spaces).
Production tips
- Session persistence: commit
session.dbto persistent storage (volume mount in Docker, not ephemeral FS). - Reconnect logic: listen for
events.Disconnectedand callclient.Connect()with exponential back-off. - Rate limits: WhatsApp will ban numbers that send bulk messages aggressively. Space out sends and keep volumes reasonable.
- Multi-device: whatsmeow supports the multi-device protocol—your phone does not need to stay online after the initial QR scan.
Read the full article with complete runnable code on Medium →