Appearance
Quickstart - Go
Connect your first bank account in sandbox mode with github.com/budgetbakers/partner-sdk-go. You need a partner account in the partner portal - registration provisions sandbox access instantly, no card required.
1. Install
sh
go get github.com/budgetbakers/partner-sdk-go@v0.2.1Go 1.24 or newer; standard library only; package name partner. Until the module is published, download partner-sdk-go-0.2.1.zip from the downloads page instead, unpack it next to your module and run go mod edit -replace github.com/budgetbakers/partner-sdk-go=../partner-sdk-go followed by go mod tidy.
2. Create the client
Your API key selects the mode: bb_test_… keys run sandbox (fictional *_xf banks), bb_live_… keys run live. Keys are server-side only.
go
import partner "github.com/budgetbakers/partner-sdk-go"
bb := partner.New("{{BB_API_KEY}}")
config, err := bb.Partner.GetConfig(ctx)
if err != nil {
return err
}
fmt.Println(config.Mode) // "sandbox"3. Create an end-user client
A client is one of your end users - bank connections, accounts and transactions all belong to a client, and the X-Client-Id scoping keeps each user's data isolated. Pass your own user id as ExternalID and the call becomes an upsert - safe to call on every login, no duplicates, no need to store a BudgetBakers-id mapping on your side:
go
client, err := bb.Clients.Create(ctx, partner.ClientCreateRequest{
ExternalID: "user-42",
Email: "user42@example.com",
CountryCode: "CZ",
})
if err != nil {
return err
}
scope := bb.Client(client.ID) // all user-scoped calls go through this4. Start the hosted connect flow
Create a connect session and send the user to HostedURL (a web redirect, or from your app in the system browser; see Mobile apps).
The return URL works like an OAuth redirect URI: the portal (Apps → return URLs) holds your allowlist of patterns - one per platform (web URL, iOS universal link, Android app link, custom scheme) - and each session passes the concrete URL for where this user should land. It must match a registered pattern, otherwise the create fails with validation_error.
go
session, err := scope.ConnectSessions.Create(ctx, partner.ConnectSessionCreateParams{
ReturnURL: "https://app.example.com/bb-callback",
})
if err != nil {
return err
}
redirectUserTo(session.HostedURL)The redirect back to your return URL is a UX signal - the authoritative result is the session state:
go
done, err := scope.ConnectSessions.WaitForTerminal(ctx, session.SessionID)
if err != nil {
return err
}
if done.State == partner.SessionCompleted {
fmt.Println("connected:", *done.ConnectionID)
}5. Receive webhooks
Configure your webhook URL in the portal (per mode); every delivery is signed. See the webhook verification guide for the full contract.
go
http.HandleFunc("/webhooks/bb", func(w http.ResponseWriter, r *http.Request) {
body, err := io.ReadAll(r.Body) // the RAW body bytes
if err != nil {
w.WriteHeader(http.StatusBadRequest)
return
}
result := partner.Verify(
[]string{"{{BB_WEBHOOK_SECRET}}"}, // ALL active secrets (two during rotation)
r.Header.Get(partner.SignatureHeader),
body,
)
if result != partner.VerifyValid {
w.WriteHeader(http.StatusUnauthorized)
return
}
event, err := partner.ParseEvent(body)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
return
}
if ev, ok := event.(*partner.WebhookEvent); ok && ev.Type == partner.EventConnectionCreateSuccess {
// fetch data, update your records
}
w.WriteHeader(http.StatusOK) // always 2xx for verified deliveries - unknown types included
})6. Read accounts and transactions
go
// Every account of the connection, all pages walked. An account the user left
// unchecked in the account picker is listed too, as SubscriptionStatus "Disabled".
accounts, err := scope.Connections.ListAccounts(ctx, connectionID)
if err != nil {
return err
}
for tx, err := range scope.Accounts.Transactions(ctx, accounts[0].ID, partner.ListTransactionsParams{}) {
if err != nil {
return err
}
// tx.Amount is a *partner.Decimal - money is never a float.
// Only ID, Seq, CreatedSeq and RecordDate are guaranteed; every other
// field is a pointer that may be nil.
_ = tx
}Polling for changes? Set SinceSeq: partner.Ptr(lastSeq) in partner.ListTransactionsParams to get every row inserted or modified since the highest Seq you have seen.
Error handling
Branch on error.code only - never parse messages (full code reference):
go
_, err := scope.Connections.Refresh(ctx, connectionID)
var apiErr *partner.APIError
if errors.As(err, &apiErr) && apiErr.Code == partner.CodeRefreshCooldown {
fmt.Println("retry after", apiErr.NextRefreshPossibleAt)
} else if err != nil {
return err
}The SDK already retries 429/5xx with exponential backoff (honoring Retry-After) and auto-generates Idempotency-Key on creates.
Going live
Complete the go-live checklist in the portal (card verification, business check, terms) - production unlocks automatically and you can mint a bb_live_… key. The code above works unchanged; only the key differs.