Skip to main content
The Square Go SDK allows you to provide a custom HTTP client, giving you full control over networking behavior including timeouts, connection pooling, transport configuration, and instrumentation.

Why Use a Custom HTTP Client?

Providing your own HTTP client is useful for:
  • Setting custom timeouts and connection limits
  • Adding request/response logging and monitoring
  • Configuring proxy settings
  • Implementing custom retry logic or circuit breakers
  • Adding distributed tracing headers
  • Using connection pooling optimized for your workload
  • Meeting specific security or compliance requirements
If you don’t provide a custom HTTP client, the SDK uses http.DefaultClient, which waits indefinitely for responses. It’s strongly recommended to provide a client with appropriate timeouts.

Basic Usage

Use option.WithHTTPClient() to provide a custom HTTP client when creating the Square client.

Simple Timeout Configuration

The most common use case is setting a timeout:
import (
    "net/http"
    "time"
    
    squareclient "github.com/square/square-go-sdk/client"
    "github.com/square/square-go-sdk/option"
)

func main() {
    client := squareclient.NewClient(
        option.WithToken("YOUR_ACCESS_TOKEN"),
        option.WithHTTPClient(
            &http.Client{
                Timeout: 5 * time.Second,
            },
        ),
    )
    
    // Use the client as normal
}

Advanced HTTP Configuration

For production environments, you’ll likely want more sophisticated configuration:
import (
    "crypto/tls"
    "net"
    "net/http"
    "time"
    
    squareclient "github.com/square/square-go-sdk/client"
    "github.com/square/square-go-sdk/option"
)

func main() {
    // Create a custom transport with optimized settings
    transport := &http.Transport{
        DialContext: (&net.Dialer{
            Timeout:   10 * time.Second,
            KeepAlive: 30 * time.Second,
        }).DialContext,
        MaxIdleConns:          100,
        MaxIdleConnsPerHost:   10,
        IdleConnTimeout:       90 * time.Second,
        TLSHandshakeTimeout:   10 * time.Second,
        ExpectContinueTimeout: 1 * time.Second,
        // Enforce TLS 1.2 minimum
        TLSClientConfig: &tls.Config{
            MinVersion: tls.VersionTLS12,
        },
    }
    
    httpClient := &http.Client{
        Transport: transport,
        Timeout:   30 * time.Second,
    }
    
    client := squareclient.NewClient(
        option.WithToken("YOUR_ACCESS_TOKEN"),
        option.WithHTTPClient(httpClient),
    )
    
    // Use the client
}

Client-Level vs Request-Level Options

You can provide the HTTP client at either the client level (affecting all requests) or at the request level (for individual API calls).

Client-Level Configuration

Set once during client initialization:
client := squareclient.NewClient(
    option.WithToken("YOUR_ACCESS_TOKEN"),
    option.WithHTTPClient(
        &http.Client{
            Timeout: 5 * time.Second,
        },
    ),
)

// All requests use this HTTP client
response, err := client.Payments.List(ctx, request)

Request-Level Configuration

Override for specific requests:
import (
    "context"
    "net/http"
    "time"
    
    "github.com/square/square-go-sdk"
    squareclient "github.com/square/square-go-sdk/client"
    "github.com/square/square-go-sdk/option"
)

func main() {
    client := squareclient.NewClient(
        option.WithToken("YOUR_ACCESS_TOKEN"),
    )
    
    // Use a different HTTP client for this specific request
    response, err := client.Payments.List(
        context.TODO(),
        &square.ListPaymentsRequest{
            Total: square.Int64(100),
        },
        option.WithHTTPClient(
            &http.Client{
                Timeout: 10 * time.Second,
            },
        ),
    )
    
    if err != nil {
        // Handle error
    }
}
Request-level options override client-level options, allowing you to use different configurations for different types of requests.

Common Use Cases

Request/Response Logging

Implement a custom RoundTripper to log all requests and responses:
import (
    "fmt"
    "net/http"
    "net/http/httputil"
    "time"
)

type LoggingRoundTripper struct {
    inner http.RoundTripper
}

func (l *LoggingRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
    // Log the request
    reqDump, _ := httputil.DumpRequestOut(req, true)
    fmt.Printf("Request:\n%s\n", reqDump)
    
    start := time.Now()
    resp, err := l.inner.RoundTrip(req)
    duration := time.Since(start)
    
    if err != nil {
        fmt.Printf("Error: %v (took %v)\n", err, duration)
        return nil, err
    }
    
    // Log the response
    respDump, _ := httputil.DumpResponse(resp, true)
    fmt.Printf("Response (took %v):\n%s\n", duration, respDump)
    
    return resp, nil
}

// Usage
func main() {
    httpClient := &http.Client{
        Transport: &LoggingRoundTripper{
            inner: http.DefaultTransport,
        },
        Timeout: 30 * time.Second,
    }
    
    client := squareclient.NewClient(
        option.WithToken("YOUR_ACCESS_TOKEN"),
        option.WithHTTPClient(httpClient),
    )
}

Proxy Configuration

Configure the client to use a proxy:
import (
    "net/http"
    "net/url"
    "time"
)

