waga

package module
v0.7.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 15, 2026 License: MIT Imports: 15 Imported by: 0

README

WhatsApp Gateway Go SDK

A Go SDK for the WhatsApp Gateway API. This SDK provides an ergonomic client wrapper around generated types from the OpenAPI specification.

Installation

go get github.com/glennprays/whatsapp-gateway-sdk-go

Quick Start

package main

import (
    "context"
    "fmt"
    "log"

    "github.com/glennprays/whatsapp-gateway-sdk-go"
)

func main() {
    // Create client
    client := waga.NewClient(waga.WithBaseURL("http://localhost:3000/api/v1"))

    // Register phone number and get JWT token
    _, err := client.Register(context.Background(), "6281234567890", "your_secret_key")
    if err != nil {
        log.Fatal(err)
    }

    // Get QR code for WhatsApp login
    qr, err := client.GetQRCode(context.Background(), "json")
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println("Scan this QR code:", qr.QRCode)

    // Send a message
    resp, err := client.SendText(context.Background(), "6289876543210@s.whatsapp.net", "Hello!")
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println("Message sent:", resp.MessageId)
}

Configuration

The client can be configured with various options:

client := waga.NewClient(
    waga.WithBaseURL("https://your-gateway.com/api/v1"),
    waga.WithTimeout(30 * time.Second),
    waga.WithToken("existing_jwt_token"),  // If you already have a token
    waga.WithUserAgent("MyApp/1.0"),
)
Available Options
Option Description Default
WithBaseURL(url) API base URL http://localhost:3000/api/v1
WithTimeout(d) HTTP request timeout 30s
WithToken(token) Pre-existing JWT token ""
WithHTTPClient(c) Custom HTTP client Default client
WithUserAgent(ua) Custom User-Agent header WhatsApp-Gateway-SDK-Go/1.0

Authentication

Register New Phone Number
resp, err := client.Register(ctx, "6281234567890", "your_secret_key")
if err != nil {
    log.Fatal(err)
}
// Token is automatically stored in client
fmt.Println("Token:", resp.Token)
Use Existing Token
// Option 1: During client creation
client := waga.NewClient(waga.WithToken("your_jwt_token"))

// Option 2: Set token later
client := waga.NewClient()
client.SetToken("your_jwt_token")

WhatsApp Authentication

Get QR Code
// Get QR code as base64 (json format)
qr, err := client.GetQRCode(ctx, "json")
if err != nil {
    log.Fatal(err)
}
fmt.Println("QR Code:", qr.QRCode)        // Base64 encoded image
fmt.Println("Expires in:", qr.ExpiresIn)  // Seconds until expiry

// Get QR code as HTML img tag (html format)
qrHTML, err := client.GetQRCode(ctx, "html")
Get Pair Code
pair, err := client.GetPairCode(ctx)
if err != nil {
    log.Fatal(err)
}
fmt.Println("Pair Code:", pair.PairCode)
fmt.Println("Expires in:", pair.ExpiresIn)
Check Login Status
status, err := client.GetLoginStatus(ctx)
if err != nil {
    log.Fatal(err)
}
fmt.Println("Authenticated:", status.Authenticated)
Reconnect Session
err := client.Reconnect(ctx)
if err != nil {
    log.Fatal(err)
}
Logout
err := client.Logout(ctx)
if err != nil {
    log.Fatal(err)
}

Sending Messages

Send Options

Every SendXxx method takes a trailing variadic of SendOptions for canonical chat addressing, quoted replies, @-mentions, and send idempotency. Existing call sites keep working unchanged — options are purely additive.

resp, err := client.SendText(ctx, recipient, "Hi team!",
    waga.WithChat("120363000000000000@g.us"),        // canonical recipient (see below)
    waga.WithReply("MSG_ID", senderJID, "quoted text"), // quote an existing message
    waga.WithMentions("6281111111111", "6282222222222"), // @-tag participants
    waga.WithIdempotencyKey("order-4711-notify"),       // safe retry (see Idempotency)
)

The same options work on the media sends (SendImage/SendAudio/SendVideo/ SendDocument/SendSticker) and SendLocation/SendPoll.

Chat Addressing

WithChat sets the canonical recipient — a bare number, a user JID (@s.whatsapp.net), a group JID (@g.us), or a @lid. It takes precedence over the positional recipient argument, which the gateway treats as the deprecated msisdn alias. Send responses echo the resolved recipient in resp.Chat:

resp, _ := client.SendText(ctx, "", "Hi!", waga.WithChat("120363000000000000@g.us"))
fmt.Println("delivered to:", resp.Chat)
Idempotency

WithIdempotencyKey sends an Idempotency-Key header. Reusing the same key replays the gateway's original response (HTTP 200); an in-flight duplicate returns 409 (waga.ErrConflict) and the same key with a different request body returns 422 (SDKError.Code == 422). The header is only sent when a key is provided.

Text Message
// Format phone number to WhatsApp JID format
recipient := waga.FormatMSISDN("6289876543210")  // -> "6289876543210@s.whatsapp.net"

resp, err := client.SendText(ctx, recipient, "Hello from Go SDK!")
if err != nil {
    log.Fatal(err)
}
fmt.Println("Message ID:", resp.MessageId)
Image Message
// Open image file
file, err := os.Open("photo.jpg")
if err != nil {
    log.Fatal(err)
}
defer file.Close()

resp, err := client.SendImage(ctx, recipient, file, "Check this out!", false)
if err != nil {
    log.Fatal(err)
}
fmt.Println("Message ID:", resp.MessageId)
Audio Message
file, err := os.Open("voice.ogg")
if err != nil {
    log.Fatal(err)
}
defer file.Close()

resp, err := client.SendAudio(ctx, recipient, file, true, false) // isPTT=true
if err != nil {
    log.Fatal(err)
}
fmt.Println("Message ID:", resp.MessageId)
Video Message
file, err := os.Open("clip.mp4")
if err != nil {
    log.Fatal(err)
}
defer file.Close()

resp, err := client.SendVideo(ctx, recipient, file, "Look at this", false, false)
if err != nil {
    log.Fatal(err)
}
fmt.Println("Message ID:", resp.MessageId)
Document Message
file, err := os.Open("invoice.pdf")
if err != nil {
    log.Fatal(err)
}
defer file.Close()

resp, err := client.SendDocument(ctx, recipient, file, "invoice.pdf", "Monthly invoice")
if err != nil {
    log.Fatal(err)
}
fmt.Println("Message ID:", resp.MessageId)
Location Message
resp, err := client.SendLocation(ctx, recipient, -6.2088, 106.8456, "Jakarta", "Jakarta, Indonesia")
if err != nil {
    log.Fatal(err)
}
fmt.Println("Message ID:", resp.MessageId)
Poll Message
options := []string{"Red", "Green", "Blue"}

// selectableCount limits how many options a user can select (0 = no limit)
resp, err := client.SendPoll(ctx, recipient, "What is your favorite color?", options, 1)
if err != nil {
    log.Fatal(err)
}
fmt.Println("Message ID:", resp.MessageId)
Sticker Message
// Open sticker file (WebP format)
file, err := os.Open("sticker.webp")
if err != nil {
    log.Fatal(err)
}
defer file.Close()

resp, err := client.SendSticker(ctx, recipient, file)
if err != nil {
    log.Fatal(err)
}
fmt.Println("Message ID:", resp.MessageId)
Edit Message
err := client.EditMessage(ctx, recipient, messageID, "Updated message")
if err != nil {
    log.Fatal(err)
}
Delete Message
err := client.DeleteMessage(ctx, recipient, messageID)
if err != nil {
    log.Fatal(err)
}
React to Message
err := client.React(ctx, recipient, messageID, "👍")
if err != nil {
    log.Fatal(err)
}

For reactions to incoming messages in groups/DMs, pass optional sender_msisdn:

err := client.React(ctx, groupJID, messageID, "👍", "6281111111111@s.whatsapp.net")
Check Contact
contact, err := client.CheckContact(ctx, "6281234567890")
if err != nil {
    log.Fatal(err)
}
fmt.Println(contact.JID, contact.IsOnWhatsApp, contact.VerifiedName)

Incoming Messages

Fetch the most recent incoming messages buffered by the gateway (newest first):

resp, err := client.GetIncomingMessages(ctx, 10)
if err != nil {
    log.Fatal(err)
}
for _, msg := range resp.Messages {
    fmt.Printf("[%s] %s: %s\n", msg.Type, msg.From, msg.Text)
}

Note: media URLs are not populated by this endpoint; use webhooks for fetchable media.

Contacts & Groups

All of these accept the canonical chat (a bare number, @s.whatsapp.net, @g.us, or @lid).

List Contacts

Locally-synced contacts, paginated (limit defaults to 100, max 500). Never errors on an empty address book:

page, err := client.ListContacts(ctx, 100, 0) // limit, offset
if err != nil {
    log.Fatal(err)
}
fmt.Printf("%d of %d contacts\n", page.Count, page.Total)
for _, ct := range page.Contacts {
    fmt.Println(ct.JID, ct.PushName)
}
Contact Info

Server-side profile lookup (status, picture id, verified name, device count, lid):

info, err := client.GetContactInfo(ctx, "6281234567890")
if err != nil {
    log.Fatal(err)
}
fmt.Println(info.Status, info.DeviceCount, info.LID)
Avatar (with conditional fetch)

Profile picture for a user or group. preview requests the thumbnail. The returned ID doubles as an ETag — pass it back as the optional priorID to skip re-downloading when unchanged:

av, err := client.GetAvatar(ctx, "6281234567890", false)
if err != nil {
    if errors.Is(err, waga.ErrNotFound) {
        // no profile picture
    } else if errors.Is(err, waga.ErrForbidden) {
        // picture hidden by privacy settings
    }
    return
}
fmt.Println(av.URL, av.ID)

// Later — only re-fetch if the picture changed:
av2, err := client.GetAvatar(ctx, "6281234567890", false, av.ID)
if errors.Is(err, waga.ErrNotModified) {
    // unchanged; keep the cached av
}
List Groups

Joined-group summaries. This is a budgeted server read — a 429 maps to ErrRateLimited:

groups, err := client.ListGroups(ctx)
if errors.Is(err, waga.ErrRateLimited) {
    // read budget exhausted; back off
}
for _, g := range groups.Groups {
    fmt.Println(g.JID, g.Name, g.ParticipantCount)
}
Group Info

Full detail plus participant roster. Requires a group JID; ErrForbidden if the account is not a member, ErrNotFound if the group is absent:

g, err := client.GetGroupInfo(ctx, "120363000000000000@g.us")
if err != nil {
    log.Fatal(err)
}
for _, p := range g.Participants {
    fmt.Println(p.JID, p.IsAdmin, p.IsSuperAdmin)
}

Read Receipts & Presence

Mark Messages as Read

Sends blue ticks. For group chats, pass the message author as sender:

err := client.MarkRead(ctx, "120363000000000000@g.us",
    []string{"MSG_ID_1", "MSG_ID_2"},
    "6281234567890@s.whatsapp.net") // sender (author); "" for one-to-one chats
Typing Indicator
err := client.SendChatPresence(ctx, recipient, waga.PresenceComposing) // typing…
// ... waga.PresenceRecording (voice note) / waga.PresencePaused (cleared)

Group & Community Management

Every group/community method requires an explicit group JID (@g.us); a bare number or user JID is rejected with 400. These endpoints are gated server-side by GROUP_MANAGEMENT_ENABLED (the whole mutation surface returns 404 when the gateway disables it). Batch operations return 200 with a per-participant Results slice — a single bad member is reported there, not as an overall error.

// Create a group (or a community with IsCommunity: true).
grp, err := client.CreateGroup(ctx, waga.CreateGroupRequest{
    Name:         "Project X",
    Participants: []string{"6281111111111", "6282222222222"},
})

// Roster mutations: add | remove | promote | demote.
res, err := client.UpdateGroupParticipants(ctx, grp.GroupJID, "add", []string{"6283333333333"})
for _, r := range res.Results {
    fmt.Println(r.JID, r.Status) // ok | invited (privacy-blocked add) | failed
}

