Go
cryptolike (Go SDK)
Official server-side SDK for the CryptoLike merchant API. Go ≥ 1.23, standard library only.
Pre-release: the Go module proxy has nothing under this name yet. Install from a checkout of the repository (
$CL= path to the clone, see SDK.md); the command below is the one for after the public release.
go mod edit -replace github.com/Petya88/Cryptolike/sdk/go="$CL/sdk/go" && go mod tidy
# after the release:
go get github.com/Petya88/Cryptolike/sdk/goimport cryptolike "github.com/Petya88/Cryptolike/sdk/go"- Signed requests (
X-API-Key/X-Timestamp/X-Signature, HMAC-SHA256 — API-AUTH) Idempotency-Keygenerated for everyPOST(pass your own to make retries exact)- Retries with exponential backoff and jitter on
429(honoursRetry-After),5xxand transport errors;contextaware - Typed resources mirroring
docs/openapi.yaml; amounts are decimal strings, never floats VerifyWebhook— signature and timestamp check of webhook deliveries (WEBHOOKS)
The secret stays on your server
The API secret signs requests as you: whoever has it can create payments, read balances and change your webhooks. Use this package from your backend only; never embed the secret in a mobile app or anything served to a browser. Your page needs only the invoice id /
CheckoutURLyour server obtained — see WIDGET §1.
New reads CRYPTOLIKE_API_KEY_ID and CRYPTOLIKE_API_SECRET from the environment when
Options leaves them empty; the secret is never logged and Client.String() redacts it, which
covers %v, %+v, %s, %q and log.Print. %#v prints the struct literal — do not dump a
client that way.
Quickstart — to the first confirmed payment
api, err := cryptolike.New(cryptolike.Options{}) // credentials from the environment
// 1. Create a payment on your server; send the customer to CheckoutURL.
inv, err := api.Invoices.Create(ctx, cryptolike.CreateInvoiceRequest{
Amount: "10.50", Currency: "USD", OrderID: "A-1001", ReturnURL: "https://shop.example/orders/A-1001",
}, "order-A-1001") // Idempotency-Key — a retry never creates a second payment
http.Redirect(w, r, inv.CheckoutURL, http.StatusFound)
// 2. Learn the outcome from the webhook (below) — or poll:
detail, err := api.Invoices.Get(ctx, inv.ID)
if detail.Status == "paid" { fulfil(*detail.OrderID) }// 3. Webhook receiver — verify over the RAW body, dedupe by EventID, answer 2xx fast.
http.HandleFunc("POST /hooks/cryptolike", func(w http.ResponseWriter, r *http.Request) {
body, _ := io.ReadAll(http.MaxBytesReader(w, r.Body, 1<<20))
event, err := cryptolike.VerifyWebhook(r.Header, body, os.Getenv("CRYPTOLIKE_WEBHOOK_SECRET"), nil)
if err != nil { w.WriteHeader(http.StatusUnauthorized); return }
if !alreadySeen(event.EventID) { handle(event) } // event.Type == "invoice.paid" → fulfil
w.WriteHeader(http.StatusOK)
})Full examples: examples/create-payment, examples/webhook.
Resources
| Resource | Methods |
|---|---|
api.Invoices |
Create(ctx, req, idempotencyKey), Get, List(ctx, q), Each(ctx, q, fn), Cancel, ExportCSV |
api.Addresses |
Create, Get(ctx, id, q), List, Each, Archive(ctx, id, idempotencyKey), Restore(ctx, id, idempotencyKey) |
api (archive) |
ArchiveStaticAddress, RestoreStaticAddress, ListArchivedStaticAddresses(ctx, q), EachArchivedStaticAddress(ctx, q, fn) |
api.Transactions |
List, Each, Get, ExportCSV |
api.Balances / api.Currencies / api.Rates |
List(ctx) (Rates.List(ctx, "EUR")) |
api.Withdrawals |
Estimate, Create, Get, List, Each, Cancel |
api.Webhooks.Endpoints |
List, Create, Get, Update, Delete, RotateSecret |
api.Webhooks.Deliveries |
List, Get, Retry |
api.Embed |
Get, Set(ctx, origins) — checkout widget allowlist |
api.Request(ctx, method, path, query, body, idempotencyKey, &out) |
any endpoint, signed |
Static-address archive
Archiving stops offering an address and takes it out of real-time scanning; it is never deleted, and its address is never issued again. The platform checks archived addresses for incoming transfers regularly (every 24 h by default): a transfer found is credited as usual and restores the address automatically (webhook static_address.restored, reason deposit). Addresses with no incoming transfers for the platform's period (180 days by default) are archived automatically after a warning 7 days earlier (static_address.archive_warning, then static_address.archived with reason auto).
_, err := api.ArchiveStaticAddress(ctx, id, "") // POST /v1/addresses/{id}/archive — a repeat is a no-op
page, err := api.ListArchivedStaticAddresses(ctx, url.Values{"search": {"shop"}}) // status=archived; coin_id, customer_id, limit, cursor
err = api.EachArchivedStaticAddress(ctx, nil, func(a cryptolike.StaticAddress) bool { return true })
_, err = api.RestoreStaticAddress(ctx, id, "") // *APIError 409 conflict, Details["active_address_id"]: archive that one firstDecode a verified archive event with event.DecodeData(&d) into StaticAddressArchivedData, StaticAddressRestoredData (Reason, Deposit, ConflictWith) or StaticAddressArchiveWarningData (ArchiveAfter, InactiveSince); types EventStaticAddressArchived|Restored|ArchiveWarning. Full example: examples/static-address-archive, examples/webhook.
List filters are url.Values named as in the API — cryptolike.Q("status", "paid", "limit", "50").
List returns a Page[T] (Items, NextCursor); Each walks every page. Nullable fields are
pointers; timestamps are time.Time.
Payer chooses the coin
An invoice in a fiat currency (or a ticker on several networks) without CoinID lets the payer
choose the coin on the payment page; each option shows an estimate (≈) — the exact amount and
rate are fixed when the payer picks. AllowedCoinIDs narrows the choice for one invoice; leave
it nil to offer every coin you offer for invoices:
coins, _ := api.Currencies.List(ctx) // signed: each coin carries InvoiceEnabled
var ids []string
for _, c := range coins {
if (c.InvoiceEnabled == nil || *c.InvoiceEnabled) && !c.Maintenance && c.Ticker == "USDT" {
ids = append(ids, c.ID)
}
}
inv, err := api.Invoices.Create(ctx, cryptolike.CreateInvoiceRequest{
Amount: "49.90", Currency: "USD", AllowedCoinIDs: ids,
}, "")
// inv.AllowedCoinIDs — the kept set; nil = every coin offered for invoices.The server keeps the ids you offer for invoices («Available for invoices» in the cabinet → Coins)
that match Currency, drops the rest; none left → *APIError 422 no_allowed_coins
(Field() == "allowed_coin_ids"). Not combinable with CoinID (400). An empty non-nil slice is
refused before the request (ErrEmptyAllowedCoins) — a filter that found nothing never turns
into "every coin".
Errors
A non-2xx response is an *APIError (Status, Code, Message, Details, Field(),
RetryAfter, RequestID); a transport failure that survived the retries is a *TransportError
(unwraps to the cause, including context.Canceled). Use errors.As. Codes:
API-AUTH §5, API §5.
A payout with a key whose withdraw a developer requested and an owner/admin has not approved yet is
403 CodeWithdrawPendingApproval (apiErr.WithdrawPendingApproval()): nothing was created, not
retried — approve the request in the cabinet (API keys); the key's other permissions work meanwhile.
Retries and idempotency
429 (after Retry-After, or backoff), 5xx except 501, and connection errors are retried up to
MaxRetries (default 3; -1 disables) with min(RetryMax, RetryBase · 2^attempt) full-jitter
backoff, stopping when the context ends. Every POST carries an Idempotency-Key (yours, or a
UUID generated once per call and reused on each retry), so a retry never creates a second invoice,
address or withdrawal. Each attempt is signed with a fresh timestamp. Redirects are never followed.
A 401 auth.replay — two identical requests signed within the same second share a signature —
waits for the next second and signs afresh.
Options
cryptolike.Options{
KeyID, Secret, // default: CRYPTOLIKE_API_KEY_ID / CRYPTOLIKE_API_SECRET
BaseURL, // default: CRYPTOLIKE_API_URL or https://api.cryptolike.net
HTTPClient: &http.Client{Timeout: 30 * time.Second},
MaxRetries: 3, RetryBase: 500 * time.Millisecond, RetryMax: 8 * time.Second, RetryAfterMax: 30 * time.Second,
APIVersion: "v1", // X-API-Version pin
AppInfo: "my-shop/2.3", // appended to User-Agent: cryptolike-sdk-go/<version>
}Webhook verification
VerifyWebhook(headers, rawBody, secret, &VerifyOptions{Tolerance: 5 * time.Minute}) returns the
event (EventID, Type, CreatedAt, Data as json.RawMessage) or a *WebhookVerificationError
with Reason (missing_header | invalid_timestamp | timestamp_out_of_tolerance |
invalid_signature | invalid_body). Answer 401/400 on failure, 2xx on success, and
deduplicate by EventID — deliveries are at least once and may arrive out of order.