The Turnkey Go SDK is the official Go client for interacting with the Turnkey API.
Warning
Migrating from v1? Update github.com/tkhq/go-sdk to the new module path:
github.com/tkhq/go-sdk/v2— API client and types
The v2 module will automatically pull in the required crypto and encoding dependencies
github.com/tkhq/go-sdk/cryptogithub.com/tkhq/go-sdk/encoding
The SDK has three importable Go modules: (one for the core API client and one for each major feature area)
| Module | Import Path | Purpose |
|---|---|---|
| Root | github.com/tkhq/go-sdk/v2 |
API client, generated request/response types |
| Crypto | github.com/tkhq/go-sdk/crypto |
API key generation, signing, encryption, attestation |
| Encoding | github.com/tkhq/go-sdk/encoding |
Hex, Base58, and JSON encoding utilities |
go-sdk/
├── client.go # HTTP client
├── client_gen.go # generated API methods
├── client_extensions.go # hand-written client helpers
├── types_gen.go # generated request/response types
├── types_extensions.go # hand-written type helpers
├── stamper.go # request signing (stamp) implementation
├── crypto/ # key generation, signing, encryption, attestation
│ ├── apikey.go
│ ├── apikey_ecdsa.go
│ ├── apikey_ed25519.go
│ ├── constants.go
│ ├── enclave.go
│ ├── encryptionkey.go
│ ├── hpke.go
│ ├── store.go
│ └── verify.go
├── encoding/ # hex, base58, JSON utilities
│ ├── base58.go
│ ├── hex.go
│ └── json.go
├── codegen/ # code generation tooling
│ ├── main.go
│ ├── generators/ # per-file code generators
│ └── inputs/ # activities.json + swagger specs
└── examples/
├── apikey/
├── delegated_access/
├── otp/
├── signing/
├── wallets/
└── whoami/
go get github.com/tkhq/go-sdk/v2The root v2 module pulls in the crypto and encoding modules it needs. If you want to use those modules directly, install them explicitly:
go get github.com/tkhq/go-sdk/crypto
go get github.com/tkhq/go-sdk/encodingIn order to use the SDK, you first need to create and register an API key. When creating API keys, the private key never leaves the local system, but the public key must be registered to your Turnkey account.
The easiest way to manage your API keys is with the Turnkey CLI, but you can also create one using this SDK. See this example.
package main
import (
"context"
"fmt"
"log"
"os"
turnkey "github.com/tkhq/go-sdk/v2"
)
func main() {
// NB: make sure to create and register an API key first.
stamper, err := turnkey.NewAPIKeyStamper(os.Getenv("TURNKEY_API_PRIVATE_KEY"))
if err != nil {
log.Fatal("failed to create stamper:", err)
}
client, err := turnkey.NewClient(stamper, os.Getenv("TURNKEY_ORGANIZATION_ID"))
if err != nil {
log.Fatal("failed to create SDK client:", err)
}
resp, err := client.GetWhoami(context.Background(), turnkey.GetWhoamiRequest{})
if err != nil {
log.Fatal("failed to get whoami:", err)
}
fmt.Printf("UserID: %s\n", resp.UserID)
}NewAPIKeyStamper is the built-in stamper and covers most use cases. If you need signing to happen elsewhere — a hardware security module, AWS KMS, or a remote signing service — implement the Stamper interface and pass it to NewClient identically:
type Stamper interface {
Stamp(ctx context.Context, body []byte) (*Stamp, error)
}Any type that satisfies this interface works as a drop-in replacement without changing any other code.
API errors are returned as *turnkey.RequestError, which exposes the HTTP status code, parsed status message, and raw response body.
result, err := client.CreateWallet(ctx, input)
if err != nil {
log.Printf("failed to create wallet: %v", err)
if reqErr, ok := err.(*turnkey.RequestError); ok {
log.Printf("Turnkey API error (HTTP %d): %s", reqErr.StatusCode, reqErr.Body)
}
return nil, err
}For activity-specific flows, the SDK also returns *turnkey.ActivityFailedError (activity rejected or failed) and *turnkey.ActivityRequiresApprovalError (consensus required), type-assert these if you need to handle them explicitly.
By default, the SDK prints failed API responses to stdout via fmt.Printf. To route logs to Zap, Logrus, Datadog, or any other logger, implement the turnkey.Logger interface and pass it via WithLogger:
type myLogger struct{}
func (l *myLogger) Printf(format string, v ...interface{}) {
log.Printf("[turnkey] "+format, v...)
}
stamper, err := turnkey.NewAPIKeyStamper(os.Getenv("TURNKEY_API_PRIVATE_KEY"))
if err != nil {
log.Fatal("failed to create stamper:", err)
}
client, err := turnkey.NewClient(
stamper,
os.Getenv("TURNKEY_ORGANIZATION_ID"),
turnkey.WithLogger(&myLogger{}),
)See this README in examples for more complete code samples, including:
The SDK uses custom changeset tooling for changelog management. Each module (root, crypto, encoding) is versioned independently — a single release can bump any subset of them depending on which modules have pending changesets.
Step 1 — Create a changeset
Add one markdown file under .changesets/ for each releasable module change.
Each file uses frontmatter to identify the module, bump type (patch / minor
/ major), title, and date:
---
module: "root"
bump: "patch"
title: "Short release note"
date: "2026-07-09"
---
Longer release note text.Use module: "root" for github.com/tkhq/go-sdk/v2, module: "crypto" for
github.com/tkhq/go-sdk/crypto, and module: "encoding" for
github.com/tkhq/go-sdk/encoding. Repeat once per logical change. Changesets
accumulate in .changesets/ and can land across multiple PRs before a release
is cut.
Step 2 — Cut a release branch and open the PR
make release-branchMust be run from a clean main. For each module with pending changesets, this:
- Bumps the module's
VERSIONfile (patch/minor/major from the highest bump across its changesets). - Prepends a release section to the module's
CHANGELOG.md. - Deletes the consumed changeset files from
.changesets/. - Rewrites inter-module
go.modrequirements from local placeholder versions to the release versions. - Rewrites matching
go.workreplaces so the release branch can build before the new module tags exist. - Creates a
release/vYYYY-MM-Nbranch (whereNauto-increments per month), commits the changes, and optionally pushes + opens the PR viagh.
Review the diff, then merge the PR into main.
Step 3 — Tag and publish (automatic)
Merging a release/v* PR triggers .github/workflows/tag.yml, which (after manual approval on the Production environment):
- Lints, builds, and tests.
- Reads each module's
VERSIONand creates GitHub releases taggedvX.Y.Z,crypto/vX.Y.Z,encoding/vX.Y.Z. - Pings
sum.golang.orgso pkg.go.dev indexes the new versions.
The workflow can also be triggered manually via workflow_dispatch with a vYYYY-MM-N release id.
Contributions are welcome! Please open an issue or submit a pull request with any improvements or bug fixes.
This project is licensed under the Apache License 2.0. See the LICENSE file for details.