// Settings, name, topic.
announce := true
client.SetGroupSettings(ctx, grp.GroupJID, &announce, nil) // ≥1 non-nil flag
client.SetGroupName(ctx, grp.GroupJID, "Project X — Q3")   // ≤25 chars
client.SetGroupTopic(ctx, grp.GroupJID, "")                // ≤512; empty clears

// Photo (multipart JPEG).
f, _ := os.Open("group.jpg")
defer f.Close()
client.SetGroupPhoto(ctx, grp.GroupJID, f)
client.DeleteGroupPhoto(ctx, grp.GroupJID)

// Invite links.
link, _ := client.GetGroupInviteLink(ctx, grp.GroupJID)
client.ResetGroupInviteLink(ctx, grp.GroupJID) // revoke + regenerate

// Preview a link without joining (a revoked link matches ErrGone).
info, err := client.GetGroupInviteInfo(ctx, "https://chat.whatsapp.com/XXXX")
if errors.Is(err, waga.ErrGone) {
    // link revoked
}
client.JoinGroup(ctx, "https://chat.whatsapp.com/XXXX") // gated by GROUP_JOIN_VIA_LINK_ENABLED

// Pending join requests.
reqs, _ := client.ListJoinRequests(ctx, grp.GroupJID)
client.ReviewJoinRequests(ctx, grp.GroupJID, "approve", []string{"6284444444444"}) // approve | reject

// Communities.
client.LinkSubGroup(ctx, communityJID, subGroupJID)
client.UnlinkSubGroup(ctx, communityJID, subGroupJID)
subs, _ := client.ListSubGroups(ctx, communityJID)
members, _ := client.ListCommunityParticipants(ctx, communityJID)

Read-only group/community methods (ListGroups, GetGroupInfo, ListSubGroups, ListCommunityParticipants) stay available even when mutations are disabled.

Admin Module

The gateway's operator-only admin plane is exposed as a separate, opt-in client so tenant code can't accidentally reach it. It is served at the server root (not under /api/v1) and is bearer-gated by the gateway's ADMIN_API_SECRET (the plane returns 404 when that secret is unset).

Construct it with NewAdminClient, pointing WithBaseURL at the gateway origin and passing the admin secret via WithAdminSecret:

admin := waga.NewAdminClient(
    waga.WithBaseURL("https://gateway.example.com"), // server ROOT, no /api/v1
    waga.WithAdminSecret(os.Getenv("ADMIN_API_SECRET")),
)

// Per-instance session inventory (masked phones, honest states, hostname).
inv, err := admin.Sessions(ctx)
for _, s := range inv.Sessions {
    fmt.Println(s.PhoneMasked, s.State)
}

// One account (ErrNotFound if unknown).
one, err := admin.Session(ctx, "6281234567890")

// Root health probes (no admin secret required).
live, _ := admin.Live(ctx)   // always 200 for a running process
ready, err := admin.Ready(ctx)
if err == nil && ready.Status != "ready" {
    // not_ready (HTTP 503): DB or queue down — the body is still returned
}

Job Status

When the gateway runs in queue mode, send methods return a job ID instead of a message ID. The send response tells you which mode you're in:

resp, err := client.SendText(ctx, recipient, "Hello!")
if err != nil {
    log.Fatal(err)
}
if resp.JobID != "" {
    // Queue mode: poll the job until it completes
    status, _ := client.GetJobStatus(ctx, resp.JobID)
    fmt.Println("Status:", status.Status) // queued | processing | completed | failed
    if status.MessageID != nil {
        fmt.Println("Message ID:", *status.MessageID)
    }
} else {
    // Direct mode: the message ID is available immediately
    fmt.Println("Message ID:", resp.MessageId)
}

Trace IDs

Attach a trace ID to correlate your application logs with gateway logs. Every request made with the returned context sends it as the X-Trace-ID header:

ctx := waga.WithTraceID(context.Background(), "order-12345")
resp, err := client.SendText(ctx, recipient, "Your order shipped!")

If a request fails, the gateway's trace ID is attached to the error so you can find the failing request in the gateway logs:

if err != nil {
    var apiErr *waga.SDKError
    if errors.As(err, &apiErr) {
        log.Printf("gateway error (trace %s): %s", apiErr.TraceID, apiErr.Message)
    }
}

Webhook Management

Register Webhook
err := client.RegisterWebhook(ctx, "https://example.com/webhook", "my_hmac_secret")
if err != nil {
    log.Fatal(err)
}
Get Registered Webhook
webhook, err := client.GetWebhook(ctx)
if err != nil {
    log.Fatal(err)
}
fmt.Println("Webhook URL:", webhook.URL)
Unregister Webhook
err := client.UnregisterWebhook(ctx)
if err != nil {
    log.Fatal(err)
}

Webhook Verification

When receiving webhooks on your server, verify the signature:

import (
    "io"
    "net/http"
    "errors"

    "github.com/glennprays/whatsapp-gateway-sdk-go"
)

func handleWebhook(w http.ResponseWriter, r *http.Request) {
    body, _ := io.ReadAll(r.Body)
    signature := r.Header.Get("X-Webhook-Signature")

    // Create verifier with your HMAC secret
    verifier := waga.NewWebhookVerifier("my_hmac_secret")

    // Verify and parse incoming message webhook
    payload, err := verifier.ParseIncomingWebhook(body, signature)
    if err != nil {
        if errors.Is(err, waga.ErrInvalidSignature) {
            http.Error(w, "Invalid signature", http.StatusUnauthorized)
            return
        }
        http.Error(w, "Invalid payload", http.StatusBadRequest)
        return
    }

    // Process the webhook
    fmt.Printf("Received message from %s: %s\n", payload.From, payload.Text)
}
Unified Dispatch with ParseWebhook

When a single endpoint receives every webhook type, ParseWebhook verifies the signature and dispatches on the event field into a discriminated WebhookEvent. Exactly one of Incoming, Outgoing, or Session is non-nil. The narrower ParseIncomingWebhook / ParseOutgoingWebhook remain available.

ev, err := verifier.ParseWebhook(body, signature)
if err != nil {
    if errors.Is(err, waga.ErrUnknownWebhookEvent) {
        http.Error(w, "unknown event", http.StatusBadRequest)
        return
    }
    // errors.Is(err, waga.ErrInvalidSignature) for a bad signature
    http.Error(w, "invalid webhook", http.StatusBadRequest)
    return
}

switch ev.Event {
case waga.WebhookEventMessageIncoming:
    fmt.Printf("from %s: %s\n", ev.Incoming.From, ev.Incoming.Text)
case waga.WebhookEventMessageSent, waga.WebhookEventMessageQueued, waga.WebhookEventMessageFailed:
    fmt.Printf("%s: %s\n", ev.Outgoing.Event, ev.Outgoing.MessageId)
case waga.WebhookEventSessionBanned:
    fmt.Printf("account %s banned for %ds\n", ev.Session.PhoneNumber, ev.Session.ExpiresIn)
default: // other session.* lifecycle events
    fmt.Printf("session event %s for %s\n", ev.Event, ev.Session.JID)
}
Session Events

Besides the four message events, the gateway emits six session.* lifecycle events, decoded into SessionEvent (flat envelope Event/PhoneNumber/JID/ Timestamp plus event-specific extras):

Event Extras
WebhookEventSessionLoggedOut OnConnect, Reason, ReasonText
WebhookEventSessionBanned Code, ReasonText, ExpiresIn
WebhookEventSessionConnectFailure Reason, ReasonText, Message
WebhookEventSessionConnected envelope only
WebhookEventSessionDisconnected envelope only
WebhookEventSessionReplaced envelope only
Webhook Payload Types
Incoming Message
type IncomingWebhookPayload struct {
    Event      IncomingEventMessage      // "message.incoming"
    Chat       string                    // Chat ID
    From       string                    // Sender's ID
    IsGroup    bool                      // Is from group
    MessageId string                    // Message ID
    PushName   string                    // Sender's display name
    Timestamp  int                       // Unix timestamp
    Text       string                    // Message text (if text message)
    Type       IncomingMessageType       // "text", "image", "video", "audio", "document"
    Media      *IncomingMessageMediaInfo // Media info (if media message)
}
Outgoing Event
type OutgoingWebhookPayload struct {
    Event       OutgoingEventMessage // "message.queued", "message.sent", "message.failed"
    JobId       string               // Job ID
    To          string               // Recipient's ID
    PhoneNumber string               // Sender's phone number
    Timestamp   int                  // Unix timestamp
    MessageId   string               // Message ID
    Metadata    map[string]interface{} // Additional metadata
}

Error Handling

_, err := client.SendText(ctx, recipient, "Hello")
if err != nil {
    var sdkErr *waga.SDKError
    if waga.IsUnauthorized(err) {
        // Token expired or invalid
        fmt.Println("Please re-register")
    } else if waga.IsBadRequest(err) {
        // Invalid request parameters
        fmt.Println("Check your input")
    } else if waga.IsRateLimited(err) {
        // Too many requests
        fmt.Println("Please wait before retrying")
    } else if errors.As(err, &sdkErr) {
        // Other API error
        fmt.Printf("Error %d: %s\n", sdkErr.Code, sdkErr.Message)
    } else {
        // Network or other error
        fmt.Printf("Unexpected error: %v\n", err)
    }
}
Error Types
Error HTTP Code Description
ErrUnauthorized 401 Invalid or missing token
ErrBadRequest 400 Invalid request parameters
ErrForbidden 403 No permission for this action
ErrNotFound 404 Resource not found
ErrConflict 409 Resource conflict
ErrGone 410 Resource gone (e.g. revoked group invite link)
ErrNotModified 304 GetAvatar — picture unchanged (conditional fetch)
ErrRateLimited 429 Too many requests
ErrInternalServer 500 Server error
ErrInvalidSignature - Webhook signature verification failed
ErrUnknownWebhookEvent - ParseWebhook got an unrecognized event
ErrNotAuthenticated - Client has no token set

Helper Functions

Format MSISDN

Convert phone number to WhatsApp JID format:

jid := waga.FormatMSISDN("6281234567890")
// Returns: "6281234567890@s.whatsapp.net"
Format Group ID

Convert group ID to WhatsApp group JID format:

jid := waga.FormatGroupID("1234567890")
// Returns: "1234567890@g.us"
Compute Signature

Compute webhook signature (useful for testing):

payload := []byte(`{"event":"message.incoming"}`)
signature := waga.ComputeSignature(payload, "my_hmac_secret")
// Returns: "sha256=..."

Health Check

health, err := client.Health(ctx)
if err != nil {
    log.Fatal(err)
}
fmt.Printf("Status: %s\n", health.Status)

Development

Generate Types from OpenAPI
# Install oapi-codegen
go install github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen@latest

# Generate types
make generate
Run Tests
go test ./...

Requirements

  • Go 1.24 or later
  • Running WhatsApp Gateway instance

License

MIT License

Documentation

Overview

Package waga provides a Go client for interacting with the WhatsApp Gateway API.

The waga package enables Go applications to send WhatsApp messages, manage webhooks, and handle authentication with a WhatsApp Gateway service. It supports text and image messaging, message editing/deletion, webhook verification, and session management.

Quick Start

To get started, create a new client and register your phone number:

client := waga.NewClient()
resp, err := client.Register(ctx, "6281234567890", "your-secret-key")
if err != nil {
    log.Fatal(err)
}

Once registered, you can send messages:

msisdn := waga.FormatMSISDN("6281234567890")
msgResp, err := client.SendText(ctx, msisdn, "Hello, World!")
if err != nil {
    log.Fatal(err)
}

Authentication

The SDK supports two authentication methods:

1. Register a new phone number to obtain a JWT token 2. Use an existing JWT token with SetToken() or WithToken()

The client automatically stores the token returned by Register() for subsequent requests.

Message Formatting

Phone numbers should be formatted as MSISDN (WhatsApp JID format). Use the helper functions FormatMSISDN() for individuals and FormatGroupID() for groups:

individual := waga.FormatMSISDN("6281234567890")  // "6281234567890@s.whatsapp.net"
group := waga.FormatGroupID("1234567890")          // "1234567890@g.us"

Webhooks

Webhooks allow you to receive real-time notifications for incoming and outgoing messages. To handle webhooks securely, use the WebhookVerifier:

verifier := waga.NewWebhookVerifier("your-hmac-secret")
payload, err := verifier.ParseIncomingWebhook(body, signatureHeader)
if err != nil {
    log.Fatal(err)
}

