Modulegithub.com/Appiasoft/compense-saas-sdk-go (package compense, subpackage webhook)
Version1.1.0 (compense.Version) · API v1
RequirementsGo 1.21+ (range-over-func iterators with Go 1.23+)
DependenciesStandard library only
RepositoryAppiasoft/compense-saas-sdk-go · guides in docs/ · examples in examples/

Install #

Shell
go get github.com/Appiasoft/compense-saas-sdk-go@v1.1.0
# Private repository: GOPRIVATE=github.com/Appiasoft/* and git credentials

Client #

Go
import compense "github.com/Appiasoft/compense-saas-sdk-go"

client, err := compense.NewClient(os.Getenv("COMPENSE_API_KEY"),
	compense.WithEnvironment(compense.Sandbox),  // Production (default) | Sandbox | Dev
	compense.WithBaseURL("https://gw.example.co"), // another URL; takes precedence over the environment
	compense.WithHTTPClient(myHTTPClient),       // proxies, mTLS, tracing, tests (not modified)
	compense.WithTimeout(30*time.Second),        // per attempt (only for the client the SDK creates)
	compense.WithRetry(compense.RetryConfig{MaxAttempts: 3}),
	compense.WithRetryWrites(false),
	compense.WithUserAgent("horizonte-erp/2.0"),
	compense.WithLogger(slog.Default()),         // deprecation warnings
)
if err != nil {
	log.Fatal(err) // *compense.ConfigError
}

The *Client is safe for concurrent use: create one per key and reuse it. Every method takes a context.Context first (deadlines and cancellation, including during the waits between retries).

Conventions #

  • Services in PascalCase with the same namespaces as Python: client.Wallet.Bills.Pay, client.Admin.HR.CreateNovelty, client.PQRS.Submit.
  • Optional parameters in *Params structs (pass nil if you have none); empty fields aren’t sent. Optional booleans with compense.Bool(true).
  • The Idempotency-Key goes as a variadic option: compense.WithIdempotencyKey("...").
  • Writes → *compense.Transaction (Data map[string]any); reads → models (*Balance, *Resolution, *Page, *OffsetPage…) or map[string]any; every model keeps Raw.
  • Responses are decoded with json.Number: numbers keep their exact digits.
  • Amounts: compense.Amount accepts text constants ("65000") or AmountFromInt, AmountFromRat and AmountFromFloat (discouraged).
  • Decisions: compense.DecisionApprove / compense.DecisionReject.

Errors #

Every non-2xx response is a *compense.APIError (StatusCode, Title, Detail, TraceID, Errors(), RetryAfter, Problem). Classify them with errors.Is or the helpers:

StatusSentinelHelper
401, 403ErrAuthIsAuthError
404ErrNotFoundIsNotFound
409ErrIdempotencyConflictIsIdempotencyConflict
422ErrValidationIsValidationError
429ErrRateLimitedIsRateLimited
502, 504ErrUpstreamIsUpstreamError
other 5xxErrServerIsServerError

The SDK’s client doesn’t follow redirects: a 3xx arrives as an *APIError.

Pagination #

Go
it := compense.NewPageIterator(ctx, func(ctx context.Context, page int) (*compense.Page, error) {
	return client.Webhooks.Deliveries.List(ctx, &compense.DeliveryListParams{Status: "failed", Page: page, PageSize: 100})
})
for it.Next() {
	client.Webhooks.Deliveries.Replay(ctx, fmt.Sprint(it.Item()["id"]))
}
if err := it.Err(); err != nil {
	log.Fatal(err)
}
// Go 1.23+: for item, err := range it.All() { ... }

NewOffsetIterator does the same for limit/offset lists.

Webhooks #

Go
import "github.com/Appiasoft/compense-saas-sdk-go/webhook"

http.Handle("/compense/webhooks", webhook.Handler([]string{newSecret, oldSecret},
	func(ctx context.Context, ev *webhook.Event) error {
		if ev.IsTest() || alreadyApplied(ev.ID) {
			return nil
		}
		return handle(ev.Event(), ev.Resource())
	}))

Handler responds 401 if verification fails, 500 if your function returns an error (so Compense retries) and 200 otherwise. Outside net/http, use webhook.Verify(rawBody, header, secrets, opts...) or webhook.VerifyRequest(r, secrets). Options: WithTolerance, WithoutTolerance, WithNow. Errors: webhook.ErrVerification (ErrSignature, ErrTimestamp) and ErrNoSecret. See Verifying the signature.

Testing #

Start an httptest.Server and point the client at it:

Go
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
	if r.Header.Get("X-API-Key") != "cmp_test" {
		w.WriteHeader(http.StatusUnauthorized)
		return
	}
	w.Header().Set("Content-Type", "application/json")
	fmt.Fprint(w, `{"total":"150000.00","available_cash":"150000.00","currency":"COP"}`)
}))
defer srv.Close()

client, _ := compense.NewClient("cmp_test", compense.WithBaseURL(srv.URL))
balance, _ := client.Wallet.Balance(context.Background(), "1032456789")
fmt.Println(balance.Available.Amount) // 150000.00