Whatify LogoWhatify
SDKs

Go SDK

Official Whatify SDK for Go applications

The official Whatify SDK for Go applications with idiomatic Go patterns and full type safety.

Before using this SDK, sign up at https://whatify.dev to get your API token.

Installation

go get github.com/yourorg/whatify-go

Quick Start

package main

import (
    "fmt"
    "log"

    "github.com/yourorg/whatify-go"
)

func main() {
    client := whatify.NewClient("your-api-token", nil)

    // Send a message
    resp, err := client.SendText("1234567890", "Hello from Go!")
    if err != nil {
        log.Fatal(err)
    }

    fmt.Printf("Message sent: %v\n", resp.Success)
}

Configuration

Basic Configuration

import "github.com/yourorg/whatify-go"

client := whatify.NewClient("your-api-token", nil)

Custom Configuration

config := &whatify.Config{
    APIToken: "your-api-token",
}

client := whatify.NewClient("", config)

Using API Key

config := &whatify.Config{
    APIKey: "your-api-key",
}

client := whatify.NewClient("", config)

From Environment Variables

import "os"

client := whatify.NewClient(os.Getenv("WHATIFY_API_TOKEN"), nil)

API Methods

Connect Device

Connect your WhatsApp device.

resp, err := client.Connect()
if err != nil {
    log.Fatal(err)
}

fmt.Printf("QR Code: %s\n", resp.QRCode)
fmt.Printf("Status: %s\n", resp.Status)

Response:

type ConnectResponse struct {
    Success bool   `json:"success"`
    QRCode  string `json:"qrCode,omitempty"`
    Status  string `json:"status"`
    Message string `json:"message"`
}

Disconnect Device

resp, err := client.Disconnect()
if err != nil {
    log.Fatal(err)
}

fmt.Printf("Disconnected: %v\n", resp.Success)

Check Status

resp, err := client.Status()
if err != nil {
    log.Fatal(err)
}

fmt.Printf("Status: %s\n", resp.Status)
fmt.Printf("Connected: %v\n", resp.IsConnected)

Response:

type StatusResponse struct {
    Success     bool   `json:"success"`
    Status      string `json:"status"`
    IsConnected bool   `json:"isConnected"`
    Message     string `json:"message"`
}

Send Messages

Send Text Message

// Single recipient
resp, err := client.SendText("1234567890", "Hello World!")
if err != nil {
    log.Fatal(err)
}

// Multiple recipients
resp, err := client.SendText(
    []string{"1234567890", "0987654321"},
    "Bulk message",
)
if err != nil {
    log.Fatal(err)
}

Send Attachment

resp, err := client.SendAttachment("1234567890", &whatify.Attachment{
    Type:    "image",
    URL:     "https://example.com/image.jpg",
    Caption: "Check this out!",
})
if err != nil {
    log.Fatal(err)
}

Send Text with Attachment

req := &whatify.SendMessageRequest{
    To:   "1234567890",
    Text: "Here is your document",
    Attachment: &whatify.Attachment{
        Type:     "document",
        URL:      "https://example.com/report.pdf",
        Filename: "report.pdf",
    },
}

resp, err := client.Send(req)
if err != nil {
    log.Fatal(err)
}

Attachment Types:

const (
    AttachmentTypeImage    = "image"
    AttachmentTypeVideo    = "video"
    AttachmentTypeAudio    = "audio"
    AttachmentTypeDocument = "document"
)

Response:

type SendMessageResponse struct {
    Success    bool     `json:"success"`
    Message    string   `json:"message"`
    MessageIDs []string `json:"messageIds,omitempty"`
}

Send OTP

Send One-Time Password (OTP) codes via WhatsApp with multi-language support.

Send OTP (English)

resp, err := client.SendOTP("1234567890", "123456", "English")
if err != nil {
    log.Fatal(err)
}

fmt.Printf("OTP sent: %+v\n", resp.Data)

Send OTP (Arabic)

resp, err := client.SendOTP("1234567890", "654321", "Arabic")
if err != nil {
    log.Fatal(err)
}

Generate and Send OTP

import (
    "math/rand"
    "time"
)

// Generate a random 6-digit OTP
rand.Seed(time.Now().UnixNano())
otpCode := fmt.Sprintf("%06d", rand.Intn(1000000))

resp, err := client.SendOTP("1234567890", otpCode, "English")
if err != nil {
    log.Fatal(err)
}

// Store OTP in database for verification
otp := &OTP{
    Phone:     "1234567890",
    Code:      otpCode,
    ExpiresAt: time.Now().Add(10 * time.Minute),
}
db.Create(otp)

Using Request Struct

req := &whatify.SendOTPRequest{
    Phone:    "1234567890",
    Code:     "123456",
    Language: "English",
}

resp, err := client.SendOTPWithRequest(req)
if err != nil {
    log.Fatal(err)
}

OTP Parameters:

  • phone (string, required) - Recipient phone number with country code
  • code (string, required) - OTP code (4-8 digits)
  • language (string, optional) - Message language: "English" or "Arabic" (default: "English")

Response:

type SendOTPResponse struct {
    Success bool              `json:"success"`
    Message string            `json:"message"`
    Data    *SendOTPData      `json:"data,omitempty"`
}

type SendOTPData struct {
    MessageID         string `json:"messageId"`
    WhatsAppMessageID string `json:"whatsappMessageId"`
    Phone             string `json:"phone"`
    Code              string `json:"code"`
    Language          string `json:"language"`
    SentAt            string `json:"sentAt"`
}

Expamle OTP Message:

  • English: 123456 is your verification code. For your security, do not share this code
  • Arabic: 123456 هذا هو رمز التحقق الخاص بك. لحمايتك، يرجى عدم مشاركة هذا الرمز.

Error Handling

Type Assertions

resp, err := client.SendText("1234567890", "Hello!")
if err != nil {
    if apiErr, ok := err.(*whatify.APIError); ok {
        fmt.Printf("API Error: %s (status: %d)\n",
            apiErr.Message, apiErr.StatusCode)
    } else {
        fmt.Printf("Error: %v\n", err)
    }
    return
}

if resp.Success {
    fmt.Println("Message sent successfully!")
}

Custom Error Type

type APIError struct {
    StatusCode int
    Message    string
}

func (e *APIError) Error() string {
    return fmt.Sprintf("API error %d: %s", e.StatusCode, e.Message)
}

Context Support

All methods support context for timeout and cancellation:

import (
    "context"
    "time"
)

// With timeout
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()

resp, err := client.SendTextWithContext(ctx, "1234567890", "Hello!")
if err != nil {
    log.Fatal(err)
}
// With cancellation
ctx, cancel := context.WithCancel(context.Background())
defer cancel()

// Cancel after 5 seconds
go func() {
    time.Sleep(5 * time.Second)
    cancel()
}()

resp, err := client.ConnectWithContext(ctx)
if err != nil {
    if err == context.Canceled {
        log.Println("Operation canceled")
    }
    return
}

Framework Integration

Gin Web Framework

package main

import (
    "net/http"
    "os"

    "github.com/gin-gonic/gin"
    "github.com/yourorg/whatify-go"
)

var client *whatify.Client

func init() {
    client = whatify.NewClient(os.Getenv("WHATIFY_API_TOKEN"), nil)
}

func main() {
    r := gin.Default()

    r.POST("/send-message", sendMessage)
    r.GET("/status", checkStatus)

    r.Run(":8080")
}

type SendMessageBody struct {
    Phone   string `json:"phone" binding:"required"`
    Message string `json:"message" binding:"required"`
}

func sendMessage(c *gin.Context) {
    var body SendMessageBody
    if err := c.ShouldBindJSON(&body); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        return
    }

    resp, err := client.SendText(body.Phone, body.Message)
    if err != nil {
        c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
        return
    }

    c.JSON(http.StatusOK, resp)
}

func checkStatus(c *gin.Context) {
    resp, err := client.Status()
    if err != nil {
        c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
        return
    }

    c.JSON(http.StatusOK, resp)
}

Echo Framework

package main

import (
    "net/http"
    "os"

    "github.com/labstack/echo/v4"
    "github.com/yourorg/whatify-go"
)

var client *whatify.Client

func init() {
    client = whatify.NewClient(os.Getenv("WHATIFY_API_TOKEN"), nil)
}

func main() {
    e := echo.New()

    e.POST("/send-message", sendMessage)
    e.GET("/status", checkStatus)

    e.Start(":8080")
}

type SendMessageRequest struct {
    Phone   string `json:"phone"`
    Message string `json:"message"`
}

func sendMessage(c echo.Context) error {
    req := new(SendMessageRequest)
    if err := c.Bind(req); err != nil {
        return c.JSON(http.StatusBadRequest, map[string]string{
            "error": err.Error(),
        })
    }

    resp, err := client.SendText(req.Phone, req.Message)
    if err != nil {
        return c.JSON(http.StatusInternalServerError, map[string]string{
            "error": err.Error(),
        })
    }

    return c.JSON(http.StatusOK, resp)
}

func checkStatus(c echo.Context) error {
    resp, err := client.Status()
    if err != nil {
        return c.JSON(http.StatusInternalServerError, map[string]string{
            "error": err.Error(),
        })
    }

    return c.JSON(http.StatusOK, resp)
}

Standard net/http

package main

import (
    "encoding/json"
    "log"
    "net/http"
    "os"

    "github.com/dilshad4/whatify-go"
)

var client *whatify.Client

func init() {
    client = whatify.NewClient(os.Getenv("WHATIFY_API_TOKEN"), nil)
}

func main() {
    http.HandleFunc("/send-message", sendMessageHandler)
    http.HandleFunc("/status", statusHandler)

    log.Fatal(http.ListenAndServe(":8080", nil))
}

type SendMessageRequest struct {
    Phone   string `json:"phone"`
    Message string `json:"message"`
}

func sendMessageHandler(w http.ResponseWriter, r *http.Request) {
    if r.Method != http.MethodPost {
        http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
        return
    }

    var req SendMessageRequest
    if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
        http.Error(w, err.Error(), http.StatusBadRequest)
        return
    }

    resp, err := client.SendText(req.Phone, req.Message)
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }

    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(resp)
}