Configuration

The client can be configured with options:

client := waga.NewClient(
    waga.WithBaseURL("https://api.example.com"),
    waga.WithTimeout(60*time.Second),
    waga.WithToken("existing-jwt-token"),
)

Error Handling

The SDK returns typed errors that can be checked using helper functions:

resp, err := client.SendText(ctx, msisdn, message)
if waga.IsUnauthorized(err) {
    // Handle authentication error
} else if waga.IsRateLimited(err) {
    // Handle rate limiting
}

For more examples and detailed usage, see the examples directory and README.md.

Index

Constants

View Source
const (
	// EventMessageQueued is emitted when a message is added to the queue
	EventMessageQueued = OutgoingEventMessageMessageQueued
	// EventMessageSent is emitted when a message is successfully sent
	EventMessageSent = OutgoingEventMessageMessageSent
	// EventMessageFailed is emitted when a message fails to send
	EventMessageFailed = OutgoingEventMessageMessageFailed
	// EventMessageIncoming is emitted when a message is received
	EventMessageIncoming = IncomingEventMessageMessageIncoming
)

Outgoing event type constants for convenience

View Source
const (
	// MessageTypeText represents a text message
	MessageTypeText = IncomingMessageTypeText
	// MessageTypeImage represents an image message
	MessageTypeImage = IncomingMessageTypeImage
	// MessageTypeVideo represents a video message
	MessageTypeVideo = IncomingMessageTypeVideo
	// MessageTypeAudio represents an audio message
	MessageTypeAudio = IncomingMessageTypeAudio
	// MessageTypeDocument represents a document message
	MessageTypeDocument = IncomingMessageTypeDocument
)

Incoming message type constants for convenience

View Source
const (
	// DefaultBaseURL is the default API endpoint
	DefaultBaseURL = "http://localhost:3000/api/v1"
	// DefaultAdminBaseURL is the default base URL for the admin plane. The admin
	// and health-probe routes live at the server ROOT, not under the /api/v1
	// base path, so AdminClient defaults to the bare origin.
	DefaultAdminBaseURL = "http://localhost:3000"
	// DefaultTimeout is the default HTTP request timeout
	DefaultTimeout = 30 * time.Second
	// DefaultUserAgent is the default User-Agent header
	DefaultUserAgent = "WhatsApp-Gateway-SDK-Go/1.0"
)
View Source
const TraceIDHeader = "X-Trace-ID"

TraceIDHeader is the HTTP header used to propagate a trace ID to the gateway. The gateway logs this ID with every operation, so requests can be correlated across your application and the gateway logs.

Variables

View Source
var (
	// ErrUnauthorized is returned when authentication fails (401)
	ErrUnauthorized = &SDKError{Code: http.StatusUnauthorized, Message: "unauthorized"}
	// ErrBadRequest is returned for invalid requests (400)
	ErrBadRequest = &SDKError{Code: http.StatusBadRequest, Message: "bad request"}
	// ErrForbidden is returned when access is denied (403)
	ErrForbidden = &SDKError{Code: http.StatusForbidden, Message: "forbidden"}
	// ErrNotFound is returned when a resource is not found (404)
	ErrNotFound = &SDKError{Code: http.StatusNotFound, Message: "not found"}
	// ErrConflict is returned for conflicting requests (409)
	ErrConflict = &SDKError{Code: http.StatusConflict, Message: "conflict"}
	// ErrGone is returned when a resource is no longer available (410), e.g.
	// GetGroupInviteInfo on a revoked invite link.
	ErrGone = &SDKError{Code: http.StatusGone, Message: "gone"}
	// ErrRateLimited is returned when rate limit is exceeded (429)
	ErrRateLimited = &SDKError{Code: http.StatusTooManyRequests, Message: "rate limited"}
	// ErrNotModified is returned by GetAvatar when the caller-supplied prior
	// avatar id (If-None-Match) still matches — the picture is unchanged (304).
	ErrNotModified = &SDKError{Code: http.StatusNotModified, Message: "not modified"}
	// ErrInternalServer is returned for server errors (500)
	ErrInternalServer = &SDKError{Code: http.StatusInternalServerError, Message: "internal server error"}
	// ErrInvalidSignature is returned when webhook signature verification fails
	ErrInvalidSignature = errors.New("invalid webhook signature")
	// ErrUnknownWebhookEvent is returned by ParseWebhook when the payload's
	// event field is not a recognized webhook event type.
	ErrUnknownWebhookEvent = errors.New("unknown webhook event")
	// ErrNotAuthenticated is returned when trying to use an authenticated method without setting a token
	ErrNotAuthenticated = errors.New("client not authenticated, call Register() or SetToken() first")
)

Common API errors These predefined errors can be used for comparison with errors.Is().

Functions

func ComputeSignature

func ComputeSignature(payload []byte, secret string) string

ComputeSignature computes the HMAC-SHA256 signature for a webhook payload. This is primarily useful for testing webhook implementations.

The returned signature is in the format "sha256=<hex_signature>", which matches the format used in the X-Webhook-Signature header.

Example:

payload := []byte(`{"event":"message.incoming"}`)
signature := waga.ComputeSignature(payload, "your-secret")
// Use signature to set X-Webhook-Signature header in test requests

func FormatGroupID

func FormatGroupID(groupID string) string

FormatGroupID formats a group ID to WhatsApp group JID format. If the groupID already contains "@", it is returned as-is. Otherwise, "@g.us" is appended to create a valid group JID.

Example:

groupID := waga.FormatGroupID("1234567890")  // "1234567890@g.us"

func FormatMSISDN

func FormatMSISDN(phoneNumber string) string

FormatMSISDN formats a phone number to WhatsApp JID format for individual contacts. If the phoneNumber already contains "@", it is returned as-is. Otherwise, "@s.whatsapp.net" is appended to create a valid JID.

Example:

msisdn := waga.FormatMSISDN("6281234567890")  // "6281234567890@s.whatsapp.net"

func IsBadRequest

func IsBadRequest(err error) bool

IsBadRequest checks if the error is a bad request error (HTTP 400). Returns true if err matches ErrBadRequest.

func IsConflict

func IsConflict(err error) bool

IsConflict checks if the error is a conflict error (HTTP 409). Returns true if err matches ErrConflict.

func IsForbidden

func IsForbidden(err error) bool

IsForbidden checks if the error is a forbidden error (HTTP 403). Returns true if err matches ErrForbidden.

func IsGone added in v0.7.0

func IsGone(err error) bool

IsGone checks if the error is a gone error (HTTP 410). Returns true if err matches ErrGone.

func IsInternalServer

func IsInternalServer(err error) bool

IsInternalServer checks if the error is an internal server error (HTTP 500). Returns true if err matches ErrInternalServer.

func IsNotFound

func IsNotFound(err error) bool

IsNotFound checks if the error is a not found error (HTTP 404). Returns true if err matches ErrNotFound.

func IsRateLimited

func IsRateLimited(err error) bool

IsRateLimited checks if the error is a rate limit error (HTTP 429). Returns true if err matches ErrRateLimited.

func IsUnauthorized

func IsUnauthorized(err error) bool

IsUnauthorized checks if the error is an unauthorized error (HTTP 401). Returns true if err matches ErrUnauthorized.

func TraceIDFromContext added in v0.4.0

func TraceIDFromContext(ctx context.Context) string

TraceIDFromContext returns the trace ID set by WithTraceID, or an empty string if none was set.

func WithTraceID added in v0.4.0

func WithTraceID(ctx context.Context, traceID string) context.Context

WithTraceID returns a context that carries a trace ID. Every SDK request made with the returned context sends the ID in the X-Trace-ID header:

ctx := waga.WithTraceID(context.Background(), "order-12345")
resp, err := client.SendText(ctx, msisdn, "hello")

If no trace ID is set, the gateway generates one on receipt.

Types

type AdminClient added in v0.7.0

type AdminClient struct {
	// contains filtered or unexported fields
}

AdminClient is a separate, opt-in client for the gateway's operator-only admin plane. That plane is cross-tenant, bearer-gated by the gateway's ADMIN_API_SECRET, and served at the server ROOT path (not under /api/v1) — so it is kept off the tenant-facing Client to make accidental use impossible.

Construct it with NewAdminClient and the admin secret:

admin := waga.NewAdminClient(
    waga.WithBaseURL("https://gateway.example.com"), // server ROOT
    waga.WithAdminSecret(os.Getenv("ADMIN_API_SECRET")),
)

The admin secret is sent as "Authorization: Bearer <secret>".

func NewAdminClient added in v0.7.0

func NewAdminClient(opts ...Option) *AdminClient

NewAdminClient creates an admin-plane client. Unlike NewClient it defaults its base URL to the server ROOT (DefaultAdminBaseURL); override it with WithBaseURL to point at your gateway origin. Supply the admin secret via WithAdminSecret (or WithToken).

func (*AdminClient) Live added in v0.7.0

func (a *AdminClient) Live(ctx context.Context) (*HealthResponse, error)

Live is the liveness probe (root /health/live). It always returns 200 for a running process and requires no admin secret.

func (*AdminClient) Ready added in v0.7.0

Ready is the readiness probe (root /health/ready). It requires no admin secret and returns the structured body for both "ready" (HTTP 200) and "not_ready" (HTTP 503) — inspect the returned Status rather than treating 503 as an error. Any other status (or a transport failure) is returned as an error.

func (*AdminClient) Session added in v0.7.0

func (a *AdminClient) Session(ctx context.Context, phone string) (*AdminSessionResponse, error)

Session returns one account's session state by bare phone number. An unknown account returns ErrNotFound (404).

func (*AdminClient) Sessions added in v0.7.0

func (a *AdminClient) Sessions(ctx context.Context) (*SessionInventory, error)

Sessions returns the reporting instance's session inventory (masked phones, honest states). An empty node returns Count 0, never an error.

type AdminSessionResponse added in v0.7.0

type AdminSessionResponse struct {
	Instance string               `json:"instance"`
	Session  SessionInventoryItem `json:"session"`
}

AdminSessionResponse is the GET /admin/sessions/{phone} response: one account's session plus the reporting instance hostname.

type AvatarResponse added in v0.7.0

type AvatarResponse struct {
	JID        string `json:"jid"`
	URL        string `json:"url"`
	ID         string `json:"id"`
	Type       string `json:"type"` // "image" (full res) or "preview" (thumbnail)
	DirectPath string `json:"direct_path,omitempty"`
}

AvatarResponse is a chat's (user or group) profile picture. URL is a time-limited WhatsApp CDN link the caller downloads directly. ID doubles as the ETag: pass it back to GetAvatar as priorID to get ErrNotModified (304) when the picture is unchanged.

type ChatPresenceRequest added in v0.7.0

type ChatPresenceRequest struct {
	// Chat is the canonical recipient (see SendMessageTextRequest.Chat).
	Chat string `json:"chat"`
	// State is one of composing | recording | paused.
	State string `json:"state"`
}

ChatPresenceRequest sets the typing indicator in a chat.

type Client

type Client struct {
	// contains filtered or unexported fields
}

Client is the main SDK client for interacting with the WhatsApp Gateway API. It provides methods for authentication, messaging, webhook management, and session control.

The client is safe for concurrent use.

func NewClient

func NewClient(opts ...Option) *Client

NewClient creates a new SDK client with the given options.

The client can be configured with functional options:

client := waga.NewClient(
    waga.WithBaseURL("https://api.example.com"),
    waga.WithTimeout(60*time.Second),
    waga.WithToken("existing-jwt-token"),
)

By default, the client uses:

func (*Client) CheckContact added in v0.6.0

func (c *Client) CheckContact(ctx context.Context, msisdn string) (*ContactCheckResponse, error)

CheckContact validates whether a recipient is registered on WhatsApp.

The msisdn argument can be a plain phone number or WhatsApp JID; the gateway normalizes it and returns canonical JID details.

func (*Client) CreateGroup added in v0.7.0

func (c *Client) CreateGroup(ctx context.Context, req CreateGroupRequest) (*CreateGroupResponse, error)

CreateGroup creates a group, or a community when req.IsCommunity is set. The response carries the new group's info plus a per-participant Results slice (invited/failed members are reported there, not as an error).

