A production-ready, fully idiomatic Go SDK for the GoCardless API.
Zero external dependencies — stdlib only.
| Capability | Detail |
|---|---|
| Complete API coverage | All 47 resource services matching the GoCardless API reference |
| Open Banking / Pay by Bank | Billing Requests, Bank Authorisations, Institutions |
| Outbound Payments | Full send-money flow with ECDSA/RSA request signing |
| OAuth2 | Partner auth-URL builder, token exchange, lookup, disconnect |
| Resilience | Exponential backoff + full jitter, circuit breaker, retry budgets |
| Rate-limit awareness | Parses X-RateLimit-* headers, respects Retry-After on 429 |
| Pagination | Generic Paginator[T] — stream via channels or collect to slice |
| Webhook verification | HMAC-SHA256 + typed event router + IP allowlist middleware |
| Middleware chain | Composable http.RoundTripper wrappers for tracing, metrics, signing |
| Request correlation | WithRequestID(ctx, id) propagates as X-Request-ID |
| Zero dependencies | Only the Go standard library is required |
| Go 1.22+ | Uses generics for the paginator |
go get github.com/iamkanishka/gocardless-client-goRequires Go 1.22 or later.
package main
import (
"context"
"fmt"
"log"
gocardless "github.com/iamkanishka/gocardless-client-go"
"github.com/iamkanishka/gocardless-client-go/resources"
)
func main() {
client := gocardless.New(
gocardless.WithAccessToken("sandbox_token_here"),
gocardless.WithEnvironment(gocardless.Sandbox),
)
customer, err := client.Customers.Create(context.Background(), resources.CreateCustomerParams{
Email: "alice@example.com",
GivenName: "Alice",
FamilyName: "Smith",
CountryCode: "GB",
})
if err != nil {
log.Fatal(err)
}
fmt.Println("Created customer:", customer.ID)
}gocardless-client-go/
├── gocardless.go # Root package — re-exports all public types
├── go.mod
├── client/
│ └── client.go # Functional-options constructor, all 47 services
├── resources/
│ ├── types.go # Shared enums, Metadata, status constants
│ ├── helpers.go # struct → url.Values conversion
│ ├── customers.go
│ ├── mandates.go
│ ├── payments.go
│ ├── subscriptions.go
│ ├── redirect_flows.go
│ ├── billing_requests.go
│ ├── events.go
│ ├── payouts.go
│ ├── creditors_bankaccounts_refunds.go
│ ├── remaining_resources.go
│ ├── new_resources.go
│ ├── extensions.go
│ └── resources_test.go
├── webhooks/
│ ├── webhooks.go # HMAC verification + typed Handler
│ ├── event_types.go # 123 event action constants
│ ├── ip_allowlist.go # IP allowlist middleware
│ └── webhooks_test.go
├── oauth/
│ └── oauth.go # OAuth2 partner flow
├── signing/
│ └── signing.go # ECDSA P-256 / RSA request signing
├── docs/
│ ├── usage.md # Usage guide
│ └── webhooks.md # Webhook integration guide
└── internal/
└── httpclient/
├── client.go # Resilient HTTP client (retries, backoff, CB)
├── errors.go # APIError types + Is* helpers
├── paginator.go # Generic Paginator[T]
├── middleware.go # Middleware chain, rate-limit state
└── idempotency.go # Crypto-random key generator
client := gocardless.New(
gocardless.WithAccessToken("your-token"),
gocardless.WithEnvironment(gocardless.Live),
gocardless.WithTimeout(30*time.Second),
gocardless.WithMaxRetries(3),
gocardless.WithRetryDelays(500*time.Millisecond, 30*time.Second),
gocardless.WithCircuitBreaker(),
gocardless.WithLogger(myLogger),
gocardless.WithMiddleware(myTracingMiddleware),
)| Option | Default | Description |
|---|---|---|
WithAccessToken(token) |
— | Required. Bearer access token |
WithEnvironment(env) |
Sandbox |
Sandbox or Live |
WithHTTPClient(c) |
built-in | Custom *http.Client |
WithTimeout(d) |
30s |
Per-request timeout |
WithMaxRetries(n) |
3 |
Max automatic retry attempts |
WithRetryDelays(base, max) |
500ms / 30s |
Exponential backoff range |
WithCircuitBreaker() |
disabled | Opens after 5 consecutive failures |
WithLogger(l) |
noop | Inject structured logger |
WithMiddleware(m...) |
none | Transport middleware chain |
import "errors"
_, err := client.Customers.Get(ctx, "CU_MISSING")
if err != nil {
var apiErr *gocardless.APIError
switch {
case gocardless.IsNotFound(err):
fmt.Println("not found")
case gocardless.IsUnprocessable(err):
if errors.As(err, &apiErr) {
for _, fe := range apiErr.Errors {
fmt.Printf("field %q: %s\n", fe.Field, fe.Message)
}
}
case gocardless.IsRateLimited(err):
fmt.Println("rate limited — auto-retried until budget exhausted")
case gocardless.IsInvalidState(err):
fmt.Println("resource in wrong state")
default:
log.Fatal(err)
}
}key := gocardless.NewIdempotencyKey()
payment, err := client.Payments.Create(ctx, params,
gocardless.WithIdempotencyKey(key),
)// Stream via channel (memory-efficient)
paginator := client.Payments.All(resources.ListPaymentsParams{Limit: 100})
ch, errCh := paginator.All(ctx)
for payment := range ch {
process(payment)
}
if err := <-errCh; err != nil {
log.Fatal(err)
}
// Or collect to slice
all, err := client.Payments.All(resources.ListPaymentsParams{}).Collect(ctx)handler := webhooks.NewHandler(webhookSecret).
OnPaymentPaidOut(func(e webhooks.Event) { /* … */ }).
OnPaymentFailed(func(e webhooks.Event) { /* … */ }).
OnMandateCancelled(func(e webhooks.Event) { /* … */ }).
OnBillingRequestFulfilled(func(e webhooks.Event) { /* … */ }).
OnAny(func(e webhooks.Event) { auditLog(e) })
http.Handle("/webhooks", webhooks.IPAllowlistMiddleware(handler))cfg := &oauth.Config{
ClientID: "your-client-id",
ClientSecret: "your-secret",
RedirectURI: "https://example.com/callback",
Environment: oauth.Live,
}
// Step 1: redirect user
http.Redirect(w, r, cfg.AuthURL(oauth.WithScope("read_write")), http.StatusFound)
// Step 2: exchange code
token, err := cfg.Exchange(ctx, r.URL.Query().Get("code"))
// Step 3: use token
merchantClient := gocardless.New(gocardless.WithAccessToken(token.AccessToken))signer := signing.NewECDSASigner("key-id", privateKey)
client := gocardless.New(
gocardless.WithAccessToken(token),
gocardless.WithMiddleware(signing.NewMiddleware(signer)),
)go test ./... # All tests
go test -race ./... # With race detector
go test -cover ./... # With coverageSee SECURITY.md for vulnerability disclosure policy and security notes.
See CHANGELOG.md for release history.
MIT — see LICENSE.