Go Examples: Complete Integration Guide

Production-ready Go code for integrating FetchHook webhooks using standard library and Go idioms.

#Basic Fetch

Fetch webhooks using Go's standard library http package with proper error handling.

Simple Fetch

go
package main

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

type WebhookEvent struct {
    EventID    string                 `json:"event_id"`
    Provider   string                 `json:"provider"`
    Payload    map[string]interface{} `json:"payload"`
    Headers    map[string]string      `json:"headers"`
    ReceivedAt time.Time              `json:"received_at"`
}

type WebhookResponse struct {
    Webhooks []WebhookEvent `json:"webhooks"`
    Count    int            `json:"count"`
    HasMore  bool           `json:"has_more"`
}

func fetchWebhooks(sourceID, apiKey string) (*WebhookResponse, error) {
    url := fmt.Sprintf("https://api.fetchhook.app/api/v1/%s", sourceID)

    req, err := http.NewRequest("GET", url, nil)
    if err != nil {
        return nil, err
    }

    req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", apiKey))

    client := &http.Client{Timeout: 10 * time.Second}
    resp, err := client.Do(req)
    if err != nil {
        return nil, err
    }
    defer resp.Body.Close()

    if resp.StatusCode != http.StatusOK {
        body, _ := io.ReadAll(resp.Body)
        return nil, fmt.Errorf("API error %d: %s", resp.StatusCode, body)
    }

    var result WebhookResponse
    if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
        return nil, err
    }

    return &result, nil
}

func main() {
    webhooks, err := fetchWebhooks(
        os.Getenv("FETCHHOOK_SOURCE_ID"),
        os.Getenv("FETCHHOOK_API_KEY"),
    )
    if err != nil {
        log.Fatal(err)
    }

    fmt.Printf("Received %d webhooks\n", webhooks.Count)
    for _, wh := range webhooks.Webhooks {
        fmt.Printf("  %s from %s\n", wh.EventID, wh.Provider)
    }
}

#Polling Loop with Context

Continuously poll for webhooks with graceful shutdown using context cancellation.

Production Polling

go
package main

import (
    "context"
    "log"
    "os"
    "os/signal"
    "syscall"
    "time"
)

type Poller struct {
    sourceID string
    apiKey   string
    client   *http.Client
    interval time.Duration
    lastPoll time.Time
}

func NewPoller(sourceID, apiKey string, interval time.Duration) *Poller {
    return &Poller{
        sourceID: sourceID,
        apiKey:   apiKey,
        client:   &http.Client{Timeout: 10 * time.Second},
        interval: interval,
        lastPoll: time.Now(),
    }
}

func (p *Poller) fetchSince(ctx context.Context) (*WebhookResponse, error) {
    url := fmt.Sprintf(
        "https://api.fetchhook.app/api/v1/%s?since=%s",
        p.sourceID,
        p.lastPoll.Format(time.RFC3339),
    )

    req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
    if err != nil {
        return nil, err
    }
    req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", p.apiKey))

    resp, err := p.client.Do(req)
    if err != nil {
        return nil, err
    }
    defer resp.Body.Close()

    var result WebhookResponse
    json.NewDecoder(resp.Body).Decode(&result)
    return &result, nil
}

func (p *Poller) Start(ctx context.Context) error {
    ticker := time.NewTicker(p.interval)
    defer ticker.Stop()

    for {
        select {
        case <-ctx.Done():
            return ctx.Err()
        case <-ticker.C:
            webhooks, err := p.fetchSince(ctx)
            if err != nil {
                log.Printf("Error: %v", err)
                continue
            }
            if webhooks.Count > 0 {
                log.Printf("Received %d webhooks", webhooks.Count)
                for _, wh := range webhooks.Webhooks {
                    processWebhook(wh)
                }
                p.lastPoll = time.Now()
            }
        }
    }
}