Group/community management is gated server-side by GROUP_MANAGEMENT_ENABLED; when disabled the whole mutation surface is unregistered (404).

Requires authentication (call Register or SetToken first).

func (*Client) DeleteGroupPhoto added in v0.7.0

func (c *Client) DeleteGroupPhoto(ctx context.Context, chat string) (*GroupPhotoResponse, error)

DeleteGroupPhoto removes a group's photo. chat must be a group JID ("@g.us").

Requires authentication (call Register or SetToken first).

func (*Client) DeleteMessage

func (c *Client) DeleteMessage(ctx context.Context, msisdn, messageID string) error

DeleteMessage deletes a previously sent message. The message will be removed from the chat for both sender and recipient.

The msisdn is the recipient's phone number, and messageID is the ID of the message to delete (returned by SendText or SendImage).

Requires authentication (call Register or SetToken first).

func (*Client) EditMessage

func (c *Client) EditMessage(ctx context.Context, msisdn, messageID, newMessage string) error

EditMessage edits a previously sent message. It replaces the message content with newMessage.

The msisdn is the recipient's phone number, and messageID is the ID of the message to edit (returned by SendText or SendImage).

Requires authentication (call Register or SetToken first).

func (*Client) GetAvatar added in v0.7.0

func (c *Client) GetAvatar(ctx context.Context, chat string, preview bool, priorID ...string) (*AvatarResponse, error)

GetAvatar returns a chat's profile picture (user or group). preview requests the low-res thumbnail instead of the full-resolution image.

The returned AvatarResponse.ID doubles as an ETag. Pass it back on a later call as the optional priorID to enable conditional fetching: if the picture is unchanged the gateway replies 304 and this method returns ErrNotModified (check with errors.Is(err, waga.ErrNotModified)). A chat with no picture returns ErrNotFound (404); a hidden picture returns ErrForbidden (403).

Requires authentication (call Register or SetToken first).

func (*Client) GetContactInfo added in v0.7.0

func (c *Client) GetContactInfo(ctx context.Context, chat string) (*ContactInfoResponse, error)

GetContactInfo returns a server-side profile lookup for one user: status text, current picture id, verified business name, linked-device count, and lid.

chat is the canonical recipient (a bare number, "@s.whatsapp.net", or "@lid").

Requires authentication (call Register or SetToken first).

func (*Client) GetGroupInfo added in v0.7.0

func (c *Client) GetGroupInfo(ctx context.Context, chat string) (*GroupInfoResponse, error)

GetGroupInfo returns a single group's full detail plus its participant roster.

chat must be a group JID ("@g.us"). The account must be a member: a non-member group returns ErrForbidden (403), an absent group returns ErrNotFound (404).

Requires authentication (call Register or SetToken first).

func (*Client) GetGroupInviteInfo added in v0.7.0

func (c *Client) GetGroupInviteInfo(ctx context.Context, code string) (*GroupInfoResponse, error)

GetGroupInviteInfo previews the group behind an invite code without joining. code accepts a full chat.whatsapp.com link or a bare code. A revoked link returns an error matching ErrGone (410) — check with errors.Is(err, waga.ErrGone).

Requires authentication (call Register or SetToken first).

func (c *Client) GetGroupInviteLink(ctx context.Context, chat string) (*GroupInviteLinkResponse, error)

GetGroupInviteLink returns a group's admin invite link. chat must be a group JID ("@g.us").

Requires authentication (call Register or SetToken first).

func (*Client) GetIncomingMessages added in v0.2.0

func (c *Client) GetIncomingMessages(ctx context.Context, limit int) (*IncomingMessagesResponse, error)

GetIncomingMessages fetches the most recent incoming messages buffered by the gateway for the authenticated session, newest first.

The limit parameter caps the number of messages returned. If limit <= 0, the gateway substitutes its default (10). Values above the gateway's maximum (50) are clamped server-side.

Requires authentication (call Register or SetToken first).

func (*Client) GetJobStatus added in v0.4.0

func (c *Client) GetJobStatus(ctx context.Context, jobID string) (*JobStatusResponse, error)

GetJobStatus fetches the status of an asynchronously queued message job.

When the gateway runs in queue mode, send endpoints return 202 Accepted with a job ID instead of a message ID. Use this method to poll the job until its Status is "completed" or "failed".

Requires authentication (call Register or SetToken first).

func (*Client) GetLoginStatus

func (c *Client) GetLoginStatus(ctx context.Context) (*LoginStatus, error)

GetLoginStatus returns the current WhatsApp session status. It checks whether the WhatsApp session is authenticated and active.

Requires authentication (call Register or SetToken first).

func (*Client) GetPairCode

func (c *Client) GetPairCode(ctx context.Context) (*LoginPairResponse, error)

GetPairCode generates a pair code for WhatsApp login. The pair code is an 8-character code that can be used to link the device without scanning a QR code. Users can enter the code in WhatsApp > Linked Devices.

The pair code expires after the duration specified in ExpiresIn.

Requires authentication (call Register or SetToken first).

func (*Client) GetQRCode

func (c *Client) GetQRCode(ctx context.Context, format string) (*LoginQrResponse, error)

GetQRCode generates a QR code for WhatsApp login. The format parameter specifies the response format:

  • "json": returns a base64-encoded PNG image
  • "html": returns an HTML img tag with the embedded QR code

The QR code can be displayed to users for scanning with the WhatsApp mobile app to link their account. The QR code expires after the duration specified in ExpiresIn.

Requires authentication (call Register or SetToken first).

func (*Client) GetToken

func (c *Client) GetToken() string

GetToken returns the current JWT token stored in the client. Returns an empty string if no token has been set.

func (*Client) GetWebhook

func (c *Client) GetWebhook(ctx context.Context) (*WebhookResponse, error)

GetWebhook returns the currently registered webhook URL. Use this to check if a webhook is configured and retrieve its URL.

Requires authentication (call Register or SetToken first).

func (*Client) Health

func (c *Client) Health(ctx context.Context) (*HealthResponse, error)

Health checks the health status of the gateway service. This method does not require authentication and can be used to verify that the service is running and accessible.

func (*Client) JoinGroup added in v0.7.0

func (c *Client) JoinGroup(ctx context.Context, code string) (*JoinGroupResponse, error)

JoinGroup joins a group by invite code (a full chat.whatsapp.com link or a bare code). This mass-join vector is gated server-side by GROUP_JOIN_VIA_LINK_ENABLED (403 when disabled).

Requires authentication (call Register or SetToken first).

func (*Client) LeaveGroup added in v0.7.0

func (c *Client) LeaveGroup(ctx context.Context, chat string) error

LeaveGroup leaves a group. chat must be a group JID ("@g.us"). Allowed for non-admins.

Requires authentication (call Register or SetToken first).

func (*Client) LinkSubGroup added in v0.7.0

func (c *Client) LinkSubGroup(ctx context.Context, chat, childJID string) error

LinkSubGroup links a sub-group under a parent community. chat is the parent community JID and childJID is the sub-group JID (both "@g.us").

Requires authentication (call Register or SetToken first).

func (*Client) ListCommunityParticipants added in v0.7.0

func (c *Client) ListCommunityParticipants(ctx context.Context, chat string) (*CommunityParticipantsResponse, error)

ListCommunityParticipants lists every participant across a community's linked groups. chat must be the community JID ("@g.us").

Requires authentication (call Register or SetToken first).

func (*Client) ListContacts added in v0.7.0

func (c *Client) ListContacts(ctx context.Context, limit, offset int) (*ContactListResponse, error)

ListContacts returns a page of the account's locally-synced contacts. limit (gateway default 100, max 500) and offset paginate the result. An empty address book is not an error — this endpoint never 404s on empty.

Requires authentication (call Register or SetToken first).

func (*Client) ListGroups added in v0.7.0

func (c *Client) ListGroups(ctx context.Context) (*GroupListResponse, error)

ListGroups returns the account's joined groups as lightweight summaries (no participant roster; use GetGroupInfo for one group's full detail).

This is a server-hitting read subject to a per-account budget; when the budget is exhausted the gateway returns 429 (ErrRateLimited).

Requires authentication (call Register or SetToken first).

func (*Client) ListJoinRequests added in v0.7.0

func (c *Client) ListJoinRequests(ctx context.Context, chat string) (*GroupJoinRequestsResponse, error)

ListJoinRequests lists a group's pending join requests. chat must be a group JID ("@g.us").

Requires authentication (call Register or SetToken first).

func (*Client) ListSubGroups added in v0.7.0

func (c *Client) ListSubGroups(ctx context.Context, chat string) (*SubGroupListResponse, error)

ListSubGroups lists the sub-groups linked under a community. chat must be the community JID ("@g.us"). An empty list is not an error.

Requires authentication (call Register or SetToken first).

func (*Client) Logout

func (c *Client) Logout(ctx context.Context) error

Logout logs out from the current WhatsApp session. This disconnects the WhatsApp account and invalidates the session. After logging out, you'll need to authenticate again using GetQRCode or GetPairCode.

Requires authentication (call Register or SetToken first).

func (*Client) MarkRead added in v0.7.0

func (c *Client) MarkRead(ctx context.Context, chat string, messageIDs []string, sender string) error

MarkRead marks one or more messages in a chat as read (blue ticks).

chat is the canonical recipient. sender is the message author's JID/number and is required for group chats (pass "" for one-to-one chats).

Requires authentication (call Register or SetToken first).

func (*Client) React

func (c *Client) React(ctx context.Context, msisdn, messageID, emoji string, senderMsisdn ...string) error

React adds a reaction emoji to a previously sent message. The emoji will appear on the message for all participants in the chat.

The msisdn is the recipient's phone number, messageID is the ID of the message to react to, and emoji is the emoji character(s) to use (e.g., "👍", "❤️").

Requires authentication (call Register or SetToken first).

func (*Client) Reconnect

func (c *Client) Reconnect(ctx context.Context) error

Reconnect attempts to reconnect to the existing WhatsApp session. Use this method if the connection to WhatsApp has been lost but the session is still valid. It attempts to restore the connection without requiring re-authentication.

Requires authentication (call Register or SetToken first).

func (*Client) Register

func (c *Client) Register(ctx context.Context, phoneNumber, secretKey string) (*RegisterResponse, error)

Register registers a new phone number with the WhatsApp Gateway service. It retrieves and stores a JWT token for subsequent API requests.

The phoneNumber should be in international format without the "+" symbol (e.g., "6281234567890"). The secretKey is provided by the gateway service.

After successful registration, the token is automatically stored in the client and used for all authenticated requests.

func (*Client) RegisterWebhook

func (c *Client) RegisterWebhook(ctx context.Context, url, hmacSecret string) error

RegisterWebhook registers a URL to receive webhook events. Webhooks allow you to receive real-time notifications for incoming and outgoing messages.

The url parameter is the endpoint where webhook events will be sent. The hmacSecret parameter is optional but highly recommended for verifying webhook signatures. If provided, all webhook payloads will include an X-Webhook-Signature header that can be verified using the WebhookVerifier.

Requires authentication (call Register or SetToken first).

func (c *Client) ResetGroupInviteLink(ctx context.Context, chat string) (*GroupInviteLinkResponse, error)

ResetGroupInviteLink revokes a group's current invite link and returns a freshly generated one. chat must be a group JID ("@g.us").

Requires authentication (call Register or SetToken first).

func (*Client) ReviewJoinRequests added in v0.7.0

func (c *Client) ReviewJoinRequests(ctx context.Context, chat, action string, participants []string) (*GroupJoinRequestsActionResponse, error)

ReviewJoinRequests approves or rejects pending join requests. action is "approve" or "reject". chat must be a group JID ("@g.us").

Partial success is a 200: inspect the returned Results for each participant.

Requires authentication (call Register or SetToken first).

func (*Client) SendAudio added in v0.6.0

func (c *Client) SendAudio(ctx context.Context, msisdn string, audio io.Reader, isPTT, isViewOnce bool, opts ...SendOption) (*SendMessageResponse, error)

SendAudio sends an audio message to the specified recipient.

The audio parameter is an io.Reader containing the audio file bytes. If isPTT is true, WhatsApp renders the audio as a voice note bubble. If isViewOnce is true, the media is sent as view-once.