func statusHandler(w http.ResponseWriter, r *http.Request) {
    resp, err := client.Status()
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }

    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(resp)
}

Advanced Examples

Retry Logic with Exponential Backoff

import (
    "time"
    "math"
)

func sendWithRetry(client *whatify.Client, phone, message string, maxRetries int) (*whatify.SendMessageResponse, error) {
    var resp *whatify.SendMessageResponse
    var err error

    for i := 0; i < maxRetries; i++ {
        resp, err = client.SendText(phone, message)
        if err == nil {
            return resp, nil
        }

        // Check if it's a rate limit error
        if apiErr, ok := err.(*whatify.APIError); ok {
            if apiErr.StatusCode == 429 && i < maxRetries-1 {
                // Exponential backoff
                waitTime := time.Duration(math.Pow(2, float64(i))) * time.Second
                time.Sleep(waitTime)
                continue
            }
        }

        return nil, err
    }

    return resp, err
}

// Usage
resp, err := sendWithRetry(client, "1234567890", "Hello!", 3)

Batch Processing with Goroutines

import "sync"

type SendResult struct {
    Phone   string
    Success bool
    Error   error
}

func sendBatch(client *whatify.Client, recipients []string, message string) []SendResult {
    results := make([]SendResult, len(recipients))
    var wg sync.WaitGroup

    for i, phone := range recipients {
        wg.Add(1)

        go func(index int, p string) {
            defer wg.Done()

            _, err := client.SendText(p, message)
            results[index] = SendResult{
                Phone:   p,
                Success: err == nil,
                Error:   err,
            }
        }(i, phone)
    }

    wg.Wait()
    return results
}

// Usage
recipients := []string{"1234567890", "0987654321", "5555555555"}
results := sendBatch(client, recipients, "Bulk notification")

for _, r := range results {
    if r.Success {
        fmt.Printf("Sent to %s\n", r.Phone)
    } else {
        fmt.Printf("Failed %s: %v\n", r.Phone, r.Error)
    }
}

Rate Limiter

import (
    "sync"
    "time"
)

type RateLimiter struct {
    maxCalls int
    period   time.Duration
    calls    []time.Time
    mu       sync.Mutex
}

func NewRateLimiter(maxCalls int, period time.Duration) *RateLimiter {
    return &RateLimiter{
        maxCalls: maxCalls,
        period:   period,
        calls:    make([]time.Time, 0),
    }
}

func (rl *RateLimiter) Wait() {
    rl.mu.Lock()
    defer rl.mu.Unlock()

    now := time.Now()

    // Remove old calls
    validCalls := make([]time.Time, 0)
    for _, call := range rl.calls {
        if now.Sub(call) < rl.period {
            validCalls = append(validCalls, call)
        }
    }
    rl.calls = validCalls

    // Check if we need to wait
    if len(rl.calls) >= rl.maxCalls {
        oldestCall := rl.calls[0]
        sleepTime := rl.period - now.Sub(oldestCall)
        time.Sleep(sleepTime)
    }

    rl.calls = append(rl.calls, time.Now())
}

// Usage
limiter := NewRateLimiter(100, time.Minute)

for _, phone := range recipients {
    limiter.Wait()
    client.SendText(phone, "Rate-limited message")
}

Worker Pool Pattern

type Job struct {
    Phone   string
    Message string
}

func worker(id int, jobs <-chan Job, results chan<- SendResult, client *whatify.Client) {
    for job := range jobs {
        _, err := client.SendText(job.Phone, job.Message)
        results <- SendResult{
            Phone:   job.Phone,
            Success: err == nil,
            Error:   err,
        }
    }
}

func sendWithWorkerPool(client *whatify.Client, jobs []Job, numWorkers int) []SendResult {
    jobChan := make(chan Job, len(jobs))
    resultChan := make(chan SendResult, len(jobs))

    // Start workers
    for w := 0; w < numWorkers; w++ {
        go worker(w, jobChan, resultChan, client)
    }

    // Send jobs
    for _, job := range jobs {
        jobChan <- job
    }
    close(jobChan)

    // Collect results
    results := make([]SendResult, 0, len(jobs))
    for i := 0; i < len(jobs); i++ {
        results = append(results, <-resultChan)
    }

    return results
}

// Usage
jobs := []Job{
    {Phone: "1234567890", Message: "Hello 1"},
    {Phone: "0987654321", Message: "Hello 2"},
}

results := sendWithWorkerPool(client, jobs, 5)

Type Definitions

type Client struct {
    config *Config
}

type Config struct {
    APIToken string
    APIKey   string
}

type Attachment struct {
    Type     string `json:"type"`
    URL      string `json:"url"`
    Caption  string `json:"caption,omitempty"`
    Filename string `json:"filename,omitempty"`
}

type SendMessageRequest struct {
    To         interface{} `json:"to"`
    Text       string      `json:"text,omitempty"`
    Attachment *Attachment `json:"attachment,omitempty"`
}

Rate Limits

  • 100 requests per minute per API token
  • 1000 requests per hour per API token

Support