func main() {
    ctx, cancel := context.WithCancel(context.Background())
    defer cancel()

    sigChan := make(chan os.Signal, 1)
    signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM)
    go func() {
        <-sigChan
        cancel()
    }()

    poller := NewPoller(
        os.Getenv("FETCHHOOK_SOURCE_ID"),
        os.Getenv("FETCHHOOK_API_KEY"),
        10*time.Second,
    )
    poller.Start(ctx)
}

#Stripe Integration

Process Stripe webhooks with signature verification for security.

Stripe + FetchHook

go
package main

import (
    "crypto/hmac"
    "crypto/sha256"
    "encoding/hex"
    "encoding/json"
    "fmt"
    "strings"
)

func verifyStripeSignature(payload, sigHeader, secret string) bool {
    parts := strings.Split(sigHeader, ",")
    var timestamp, signature string

    for _, part := range parts {
        kv := strings.SplitN(part, "=", 2)
        if len(kv) != 2 {
            continue
        }
        if kv[0] == "t" {
            timestamp = kv[1]
        } else if kv[0] == "v1" {
            signature = kv[1]
        }
    }

    signedPayload := fmt.Sprintf("%s.%s", timestamp, payload)
    mac := hmac.New(sha256.New, []byte(secret))
    mac.Write([]byte(signedPayload))
    expected := hex.EncodeToString(mac.Sum(nil))

    return hmac.Equal([]byte(signature), []byte(expected))
}

func processStripeWebhook(wh WebhookEvent, secret string) error {
    payload, _ := json.Marshal(wh.Payload)
    sigHeader := wh.Headers["stripe-signature"]

    if !verifyStripeSignature(string(payload), sigHeader, secret) {
        return fmt.Errorf("invalid signature")
    }

    log.Printf("Processing Stripe event: %s", wh.EventID)
    // Handle different event types here
    return nil
}

func main() {
    webhooks, _ := fetchWebhooks(
        os.Getenv("FETCHHOOK_SOURCE_ID"),
        os.Getenv("FETCHHOOK_API_KEY"),
    )

    for _, wh := range webhooks.Webhooks {
        if wh.Provider == "stripe" {
            processStripeWebhook(wh, os.Getenv("STRIPE_WEBHOOK_SECRET"))
        }
    }
}

#Concurrent Processing

Process webhooks concurrently using goroutines and channels for maximum throughput.

Worker Pool

go
package main

import (
    "context"
    "sync"
)

type WebhookProcessor struct {
    workers   int
    webhookCh chan WebhookEvent
    wg        sync.WaitGroup
}

func NewWebhookProcessor(workers int) *WebhookProcessor {
    return &WebhookProcessor{
        workers:   workers,
        webhookCh: make(chan WebhookEvent, 100),
    }
}

func (p *WebhookProcessor) Start(ctx context.Context) {
    for i := 0; i < p.workers; i++ {
        p.wg.Add(1)
        go p.worker(ctx, i)
    }
}

func (p *WebhookProcessor) worker(ctx context.Context, id int) {
    defer p.wg.Done()

    for {
        select {
        case <-ctx.Done():
            return
        case wh := <-p.webhookCh:
            log.Printf("Worker %d processing %s", id, wh.EventID)
            // Process webhook here
        }
    }
}

func (p *WebhookProcessor) Submit(wh WebhookEvent) {
    p.webhookCh <- wh
}

func (p *WebhookProcessor) Stop() {
    close(p.webhookCh)
    p.wg.Wait()
}

func main() {
    processor := NewWebhookProcessor(5)
    ctx, cancel := context.WithCancel(context.Background())
    processor.Start(ctx)

    webhooks, _ := fetchWebhooks(
        os.Getenv("FETCHHOOK_SOURCE_ID"),
        os.Getenv("FETCHHOOK_API_KEY"),
    )

    for _, wh := range webhooks.Webhooks {
        processor.Submit(wh)
    }

    cancel()
    processor.Stop()
}

Agent Protocol Instruction

For production: Use context for cancellation and timeouts, implement exponential backoff for retries, add proper logging with structured logs, handle rate limits by checking response headers, use worker pools for concurrent processing, and store secrets in environment variables.
All Resources
Verified for Agentic Workflowsv1.0.4