func (*Client) SendChatPresence added in v0.7.0

func (c *Client) SendChatPresence(ctx context.Context, chat, state string) error

SendChatPresence sets the typing indicator in a chat. state must be one of PresenceComposing ("typing…"), PresenceRecording ("recording audio…"), or PresencePaused (cleared).

chat is the canonical recipient.

Requires authentication (call Register or SetToken first).

func (*Client) SendDocument added in v0.6.0

func (c *Client) SendDocument(ctx context.Context, msisdn string, document io.Reader, fileName, caption string, opts ...SendOption) (*SendMessageResponse, error)

SendDocument sends a document message to the specified recipient.

fileName and caption are optional. When fileName is empty, the gateway uses its own default naming.

func (*Client) SendImage

func (c *Client) SendImage(ctx context.Context, msisdn string, image io.Reader, caption string, isViewOnce bool, opts ...SendOption) (*SendMessageResponse, error)

SendImage sends an image message to the specified recipient.

The msisdn parameter should be the recipient's phone number in WhatsApp JID format. The image parameter is an io.Reader containing the image data (JPEG, PNG, etc.). The caption parameter is optional text to accompany the image. The isViewOnce parameter, if true, sends the image as a view-once media that disappears after being viewed.

Example:

file, _ := os.Open("image.jpg")
defer file.Close()
resp, err := client.SendImage(ctx, msisdn, file, "Check this out!", false)

Requires authentication (call Register or SetToken first).

func (*Client) SendLocation added in v0.3.0

func (c *Client) SendLocation(ctx context.Context, msisdn string, latitude, longitude float64, name, address string, opts ...SendOption) (*SendMessageResponse, error)

SendLocation sends a location message to the specified recipient.

The msisdn parameter should be the recipient's phone number in WhatsApp JID format. The latitude and longitude specify the geographic coordinates. The name and address parameters are optional metadata for the location pin.

Requires authentication (call Register or SetToken first).

func (*Client) SendPoll added in v0.3.0

func (c *Client) SendPoll(ctx context.Context, msisdn, question string, options []string, selectableCount int, opts ...SendOption) (*SendMessageResponse, error)

SendPoll sends a poll message to the specified recipient.

The msisdn parameter should be the recipient's phone number in WhatsApp JID format. The question is the poll question text, and options is the list of answer choices. The selectableCount limits how many options a user can select (0 means no limit).

Requires authentication (call Register or SetToken first).

func (*Client) SendSticker added in v0.3.0

func (c *Client) SendSticker(ctx context.Context, msisdn string, sticker io.Reader, opts ...SendOption) (*SendMessageResponse, error)

SendSticker sends a sticker message to the specified recipient.

The msisdn parameter should be the recipient's phone number in WhatsApp JID format. The sticker parameter is an io.Reader containing the sticker data (WebP format).

Example:

file, _ := os.Open("sticker.webp")
defer file.Close()
resp, err := client.SendSticker(ctx, msisdn, file)

Requires authentication (call Register or SetToken first).

func (*Client) SendText

func (c *Client) SendText(ctx context.Context, msisdn, message string, opts ...SendOption) (*SendMessageResponse, error)

SendText sends a text message to the specified recipient.

The msisdn parameter should be the recipient's phone number in WhatsApp JID format. Use the FormatMSISDN() helper function to convert a phone number to the correct format:

msisdn := waga.FormatMSISDN("6281234567890")  // "6281234567890@s.whatsapp.net"

For group messages, use the group ID in the format "groupId@g.us".

Requires authentication (call Register or SetToken first).

func (*Client) SendVideo added in v0.6.0

func (c *Client) SendVideo(ctx context.Context, msisdn string, video io.Reader, caption string, isGif, isViewOnce bool, opts ...SendOption) (*SendMessageResponse, error)

SendVideo sends a video message to the specified recipient.

The video parameter is an io.Reader containing the video file bytes. caption is optional. isGif toggles GIF-like rendering. isViewOnce controls view-once behavior.

func (*Client) SetGroupName added in v0.7.0

func (c *Client) SetGroupName(ctx context.Context, chat, name string) error

SetGroupName sets a group's name (subject), max 25 characters. chat must be a group JID ("@g.us").

Requires authentication (call Register or SetToken first).

func (*Client) SetGroupPhoto added in v0.7.0

func (c *Client) SetGroupPhoto(ctx context.Context, chat string, jpeg io.Reader) (*GroupPhotoResponse, error)

SetGroupPhoto sets a group's photo from a JPEG stream. chat must be a group JID ("@g.us").

Requires authentication (call Register or SetToken first).

func (*Client) SetGroupSettings added in v0.7.0

func (c *Client) SetGroupSettings(ctx context.Context, chat string, announce, locked *bool) (*GroupSettingsResponse, error)

SetGroupSettings toggles group-level settings. Pass a non-nil announce (only admins can send) and/or locked (only admins can edit group info); at least one must be supplied. chat must be a group JID ("@g.us").

Requires authentication (call Register or SetToken first).

func (*Client) SetGroupTopic added in v0.7.0

func (c *Client) SetGroupTopic(ctx context.Context, chat, topic string) error

SetGroupTopic sets a group's topic (description), max 512 characters; an empty topic clears it. chat must be a group JID ("@g.us").

Requires authentication (call Register or SetToken first).

func (*Client) SetToken

func (c *Client) SetToken(token string)

SetToken sets the JWT token for authentication. Use this method if you already have a valid token from a previous registration. The token will be used for all subsequent API requests that require authentication.

func (*Client) UnlinkSubGroup added in v0.7.0

func (c *Client) UnlinkSubGroup(ctx context.Context, chat, childJID string) error

UnlinkSubGroup unlinks a sub-group from its parent community. chat is the parent community JID and childJID is the sub-group JID (both "@g.us").

Requires authentication (call Register or SetToken first).

func (*Client) UnregisterWebhook

func (c *Client) UnregisterWebhook(ctx context.Context) error

UnregisterWebhook removes the currently registered webhook URL. After calling this method, webhook events will no longer be sent to the previously configured endpoint.

Requires authentication (call Register or SetToken first).

func (*Client) UpdateGroupParticipants added in v0.7.0

func (c *Client) UpdateGroupParticipants(ctx context.Context, chat, action string, participants []string) (*GroupParticipantsResponse, error)

UpdateGroupParticipants mutates a group's roster. action is one of "add", "remove", "promote", or "demote". chat must be a group JID ("@g.us").

Partial success is a 200: inspect the returned Results for each participant's status ("ok", "invited" when a privacy-blocked add became an invite, or "failed" with a Code). "add" is gated server-side by GROUP_ADD_PARTICIPANTS_ENABLED (403 when disabled).

Requires authentication (call Register or SetToken first).

type CommunityParticipantItem added in v0.7.0

type CommunityParticipantItem struct {
	JID string `json:"jid"`
}

CommunityParticipantItem is one member across a community's linked groups.

type CommunityParticipantsResponse added in v0.7.0

type CommunityParticipantsResponse struct {
	Participants []CommunityParticipantItem `json:"participants"`
	Count        int                        `json:"count"`
}

CommunityParticipantsResponse is every participant across a community's linked groups (Count == len(Participants)).

type ContactCheckResponse added in v0.6.0

type ContactCheckResponse struct {
	// Query is the original queried number.
	Query string `json:"query"`
	// JID is the canonical WhatsApp JID.
	JID string `json:"jid"`
	// IsOnWhatsApp indicates whether the number is registered on WhatsApp.
	IsOnWhatsApp bool `json:"is_on_whatsapp"`
	// VerifiedName is the business verified name, if available.
	VerifiedName *string `json:"verified_name,omitempty"`
}

ContactCheckResponse is the result of validating a recipient number on WhatsApp.

type ContactInfoResponse added in v0.7.0

type ContactInfoResponse struct {
	JID          string `json:"jid"`
	Status       string `json:"status,omitempty"`
	PictureID    string `json:"picture_id,omitempty"`
	VerifiedName string `json:"verified_name,omitempty"`
	DeviceCount  int    `json:"device_count"`
	LID          string `json:"lid,omitempty"`
}

ContactInfoResponse is a server-side profile lookup for one user: status text, current picture id, verified business name, linked-device count, and lid.

type ContactListItem added in v0.7.0

type ContactListItem struct {
	JID          string `json:"jid"`
	PushName     string `json:"push_name,omitempty"`
	FullName     string `json:"full_name,omitempty"`
	FirstName    string `json:"first_name,omitempty"`
	BusinessName string `json:"business_name,omitempty"`
}

ContactListItem is one entry in the account's locally-synced contact list.

type ContactListResponse added in v0.7.0

type ContactListResponse struct {
	// Contacts is this page of synced contacts.
	Contacts []ContactListItem `json:"contacts"`
	// Count is the number of items in this page.
	Count int `json:"count"`
	// Total is the total number of synced contacts.
	Total int `json:"total"`
	// Note is an optional gateway-supplied advisory (e.g. sync status).
	Note string `json:"note,omitempty"`
}

ContactListResponse is a page of the account's locally-synced contacts. The list reflects the synced address book, so an empty or partial result is not an error (GET /contact/ never 404s on empty).

type CreateGroupRequest added in v0.7.0

type CreateGroupRequest struct {
	Name                   string   `json:"name"`
	Participants           []string `json:"participants,omitempty"`
	IsCommunity            bool     `json:"is_community,omitempty"`
	LinkedParentJID        string   `json:"linked_parent_jid,omitempty"`
	IsAnnounce             bool     `json:"is_announce,omitempty"`
	IsLocked               bool     `json:"is_locked,omitempty"`
	IsJoinApprovalRequired bool     `json:"is_join_approval_required,omitempty"`
}

CreateGroupRequest creates a group, or a community when IsCommunity is true. An empty Participants list creates a group of just the account; adding participants at creation is gated server-side by GROUP_ADD_PARTICIPANTS_ENABLED.

type CreateGroupResponse added in v0.7.0

type CreateGroupResponse struct {
	GroupJID  string              `json:"group_jid"`
	GroupInfo *GroupInfoResponse  `json:"group_info"`
	Results   []ParticipantResult `json:"results"`
}

CreateGroupResponse echoes the new group's JID, full info, and per-participant add results.

type ErrorResponse

type ErrorResponse struct {
	// Error is a human-readable error message
	Error string `json:"error"`
	// Code is the HTTP status code or API error code
	Code int `json:"code"`
}

ErrorResponse represents an error response from the API.

type GroupInfoResponse added in v0.7.0

type GroupInfoResponse struct {
	JID              string                 `json:"jid"`
	Name             string                 `json:"name,omitempty"`
	Topic            string                 `json:"topic,omitempty"`
	OwnerJID         string                 `json:"owner_jid,omitempty"`
	ParticipantCount int                    `json:"participant_count"`
	IsAnnounce       bool                   `json:"is_announce"`
	IsLocked         bool                   `json:"is_locked"`
	IsCommunity      bool                   `json:"is_community"`
	IsEphemeral      bool                   `json:"is_ephemeral"`
	Participants     []GroupParticipantItem `json:"participants"`
}

GroupInfoResponse is the full detail of a single group, including its member roster. Requires the account to be a participant (403 otherwise, 404 if absent).

type GroupInviteLinkResponse added in v0.7.0

type GroupInviteLinkResponse struct {
	Chat       string `json:"chat"`        // resolved @g.us JID
	InviteLink string `json:"invite_link"` // https://chat.whatsapp.com/<code>
}

GroupInviteLinkResponse is a group's invite link.

type GroupJoinRequestItem added in v0.7.0

type GroupJoinRequestItem struct {
	JID         string `json:"jid"`
	RequestedAt string `json:"requested_at,omitempty"` // RFC3339
}

GroupJoinRequestItem is one pending join request.

type GroupJoinRequestsActionResponse added in v0.7.0

type GroupJoinRequestsActionResponse struct {
	Chat    string              `json:"chat"`
	Action  string              `json:"action"`
	Results []ParticipantResult `json:"results"`
}

GroupJoinRequestsActionResponse is the per-participant outcome of an approve/reject (partial success is a 200).

type GroupJoinRequestsResponse added in v0.7.0

type GroupJoinRequestsResponse struct {
	Chat     string                 `json:"chat"`
	Requests []GroupJoinRequestItem `json:"requests"`
	Count    int                    `json:"count"`
}

