Tutorial

How to Send WhatsApp Messages from Go (No SDK Required)

Send WhatsApp messages from Go using only net/http and encoding/json — no third-party SDK required. Session setup, QR auth, and production error handling.

August 4, 2026By Retention Stack

Meta Description: Send WhatsApp messages from Go using only net/http and encoding/json — no third-party SDK required. Session setup, QR auth, and production error handling.


Introduction

Every WhatsApp-in-Go tutorial you'll find reaches for a vendor SDK first. Skip it — the WhatsApp Messaging API is plain REST over HTTPS, and Go's standard library (net/http, encoding/json) is all you need.

In this guide, you'll send a real WhatsApp message from Go in under 5 minutes, with zero external dependencies.

What you'll need:

  • Go 1.18+
  • A RapidAPI account (free)
  • 5 minutes

Let's send a message.


Step 1: Get Your API Key (30 seconds)

  1. 1.Go to RapidAPI Hub
  2. 2.Search for "Whatsapp messaging bot"
  3. 3.Click Subscribe (Free plan available, no card required)
  4. 4.Open API Settings and copy your X-RapidAPI-Key

Step 2: Create a WhatsApp Session

go
package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"os"
)

const apiHost = "whatsapp-messaging-bot.p.rapidapi.com"

var apiKey = os.Getenv("RAPIDAPI_KEY")

func call(method, path string, body map[string]any) (int, map[string]any, error) {
	var reqBody io.Reader
	if body != nil {
		b, _ := json.Marshal(body)
		reqBody = bytes.NewReader(b)
	}

	req, err := http.NewRequest(method, "https://"+apiHost+path, reqBody)
	if err != nil {
		return 0, nil, err
	}
	req.Header.Set("x-rapidapi-key", apiKey)
	req.Header.Set("x-rapidapi-host", apiHost)
	req.Header.Set("Content-Type", "application/json")

	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		return 0, nil, err
	}
	defer resp.Body.Close()

	var result map[string]any
	json.NewDecoder(resp.Body).Decode(&result)
	return resp.StatusCode, result, nil
}

func main() {
	sessionName := "my-session"

	// Create the session
	_, created, _ := call("POST", "/v1/sessions", map[string]any{"name": sessionName})
	fmt.Println("Session created:", created)

	// Get the QR code to authenticate
	_, qr, _ := call("GET", "/v1/sessions/"+sessionName+"/auth/qr", nil)
	fmt.Println("Scan this with WhatsApp → Linked Devices:")
	fmt.Println(qr["qr"])
}
bash
RAPIDAPI_KEY=your_key_here go run main.go

Open WhatsApp on your phone → Settings → Linked Devices → Link a Device, and scan the printed QR code.


Step 3: Send Your First Message

go
func sendText(chatId, text, session string) (int, map[string]any, error) {
	return call("POST", "/v1/sendText", map[string]any{
		"chatId":  chatId,
		"text":    text,
		"session": session,
	})
}

func main() {
	status, result, err := sendText("14155552671", "Hello from Go!", "my-session")
	if err != nil {
		fmt.Println("Request failed:", err)
		return
	}
	if status == 200 || status == 201 {
		fmt.Println("Message sent.")
	} else {
		fmt.Println("Send failed:", result)
	}
}

Check your phone — that's a real WhatsApp message sent from Go with zero external dependencies.


Step 4: Sending Images and Files

Same call() helper, different endpoint and payload:

go
func sendImage(chatId, imageUrl, caption, session string) (int, map[string]any, error) {
	return call("POST", "/v1/sendImage", map[string]any{
		"chatId":  chatId,
		"image":   imageUrl,
		"caption": caption,
		"session": session,
	})
}

Both sendImage and sendFile take a public URL — the API fetches and delivers it, no upload step needed. Full parameter reference for every message type is in the interactive API reference.


Production Best Practices

1. Retry with backoff

go
func sendWithRetry(chatId, text, session string, maxRetries int) (int, map[string]any, error) {
	var status int
	var result map[string]any
	var err error

	for attempt := 1; attempt <= maxRetries; attempt++ {
		status, result, err = sendText(chatId, text, session)
		if err == nil && status < 400 {
			return status, result, nil
		}
		time.Sleep(time.Duration(attempt) * 2 * time.Second) // 2s, 4s, 6s backoff
	}
	return status, result, err
}

(add "time" to your imports)

2. Reuse http.DefaultClient or a shared *http.Client

Building a new http.Client per request throws away connection pooling. A single package-level client (as used in call() above) is the idiomatic Go pattern.

3. Confirm delivery with a webhook, not the HTTP response

A 200 from /v1/sendText means "accepted," not "delivered" — a number that isn't on WhatsApp can still return success. Register a webhook and read the message.ack event for real delivery/read status:

go
call("POST", "/v1/sessions/"+sessionName+"/webhooks", map[string]any{
	"webhookUrl": "https://yourapp.com/webhooks/whatsapp",
	"events":     []string{"message.ack"},
})

Troubleshooting

IssueFix
401 UnauthorizedRAPIDAPI_KEY environment variable isn't set, or the key's been rotated in the RapidAPI dashboard.
x509: certificate errors on older systemsUpdate your system's CA certificates — Go's net/http uses the OS trust store.
QR code expires before you scan itQR codes are short-lived — fetch it right before you're ready to scan.
Message accepted but never arrivesCheck message.ack via webhook — a 200 only means the API accepted the send.

Next Steps

External references:


Try Free on RapidAPI → Get your API key in 30 seconds → Send your first Go message in 5 minutes.

Ready to Get Started?

Try the WhatsApp API free on RapidAPI with no credit card required.

Try Free on RapidAPI