func main() {
    proxyURL, _ := url.Parse("http://proxy.example.com:8080")
    
    transport := &http.Transport{
        Proxy: http.ProxyURL(proxyURL),
    }
    
    httpClient := &http.Client{
        Transport: transport,
        Timeout:   30 * time.Second,
    }
    
    client := squareclient.NewClient(
        option.WithToken("YOUR_ACCESS_TOKEN"),
        option.WithHTTPClient(httpClient),
    )
}

Distributed Tracing

Add tracing headers to all requests:
import (
    "context"
    "fmt"
    "net/http"
    "time"
    
    "github.com/google/uuid"
)

type TracingRoundTripper struct {
    inner http.RoundTripper
}

func (t *TracingRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
    // Add tracing headers
    traceID := uuid.New().String()
    req.Header.Set("X-Trace-ID", traceID)
    req.Header.Set("X-Request-ID", uuid.New().String())
    
    fmt.Printf("Starting request with trace ID: %s\n", traceID)
    
    return t.inner.RoundTrip(req)
}

// Usage
func main() {
    httpClient := &http.Client{
        Transport: &TracingRoundTripper{
            inner: http.DefaultTransport,
        },
        Timeout: 30 * time.Second,
    }
    
    client := squareclient.NewClient(
        option.WithToken("YOUR_ACCESS_TOKEN"),
        option.WithHTTPClient(httpClient),
    )
}

Rate Limiting

Implement client-side rate limiting:
import (
    "golang.org/x/time/rate"
    "net/http"
    "time"
)

type RateLimitedRoundTripper struct {
    inner   http.RoundTripper
    limiter *rate.Limiter
}

func (r *RateLimitedRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
    // Wait for rate limiter
    if err := r.limiter.Wait(req.Context()); err != nil {
        return nil, err
    }
    
    return r.inner.RoundTrip(req)
}

// Usage: Limit to 10 requests per second
func main() {
    httpClient := &http.Client{
        Transport: &RateLimitedRoundTripper{
            inner:   http.DefaultTransport,
            limiter: rate.NewLimiter(rate.Limit(10), 1),
        },
        Timeout: 30 * time.Second,
    }
    
    client := squareclient.NewClient(
        option.WithToken("YOUR_ACCESS_TOKEN"),
        option.WithHTTPClient(httpClient),
    )
}

HTTPClient Interface

The SDK accepts any type that implements the core.HTTPClient interface:
type HTTPClient interface {
    Do(*http.Request) (*http.Response, error)
}
The standard *http.Client type implements this interface, but you can also provide your own implementation for maximum flexibility:
import (
    "net/http"
    
    "github.com/square/square-go-sdk/core"
)

type MyCustomClient struct {
    // Your custom fields
}

func (c *MyCustomClient) Do(req *http.Request) (*http.Response, error) {
    // Your custom implementation
    // ...
    return &http.Response{}, nil
}

// Usage
func main() {
    myClient := &MyCustomClient{}
    
    client := squareclient.NewClient(
        option.WithToken("YOUR_ACCESS_TOKEN"),
        option.WithHTTPClient(myClient),
    )
}

Best Practices

Always Set Timeouts

Never use http.DefaultClient in production. Always configure appropriate timeouts.

Reuse Clients

HTTP clients manage connection pools. Create them once and reuse them across requests.

Connection Pooling

Configure MaxIdleConns and MaxIdleConnsPerHost based on your expected request volume.

TLS Configuration

Set minimum TLS version and other security settings appropriate for your requirements.
For most production use cases, these settings provide a good starting point:
transport := &http.Transport{
    DialContext: (&net.Dialer{
        Timeout:   10 * time.Second,  // Connection timeout
        KeepAlive: 30 * time.Second,  // Keep-alive probes
    }).DialContext,
    MaxIdleConns:          100,              // Total idle connections
    MaxIdleConnsPerHost:   10,               // Idle connections per host
    IdleConnTimeout:       90 * time.Second, // How long idle connections stay open
    TLSHandshakeTimeout:   10 * time.Second, // TLS handshake timeout
    ExpectContinueTimeout: 1 * time.Second,  // 100-continue timeout
}

httpClient := &http.Client{
    Transport: transport,
    Timeout:   30 * time.Second, // Overall request timeout
}
Adjust these values based on your specific requirements, network conditions, and Square API endpoint response times.

Context-Based Timeouts

Remember that you can also use Go’s context package for per-request timeouts, which work in conjunction with the HTTP client timeout:
import (
    "context"
    "time"
    
    "github.com/square/square-go-sdk"
)

func main() {
    client := squareclient.NewClient(
        option.WithToken("YOUR_ACCESS_TOKEN"),
        option.WithHTTPClient(
            &http.Client{
                Timeout: 30 * time.Second, // Overall client timeout
            },
        ),
    )
    
    // Per-request timeout using context
    ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
    defer cancel()
    
    response, err := client.Payments.List(
        ctx,
        &square.ListPaymentsRequest{
            Total: square.Int64(100),
        },
    )
    
    if err != nil {
        // Handle timeout or other error
    }
}
The effective timeout is whichever limit is reached first: the context timeout or the HTTP client timeout.

Timeouts

Learn more about timeout configuration and context usage

Request Options

Explore all available request options

Retries

Understand the SDK’s automatic retry behavior

Extra Properties

Learn how to send and receive additional properties

Build docs developers (and LLMs) love