GroupJoinRequestsResponse lists a group's pending join requests.

type GroupListItem added in v0.7.0

type GroupListItem struct {
	JID              string `json:"jid"` // the group's @g.us JID
	Name             string `json:"name,omitempty"`
	Topic            string `json:"topic,omitempty"`
	OwnerJID         string `json:"owner_jid,omitempty"`
	ParticipantCount int    `json:"participant_count"`
	IsAnnounce       bool   `json:"is_announce"`  // only admins can send
	IsLocked         bool   `json:"is_locked"`    // only admins can edit group info
	IsCommunity      bool   `json:"is_community"` // this group is a community parent
}

GroupListItem is one entry in the account's joined-groups list (a lightweight summary with no participant roster).

type GroupListResponse added in v0.7.0

type GroupListResponse struct {
	Groups []GroupListItem `json:"groups"`
	Count  int             `json:"count"`
}

GroupListResponse is the account's joined groups. Not paginated; Count always equals len(Groups).

type GroupParticipantItem added in v0.7.0

type GroupParticipantItem struct {
	JID          string `json:"jid"`
	PhoneNumber  string `json:"phone_number,omitempty"`
	LID          string `json:"lid,omitempty"`
	IsAdmin      bool   `json:"is_admin"`
	IsSuperAdmin bool   `json:"is_super_admin"`
}

GroupParticipantItem is one member in a group's roster.

type GroupParticipantsResponse added in v0.7.0

type GroupParticipantsResponse struct {
	GroupJID string              `json:"group_jid"`
	Action   string              `json:"action"`
	Results  []ParticipantResult `json:"results"`
}

GroupParticipantsResponse is the per-participant outcome of a roster mutation (partial success is a 200, never an overall error).

type GroupPhotoResponse added in v0.7.0

type GroupPhotoResponse struct {
	PictureID string `json:"picture_id,omitempty"`
	Removed   bool   `json:"removed,omitempty"`
}

GroupPhotoResponse reports the new picture id (on set) or removal.

type GroupSettingsResponse added in v0.7.0

type GroupSettingsResponse struct {
	GroupJID string   `json:"group_jid"`
	Applied  []string `json:"applied"`
}

GroupSettingsResponse reports which settings were applied (e.g. ["announce"]).

type HealthComponent added in v0.7.0

type HealthComponent struct {
	Connected bool `json:"connected"`
	Enabled   bool `json:"enabled,omitempty"` // queue only
}

HealthComponent is one dependency's health in a readiness probe.

type HealthReadyResponse added in v0.7.0

type HealthReadyResponse struct {
	Status    string           `json:"status"` // "ready" | "not_ready"
	Timestamp string           `json:"timestamp"`
	Database  *HealthComponent `json:"database,omitempty"`
	Queue     *HealthComponent `json:"queue,omitempty"`
}

HealthReadyResponse is the GET /health/ready body (returned for both 200 "ready" and 503 "not_ready"). Inspect Status to branch.

type HealthResponse

type HealthResponse struct {
	// Status is the health status of the service (e.g., "ok", "degraded")
	Status string `json:"status"`
	// Timestamp is the ISO 8601 timestamp of the health check
	Timestamp string `json:"timestamp"`
}

HealthResponse represents the response from Health.

type IncomingEventMessage

type IncomingEventMessage string

IncomingEventMessage represents the type of incoming message event.

const (
	// IncomingEventMessageMessageIncoming is emitted when a new message is received
	IncomingEventMessageMessageIncoming IncomingEventMessage = "message.incoming"
)

type IncomingMessage added in v0.2.0

type IncomingMessage struct {
	// MessageId is the unique identifier for the message
	MessageId string `json:"message_id"`
	// Chat is the chat ID where the message was received
	Chat string `json:"chat"`
	// From is the sender's phone number in WhatsApp JID format
	From string `json:"from"`
	// IsGroup indicates whether the message was received in a group chat
	IsGroup bool `json:"is_group"`
	// AddressingMode is the JID addressing mode of the chat ("pn" or "lid").
	AddressingMode string `json:"addressing_mode,omitempty"`
	// PushName is the display name of the sender
	PushName string `json:"push_name"`
	// Timestamp is the Unix timestamp when the message was received
	Timestamp int64 `json:"timestamp"`
	// Text is the text content of the message (for text messages)
	Text string `json:"text,omitempty"`
	// Type is the message type (text, image, video, audio, or document)
	Type IncomingMessageType `json:"type"`
	// Media contains media metadata for non-text messages.
	// Note: Url is not populated by /message/incoming in v1; only Type, MimeType,
	// Size, Filename, and Caption are filled. Use webhooks for media URLs.
	Media *IncomingMessageMediaInfo `json:"media,omitempty"`
}

IncomingMessage represents a single received message returned by the GET /message/incoming endpoint. Field names mirror IncomingWebhookPayload (minus the Event envelope) so consumers see a consistent vocabulary across the webhook delivery and the polled inbox.

type IncomingMessageMediaInfo

type IncomingMessageMediaInfo struct {
	// Type is the media type (image, video, audio, or document)
	Type IncomingMessageType `json:"type"`
	// Url is the direct URL to download the media file. In webhooks it mirrors
	// StorageURL when the gateway stored the media, otherwise WhatsappURL.
	Url string `json:"url"`
	// StorageURL is the gateway-hosted URL when the media was downloaded and
	// stored (webhooks only)
	StorageURL string `json:"storage_url,omitempty"`
	// WhatsappURL is the raw WhatsApp media URL used when storage was skipped
	// or failed (webhooks only)
	WhatsappURL string `json:"whatsapp_url,omitempty"`
	// MimeType is the MIME type of the media file
	MimeType string `json:"mime_type"`
	// Filename is the original filename of the media file (if applicable)
	Filename string `json:"filename,omitempty"`
	// Caption is the text caption accompanying the media (if applicable)
	Caption string `json:"caption,omitempty"`
	// Size is the file size in bytes (if available)
	Size int `json:"size,omitempty"`
	// Sha256 is the hex-encoded SHA-256 hash of the media file (webhooks only)
	Sha256 string `json:"sha256,omitempty"`
}

IncomingMessageMediaInfo contains media information for incoming messages with attachments. This includes images, videos, audio, and documents.

type IncomingMessageType

type IncomingMessageType string

IncomingMessageType represents the type of incoming message content.

const (
	// IncomingMessageTypeText represents a text message
	IncomingMessageTypeText IncomingMessageType = "text"
	// IncomingMessageTypeImage represents an image message
	IncomingMessageTypeImage IncomingMessageType = "image"
	// IncomingMessageTypeVideo represents a video message
	IncomingMessageTypeVideo IncomingMessageType = "video"
	// IncomingMessageTypeAudio represents an audio message
	IncomingMessageTypeAudio IncomingMessageType = "audio"
	// IncomingMessageTypeDocument represents a document message
	IncomingMessageTypeDocument IncomingMessageType = "document"
	// IncomingMessageTypeSticker represents a sticker message
	IncomingMessageTypeSticker IncomingMessageType = "sticker"
	// IncomingMessageTypeLocation represents a location message.
	// Webhook payloads carry Latitude/Longitude/Name/Address; the polled
	// /message/incoming endpoint reports only the type.
	IncomingMessageTypeLocation IncomingMessageType = "location"
	// IncomingMessageTypePoll represents a poll message.
	// Webhook payloads carry Question/Options/SelectableCount.
	IncomingMessageTypePoll IncomingMessageType = "poll"
	// IncomingMessageTypeContact represents a contact message (reported only
	// by the polled /message/incoming endpoint; no detail fields)
	IncomingMessageTypeContact IncomingMessageType = "contact"
	// IncomingMessageTypeUnknown is reported for message types the gateway
	// does not specifically model
	IncomingMessageTypeUnknown IncomingMessageType = "unknown"
)

type IncomingMessagesResponse added in v0.2.0

type IncomingMessagesResponse struct {
	// Success indicates the request completed successfully
	Success bool `json:"success"`
	// Timestamp is the Unix milliseconds when the response was generated
	Timestamp int64 `json:"timestamp"`
	// Count is the number of messages returned (<= requested limit)
	Count int `json:"count"`
	// Messages is the list of incoming messages, newest first
	Messages []IncomingMessage `json:"messages"`
}

IncomingMessagesResponse represents the response from the GET /message/incoming endpoint. Messages are sorted newest first.

type IncomingWebhookPayload

type IncomingWebhookPayload struct {
	// Event is the type of event (always "message.incoming")
	Event IncomingEventMessage `json:"event"`
	// Chat is the chat ID where the message was received
	Chat string `json:"chat"`
	// From is the sender's phone number in WhatsApp JID format
	From string `json:"from"`
	// IsGroup indicates whether the message was received in a group chat
	IsGroup bool `json:"is_group"`
	// AddressingMode is the JID addressing mode of the chat ("pn" or "lid").
	AddressingMode string `json:"addressing_mode,omitempty"`
	// MessageId is the unique identifier for the message
	MessageId string `json:"message_id"`
	// PushName is the display name of the sender
	PushName string `json:"push_name"`
	// Timestamp is the Unix timestamp when the message was received
	Timestamp int64 `json:"timestamp"`
	// Text is the text content of the message (for text messages)
	Text string `json:"text,omitempty"`
	// Type is the message type (see IncomingMessageType constants)
	Type IncomingMessageType `json:"type"`
	// Media contains media information for image/video/audio/document/sticker
	Media *IncomingMessageMediaInfo `json:"media,omitempty"`

	// Location fields (Type == IncomingMessageTypeLocation).
	// Latitude/Longitude are pointers because (0,0) is a valid location.
	Latitude  *float64 `json:"latitude,omitempty"`
	Longitude *float64 `json:"longitude,omitempty"`
	Name      string   `json:"name,omitempty"`
	Address   string   `json:"address,omitempty"`

	// Poll fields (Type == IncomingMessageTypePoll).
	Question        string   `json:"question,omitempty"`
	Options         []string `json:"options,omitempty"`
	SelectableCount int      `json:"selectable_count,omitempty"`
}

IncomingWebhookPayload represents the payload sent to webhooks for incoming message events. This contains all information about received messages.

type JobStatusResponse added in v0.4.0

type JobStatusResponse struct {
	// JobID is the identifier returned when the message was queued
	JobID string `json:"job_id"`
	// Status is one of "queued", "processing", "completed", or "failed"
	Status string `json:"status"`
	// MessageID is the WhatsApp message ID once the job completed
	MessageID *string `json:"message_id,omitempty"`
	// Error describes why the job failed, if it did
	Error *string `json:"error,omitempty"`
	// CreatedAt is when the job was created
	CreatedAt string `json:"created_at"`
	// CompletedAt is when the job finished, if it has
	CompletedAt *string `json:"completed_at,omitempty"`
}

JobStatusResponse represents the response from the GET /message/job/:job_id endpoint, used to poll the status of an asynchronously queued message job.

type JoinGroupResponse added in v0.7.0

type JoinGroupResponse struct {
	GroupJID string `json:"group_jid"`
}

JoinGroupResponse is the JID whatsmeow returned for a join-by-link (the group, or a pending membership-approval request — whatsmeow does not distinguish).

type LoginPairResponse

type LoginPairResponse struct {
	// PairCode is the 8-character code for linking the device
	PairCode string `json:"pair_code"`
	// ExpiresIn is the number of seconds until the pair code expires
	ExpiresIn int `json:"expires_in"`
}

LoginPairResponse represents the response when requesting a pair code for login. The pair code can be used to link the WhatsApp account without scanning a QR code.

type LoginQrResponse

type LoginQrResponse struct {
	// QrCode is the base64-encoded QR code image or HTML img tag
	QrCode string `json:"qr_code"`
	// ExpiresIn is the number of seconds until the QR code expires
	ExpiresIn int `json:"expires_in"`
}

LoginQrResponse represents the response when requesting a QR code for login. The QR code can be displayed to users for scanning with the WhatsApp mobile app.

type LoginStatus

type LoginStatus struct {
	// Authenticated is true if the session is active and authenticated
	Authenticated bool `json:"authenticated"`
}

LoginStatus represents the response from GetLoginStatus. It indicates whether the WhatsApp session is currently authenticated.

