v0.18.2 · stable Go 1.22+ MIT license

A lightweight HTTP client for Go services.

Pebble is a small, opinionated wrapper around net/http with the things you find yourself reimplementing on every team: retries, timeouts, tracing, and structured errors. About 600 lines of code and one dependency.

$ go get github.com/example/pebble@latest

What Pebble gives you

⟲

Retry handling

Exponential backoff with jitter, configurable per-request. Only retries on the response codes that make sense to retry on.

⏱

Sensible timeouts

Separate dial, TLS handshake, and request timeouts. No more http.Get hanging on a misconfigured load balancer.

⊙

Tracing built in

OpenTelemetry spans for every request, with the relevant attributes. Optional, off by default.

⚠

Structured errors

Errors that tell you what failed and where. No more wrapping io.EOF in three layers of "unexpected error reading response."

⌬

Connection pooling

Per-host connection limits, idle timeouts, and graceful shutdown. The things you remember after one outage.

⚙

No magic

Configuration is explicit. Defaults are documented. There are no global state changes. Read the code, it's short.

A minimal example

The smallest useful program with Pebble:

package main

import (
    "context"
    "fmt"
    "log"

    "github.com/example/pebble"
)

func main() {
    client := pebble.New(pebble.Options{
        Timeout:    pebble.DefaultTimeout,
        MaxRetries: 3,
    })

    resp, err := client.Get(context.Background(), "https://api.example.com/v1/health")
    if err != nil {
        log.Fatal(err)
    }
    defer resp.Body.Close()

    fmt.Println("Status:", resp.StatusCode)
}

That's it. Sensible behaviour out of the box, with knobs available when you need them.

Read the documentation →