type MarkReadRequest added in v0.7.0

type MarkReadRequest struct {
	// Chat is the canonical recipient (see SendMessageTextRequest.Chat).
	Chat string `json:"chat"`
	// MessageIDs are the message IDs to mark read.
	MessageIDs []string `json:"message_ids"`
	// Sender is the message author's JID/number; required for group chats.
	Sender string `json:"sender,omitempty"`
}

MarkReadRequest marks one or more messages in a chat as read (blue ticks).

type MessageDeleteRequest

type MessageDeleteRequest struct {
	// MessageId is the ID of the message to delete
	MessageId string `json:"message_id"`
	// Msisdn is the recipient's phone number in WhatsApp JID format
	Msisdn string `json:"msisdn"`
}

MessageDeleteRequest represents a request to delete a previously sent message.

type MessageEditRequest

type MessageEditRequest struct {
	// MessageId is the ID of the message to edit
	MessageId string `json:"message_id"`
	// Msisdn is the recipient's phone number in WhatsApp JID format
	Msisdn string `json:"msisdn"`
	// NewMessage is the updated message content
	NewMessage string `json:"new_message"`
}

MessageEditRequest represents a request to edit a previously sent message.

type MessageReactRequest

type MessageReactRequest struct {
	// MessageId is the ID of the message to react to
	MessageId string `json:"message_id"`
	// Msisdn is the recipient's phone number in WhatsApp JID format
	Msisdn string `json:"msisdn"`
	// Emoji is the emoji reaction to add
	Emoji string `json:"emoji"`
	// SenderMsisdn is optional and should be set when reacting to an incoming
	// message from another sender (e.g. group chats).
	SenderMsisdn string `json:"sender_msisdn,omitempty"`
}

MessageReactRequest represents a request to add a reaction to a message.

type Option

type Option func(*Client)

Option configures the SDK client. Options are applied when creating a new client with NewClient.

func WithAdminSecret added in v0.7.0

func WithAdminSecret(secret string) Option

WithAdminSecret sets the operator admin secret sent as the Bearer credential on admin-plane requests. It is an alias for WithToken, named for admin usage.

func WithBaseURL

func WithBaseURL(url string) Option

WithBaseURL sets the base URL for the API. Use this option to connect to a different WhatsApp Gateway server.

The default base URL is "http://localhost:3000/api/v1".

Example:

client := waga.NewClient(waga.WithBaseURL("https://api.example.com"))

func WithHTTPClient

func WithHTTPClient(client *http.Client) Option

WithHTTPClient sets a custom HTTP client for making API requests. Use this option to customize HTTP behavior such as timeouts, redirects, transport settings, or proxy configuration.

Example:

httpClient := &http.Client{
    Timeout: 60 * time.Second,
    Transport: &http.Transport{...},
}
client := waga.NewClient(waga.WithHTTPClient(httpClient))

func WithTimeout

func WithTimeout(timeout time.Duration) Option

WithTimeout sets the HTTP client timeout for API requests. The timeout applies to the entire HTTP request, including connection time, redirects, and reading the response body.

The default timeout is 30 seconds.

Example:

client := waga.NewClient(waga.WithTimeout(60 * time.Second))

func WithToken

func WithToken(token string) Option

WithToken sets a pre-existing JWT token for authentication. Use this option if you already have a valid token from a previous registration and want to skip the registration step.

The token will be used for all authenticated API requests.

Example:

client := waga.NewClient(waga.WithToken("your-jwt-token"))

func WithUserAgent

func WithUserAgent(ua string) Option

WithUserAgent sets a custom User-Agent header for API requests. Use this option to identify your application in API requests.

The default User-Agent is "WhatsApp-Gateway-SDK-Go/1.0".

Example:

client := waga.NewClient(waga.WithUserAgent("MyApp/1.0"))

type OutgoingEventMessage

type OutgoingEventMessage string

OutgoingEventMessage represents the type of outgoing message event. These events are sent to webhooks to track message delivery status.

const (
	// OutgoingEventMessageMessageQueued is emitted when a message is added to the queue
	OutgoingEventMessageMessageQueued OutgoingEventMessage = "message.queued"
	// OutgoingEventMessageMessageSent is emitted when a message is successfully sent
	OutgoingEventMessageMessageSent OutgoingEventMessage = "message.sent"
	// OutgoingEventMessageMessageFailed is emitted when a message fails to send
	OutgoingEventMessageMessageFailed OutgoingEventMessage = "message.failed"
)

type OutgoingWebhookPayload

type OutgoingWebhookPayload struct {
	// Event is the type of event that occurred
	Event OutgoingEventMessage `json:"event"`
	// JobId is the unique identifier for the message job
	JobId string `json:"job_id"`
	// To is the recipient's phone number in WhatsApp JID format
	To string `json:"to"`
	// PhoneNumber is the sender's phone number
	PhoneNumber string `json:"phone_number"`
	// Timestamp is the Unix timestamp of the event
	Timestamp int64 `json:"timestamp"`
	// MessageId is the unique identifier for the message
	MessageId string `json:"message_id"`
	// Error is the failure reason carried by message.failed (and populated on
	// message.sent/queued when a reason exists); empty on success.
	Error string `json:"error,omitempty"`
	// Metadata contains additional optional information about the message
	Metadata *map[string]interface{} `json:"metadata,omitempty"`
}

OutgoingWebhookPayload represents the payload sent to webhooks for outgoing message events. These events track the delivery status of messages sent through the API.

type ParticipantInvite added in v0.7.0

type ParticipantInvite struct {
	Code      string    `json:"code"`
	ExpiresAt time.Time `json:"expires_at,omitempty"`
}

ParticipantInvite is present on a ParticipantResult when an add was converted to an invite because the target's privacy blocks a direct add.

type ParticipantResult added in v0.7.0

type ParticipantResult struct {
	JID    string             `json:"jid"`
	LID    string             `json:"lid,omitempty"`
	Status string             `json:"status"`
	Code   int                `json:"code,omitempty"`
	Invite *ParticipantInvite `json:"invite,omitempty"`
}

ParticipantResult is the per-participant outcome shared by every batch mutation. Status is one of "ok", "invited" (add converted to an invite), or "failed"; Code carries the underlying error code when failed.

type PresenceState added in v0.7.0

type PresenceState = string

PresenceState is a chat typing-indicator state.

const (
	// PresenceComposing shows the "typing…" indicator.
	PresenceComposing PresenceState = "composing"
	// PresenceRecording shows the "recording audio…" indicator.
	PresenceRecording PresenceState = "recording"
	// PresencePaused clears the typing indicator.
	PresencePaused PresenceState = "paused"
)

type RegisterRequest

type RegisterRequest struct {
	PhoneNumber string `json:"phone_number"`
	SecretKey   string `json:"secret_key"`
}

RegisterRequest represents a registration request for a new phone number. It contains the phone number and secret key required to authenticate with the WhatsApp Gateway service and obtain a JWT token.

type RegisterResponse

type RegisterResponse struct {
	// Token is the JWT authentication token for API requests
	Token string `json:"token"`
}

RegisterResponse represents the response from a successful registration. It contains the JWT token that should be used for subsequent API requests.

type SDKError

type SDKError struct {
	// Code is the HTTP status code or API error code
	Code int `json:"code"`
	// Message is the error message describing what went wrong
	Message string `json:"error"`
	// TraceID is the gateway's X-Trace-ID for this request, if the response
	// carried one. Use it to correlate the failure with gateway logs.
	TraceID string `json:"-"`
}

SDKError represents an error returned by the WhatsApp Gateway API. It contains both an HTTP status code and a human-readable error message.

func NewSDKError

func NewSDKError(code int, message string) *SDKError

NewSDKError creates a new SDKError with the given code and message. Use this function to create custom SDK errors with specific codes and messages.

Example:

err := waga.NewSDKError(418, "I'm a teapot")

func (*SDKError) Error

func (e *SDKError) Error() string

func (*SDKError) Is

func (e *SDKError) Is(target error) bool

Is implements the errors.Is interface for error comparison. It allows SDKError instances to be compared using errors.Is().

Example:

if errors.Is(err, waga.ErrUnauthorized) {
    // Handle unauthorized error
}

type SendLocationMessageRequest added in v0.3.0

type SendLocationMessageRequest struct {
	// Chat is the canonical recipient (see SendMessageTextRequest.Chat).
	Chat string `json:"chat,omitempty"`
	// Msisdn is the recipient in WhatsApp JID format.
	//
	// Deprecated: use Chat. Msisdn remains a permanent back-compat alias.
	Msisdn string `json:"msisdn,omitempty"`
	// Latitude is the geographic latitude of the location
	Latitude float64 `json:"latitude"`
	// Longitude is the geographic longitude of the location
	Longitude float64 `json:"longitude"`
	// Name is the optional name of the location
	Name string `json:"name,omitempty"`
	// Address is the optional address of the location
	Address string `json:"address,omitempty"`
	// ReplyToID quotes an existing message by ID (optional).
	ReplyToID string `json:"reply_to_id,omitempty"`
	// ReplyToSender is the number/JID of the quoted message's author (optional).
	ReplyToSender string `json:"reply_to_sender,omitempty"`
	// ReplyToText is an optional caller-supplied preview of the quoted message.
	ReplyToText string `json:"reply_to_text,omitempty"`
	// Mentions are the numbers/JIDs to @-tag (optional).
	Mentions []string `json:"mentions,omitempty"`
}

SendLocationMessageRequest represents a request to send a location message.

type SendMessageResponse

type SendMessageResponse struct {
	// Success indicates whether the message was successfully sent or queued
	Success bool `json:"success"`
	// MessageId is the unique identifier for the sent message (direct mode)
	MessageId string `json:"message_id,omitempty"`
	// Status is the job status in queue mode (e.g. "queued")
	Status string `json:"status,omitempty"`
	// JobID identifies the queued job in queue mode; poll it with GetJobStatus
	JobID string `json:"job_id,omitempty"`
	// Chat is the resolved canonical recipient JID the gateway addressed.
	Chat string `json:"chat"`
}

SendMessageResponse represents the response from sending a message.

The populated fields depend on the gateway's delivery mode:

  • Direct mode (HTTP 200): MessageId holds the sent WhatsApp message ID.
  • Queue mode (HTTP 202): Status is "queued" and JobID holds the job identifier — poll it with GetJobStatus to obtain the message ID once the job completes. MessageId is empty in this case.

type SendMessageTextRequest

type SendMessageTextRequest struct {
	// Chat is the canonical recipient: a bare number, a user JID
	// ("@s.whatsapp.net"), a group JID ("@g.us"), or a "@lid". When both Chat
	// and Msisdn are set, the gateway resolves Chat.
	Chat string `json:"chat,omitempty"`
	// Msisdn is the recipient in WhatsApp JID format.
	//
	// Deprecated: use Chat. Msisdn remains a permanent back-compat alias.
	Msisdn string `json:"msisdn,omitempty"`
	// Message is the text content to send
	Message string `json:"message"`
	// ReplyToID quotes an existing message by ID (optional).
	ReplyToID string `json:"reply_to_id,omitempty"`
	// ReplyToSender is the number/JID of the quoted message's author (optional).
	ReplyToSender string `json:"reply_to_sender,omitempty"`
	// ReplyToText is an optional caller-supplied preview of the quoted message.
	ReplyToText string `json:"reply_to_text,omitempty"`
	// Mentions are the numbers/JIDs to @-tag (optional).
	Mentions []string `json:"mentions,omitempty"`
}

SendMessageTextRequest represents a request to send a text message.

type SendOption added in v0.7.0

type SendOption func(*sendConfig)

SendOption configures a single send call — canonical chat addressing, a reply-to quote, @-mentions, and (see WithIdempotencyKey) send idempotency. Options are applied left-to-right and are accepted by every SendXxx method as a trailing variadic, so existing call sites keep compiling unchanged.

func WithChat added in v0.7.0

func WithChat(chat string) SendOption

WithChat sets the canonical recipient (a bare number, a user JID, a group JID "@g.us", or a "@lid"). It takes precedence over the positional recipient passed to the send method (which the gateway treats as the deprecated msisdn alias).

func WithIdempotencyKey added in v0.7.0

func WithIdempotencyKey(key string) SendOption

WithIdempotencyKey attaches an Idempotency-Key header to the send. Reusing the same key replays the gateway's original response (200 with Idempotent-Replay: true); an in-flight duplicate returns 409 (ErrConflict) and the same key with a different request body returns 422.

For the multipart media sends (image/audio/video/document/sticker) the retry must supply identical file content (and the same fields): the gateway keys idempotency on a hash of the raw request body, so a retry that streams different bytes is a different body (422), not a replay.

func WithMentions added in v0.7.0

func WithMentions(mentions ...string) SendOption

WithMentions @-tags the given numbers/JIDs in the outgoing message.

func WithReply added in v0.7.0

func WithReply(messageID, sender, quotedText string) SendOption

WithReply quotes an existing message. messageID and sender identify the quoted message and its author; quotedText is an optional caller-supplied preview of the quoted content (the gateway is storeless and does not look it up).

type SendPollMessageRequest added in v0.3.0

type SendPollMessageRequest struct {
	// Chat is the canonical recipient (see SendMessageTextRequest.Chat).
	Chat string `json:"chat,omitempty"`
	// Msisdn is the recipient in WhatsApp JID format.
	//
	// Deprecated: use Chat. Msisdn remains a permanent back-compat alias.
	Msisdn string `json:"msisdn,omitempty"`
	// Question is the poll question text
	Question string `json:"question"`
	// Options is the list of poll options
	Options []string `json:"options"`
	// SelectableCount is the optional maximum number of options a user can select
	SelectableCount int `json:"selectable_count,omitempty"`
	// ReplyToID quotes an existing message by ID (optional).
	ReplyToID string `json:"reply_to_id,omitempty"`
	// ReplyToSender is the number/JID of the quoted message's author (optional).
	ReplyToSender string `json:"reply_to_sender,omitempty"`
	// ReplyToText is an optional caller-supplied preview of the quoted message.
	ReplyToText string `json:"reply_to_text,omitempty"`
	// Mentions are the numbers/JIDs to @-tag (optional).
	Mentions []string `json:"mentions,omitempty"`
}

SendPollMessageRequest represents a request to send a poll message.

type SessionEvent added in v0.7.0

type SessionEvent struct {
	// Event is the session.* event type.
	Event WebhookEventType `json:"event"`
	// PhoneNumber is the account the event concerns.
	PhoneNumber string `json:"phone_number"`
	// JID is the account's device JID.
	JID string `json:"jid"`
	// Timestamp is the Unix timestamp of the event.
	Timestamp int64 `json:"timestamp"`

	// OnConnect (session.logged_out) is true when the logout happened while
	// (re)connecting rather than via an explicit remote logout.
	OnConnect bool `json:"on_connect,omitempty"`
	// Reason (session.logged_out, session.connect_failure) is the numeric reason code.
	Reason int `json:"reason,omitempty"`
	// ReasonText (session.logged_out, session.banned, session.connect_failure)
	// is the human-readable reason.
	ReasonText string `json:"reason_text,omitempty"`
	// Code (session.banned) is the numeric ban code.
	Code int `json:"code,omitempty"`
	// ExpiresIn (session.banned) is the ban duration in seconds.
	ExpiresIn int `json:"expires_in,omitempty"`
	// Message (session.connect_failure) is the failure message.
	Message string `json:"message,omitempty"`
}

SessionEvent is the flat envelope the gateway emits for session.* lifecycle events. The envelope fields (Event, PhoneNumber, JID, Timestamp) are always present; the remaining fields are event-specific extras and are zero when the event does not carry them:

  • session.logged_out: OnConnect, Reason, ReasonText
  • session.banned: Code, ReasonText, ExpiresIn
  • session.connect_failure: Reason, ReasonText, Message
  • session.connected / disconnected / replaced: envelope only

type SessionInventory added in v0.7.0

type SessionInventory struct {
	Instance string                 `json:"instance"`
	Count    int                    `json:"count"`
	Sessions []SessionInventoryItem `json:"sessions"`
}

SessionInventory is the GET /admin/sessions response. Instance identifies the reporting node (hostname); the view is per-instance by design.

type SessionInventoryItem added in v0.7.0

type SessionInventoryItem struct {
	PhoneMasked  string     `json:"phone_masked"`
	State        string     `json:"state"`  // connected|disconnected|never_paired|logged_out|banned
	Source       string     `json:"source"` // in-memory | store
	Reason       string     `json:"reason,omitempty"`
	LastSeen     time.Time  `json:"last_seen"`
	BanExpiresAt *time.Time `json:"ban_expires_at,omitempty"`
}

SessionInventoryItem is the per-instance view of one account: masked phone, honest lifecycle state, and where the state was observed.

type SubGroupItem added in v0.7.0

type SubGroupItem struct {
	JID               string `json:"jid"`
	Name              string `json:"name,omitempty"`
	IsDefaultSubGroup bool   `json:"is_default_sub_group"`
}

SubGroupItem is one linked group under a community. The default sub-group is the community's announcement group.

type SubGroupListResponse added in v0.7.0

type SubGroupListResponse struct {
	SubGroups []SubGroupItem `json:"sub_groups"`
	Count     int            `json:"count"`
}

SubGroupListResponse is a community's linked sub-groups (Count == len(SubGroups)).

type SuccessResponse

type SuccessResponse struct {
	// Success indicates whether the operation completed successfully
	Success bool `json:"success"`
}

SuccessResponse represents a generic success response from API operations.

type WebhookEvent added in v0.7.0

type WebhookEvent struct {
	// Event is the parsed event type.
	Event WebhookEventType
	// Incoming is set for message.incoming.
	Incoming *IncomingWebhookPayload
	// Outgoing is set for message.queued / message.sent / message.failed.
	Outgoing *OutgoingWebhookPayload
	// Session is set for session.* events.
	Session *SessionEvent
}

WebhookEvent is the discriminated result of ParseWebhook. Exactly one of the payload pointers is non-nil, selected by Event:

  • Incoming: message.incoming
  • Outgoing: message.queued / message.sent / message.failed
  • Session: session.*

type WebhookEventType added in v0.7.0

type WebhookEventType string

WebhookEventType is the type discriminator carried by every webhook envelope's "event" field. It is the single catalog ParseWebhook dispatches on and mirrors the gateway's domain/queue.StatusWebhookEvent.

const (
	// WebhookEventMessageIncoming is emitted for a received message.
	WebhookEventMessageIncoming WebhookEventType = "message.incoming"
	// WebhookEventMessageQueued is emitted when a message is queued.
	WebhookEventMessageQueued WebhookEventType = "message.queued"
	// WebhookEventMessageSent is emitted when a message is sent.
	WebhookEventMessageSent WebhookEventType = "message.sent"
	// WebhookEventMessageFailed is emitted when a message fails to send.
	WebhookEventMessageFailed WebhookEventType = "message.failed"

	// WebhookEventSessionLoggedOut is emitted when the account is logged out.
	WebhookEventSessionLoggedOut WebhookEventType = "session.logged_out"
	// WebhookEventSessionBanned is emitted when the account is temporarily banned.
	WebhookEventSessionBanned WebhookEventType = "session.banned"
	// WebhookEventSessionConnectFailure is emitted on a connection failure.
	WebhookEventSessionConnectFailure WebhookEventType = "session.connect_failure"
	// WebhookEventSessionConnected is emitted when the session connects.
	WebhookEventSessionConnected WebhookEventType = "session.connected"
	// WebhookEventSessionDisconnected is emitted when the session disconnects.
	WebhookEventSessionDisconnected WebhookEventType = "session.disconnected"
	// WebhookEventSessionReplaced is emitted when another process takes the socket.
	WebhookEventSessionReplaced WebhookEventType = "session.replaced"
)

type WebhookRegisterRequest

type WebhookRegisterRequest struct {
	// Url is the endpoint URL where webhook events will be sent
	Url string `json:"url"`
	// HmacSecret is the optional secret key used to sign webhook payloads for verification
	HmacSecret *string `json:"hmac_secret,omitempty"`
}

WebhookRegisterRequest represents a request to register a webhook URL. Webhooks receive real-time notifications for message events.

type WebhookResponse

type WebhookResponse struct {
	// URL is the currently registered webhook endpoint
	URL string `json:"url"`
}

WebhookResponse represents the response from GetWebhook.

type WebhookVerifier

type WebhookVerifier struct {
	// contains filtered or unexported fields
}

WebhookVerifier handles verification of incoming webhook signatures. It ensures that webhook payloads are genuine and haven't been tampered with by verifying HMAC-SHA256 signatures.

Security considerations:

  • Always keep your HMAC secret secure and never expose it in client-side code
  • Use constant-time comparison to prevent timing attacks
  • Reject webhooks with invalid or missing signatures

func NewWebhookVerifier

func NewWebhookVerifier(secret string) *WebhookVerifier

NewWebhookVerifier creates a new webhook verifier with the given HMAC secret. The secret must match the one configured when registering the webhook.

Example:

verifier := waga.NewWebhookVerifier("your-hmac-secret")

func (*WebhookVerifier) ParseIncomingWebhook

func (v *WebhookVerifier) ParseIncomingWebhook(payload []byte, signature string) (*IncomingWebhookPayload, error)

ParseIncomingWebhook parses and verifies an incoming message webhook payload. It returns the parsed payload if the signature is valid, or an error otherwise.

This method verifies the signature first, then unmarshals the JSON payload. If either step fails, it returns an error.

Example:

body, _ := io.ReadAll(r.Body)
signature := r.Header.Get("X-Webhook-Signature")
webhook, err := verifier.ParseIncomingWebhook(body, signature)
if err != nil {
    log.Fatal(err)
}
fmt.Printf("Message from %s: %s\n", webhook.From, webhook.Text)

func (*WebhookVerifier) ParseOutgoingWebhook

func (v *WebhookVerifier) ParseOutgoingWebhook(payload []byte, signature string) (*OutgoingWebhookPayload, error)

ParseOutgoingWebhook parses and verifies an outgoing event webhook payload. It returns the parsed payload if the signature is valid, or an error otherwise.

Outgoing webhooks track the delivery status of messages sent through the API. This method verifies the signature first, then unmarshals the JSON payload.

Example:

body, _ := io.ReadAll(r.Body)
signature := r.Header.Get("X-Webhook-Signature")
webhook, err := verifier.ParseOutgoingWebhook(body, signature)
if err != nil {
    log.Fatal(err)
}
fmt.Printf("Message %s: %s\n", webhook.Event, webhook.MessageId)

func (*WebhookVerifier) ParseWebhook added in v0.7.0

func (v *WebhookVerifier) ParseWebhook(payload []byte, signature string) (*WebhookEvent, error)

ParseWebhook verifies a webhook payload's signature, then inspects its "event" field and decodes it into a discriminated WebhookEvent covering every event the gateway emits: message.incoming (Incoming), message.queued/sent/failed (Outgoing), and the session.* lifecycle events (Session). Exactly one of the WebhookEvent payload pointers is non-nil.

Use this when a single endpoint receives every webhook type. The narrower ParseIncomingWebhook / ParseOutgoingWebhook remain available.

An unrecognized event yields an error wrapping ErrUnknownWebhookEvent; an invalid signature yields ErrInvalidSignature.

Example:

ev, err := verifier.ParseWebhook(body, signature)
if err != nil {
    log.Fatal(err)
}
switch ev.Event {
case waga.WebhookEventMessageIncoming:
    fmt.Println("from", ev.Incoming.From)
case waga.WebhookEventSessionBanned:
    fmt.Println("banned for", ev.Session.ExpiresIn, "seconds")
}

func (*WebhookVerifier) VerifySignature

func (v *WebhookVerifier) VerifySignature(payload []byte, signatureHeader string) bool

VerifySignature validates the X-Webhook-Signature header from a webhook request. The signature header format is: "sha256=<hex_signature>".

This method uses constant-time comparison to prevent timing attacks. It returns false if the signature is missing, malformed, or doesn't match.

Example:

signature := r.Header.Get("X-Webhook-Signature")
if !verifier.VerifySignature(body, signature) {
    return ErrInvalidSignature
}

Directories

Path Synopsis
Example usage of the WhatsApp Gateway SDK
Example usage of the WhatsApp Gateway SDK

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL