Skip to content

CryptoLike SDKs

Official server-side client libraries for the merchant API — one per language, the same behaviour in each: signed requests, an Idempotency-Key on every POST, retries with backoff, typed resources and webhook verification. The API itself is described in docs/API.md; this page gets you from an empty project to the first confirmed payment in each language.

Pre-release: nothing is published to a registry yet. npm, PyPI, Packagist and the Go module proxy all answer 404 for these names today — the commands in the Install column are the ones you will run after the public release. Until then each SDK installs from a checkout of this repository in one or two commands: Installing from the repository (pre-release). Nothing else on this page changes: the same code, the same environment variables, the same vectors.

Language Package Install (after the release) Requires Source
TypeScript / Node.js @cryptolike/sdk npm install @cryptolike/sdk Node ≥ 18 sdk/typescript
Python cryptolike pip install cryptolike Python ≥ 3.10, requests sdk/python
Go github.com/Petya88/Cryptolike/sdk/go go get github.com/Petya88/Cryptolike/sdk/go Go ≥ 1.23, stdlib only sdk/go
PHP cryptolike/sdk composer require cryptolike/sdk PHP ≥ 8.1, ext-curl or PSR-18 sdk/php

Every SDK sends User-Agent: cryptolike-sdk-<language>/<version>, pins X-API-Version: v1, and passes the same reference vectors (docs/sdk/vectors.json, generated from the platform's own signer) in its test suite.

The secret stays on your server — read this first

Your API secret never goes to a browser or a mobile app. It signs requests as you: whoever can read your page's JavaScript could create payments, read balances and change your webhooks.

  • Your server holds the key id and secret (environment variable or secret manager — every SDK reads CRYPTOLIKE_API_KEY_ID / CRYPTOLIKE_API_SECRET by default) and creates payments.
  • Your page receives only the invoice id or checkout_url and sends the customer there — or embeds the payment page (docs/WIDGET.md), which needs nothing but the id.
  • The TypeScript SDK throws BrowserSecretError when loaded in a browser.
  • Webhook secrets are server-side too: verifyWebhook runs where the delivery arrives.
  • No SDK logs the secret itself: none of them writes to stdout/stderr and none has a debug or verbose mode. Each one also redacts the client object, but only on the dump paths below — those are the ones a logger, an error reporter or a console.log actually takes. Anything not listed as covered prints the fields as they are, so treat a client like any other credential holder and never hand it to a generic object dumper.
SDK Redacted (verified) Still prints the secret
TypeScript console.log(client), util.inspect (any options, incl. showHidden / customInspect: false), console.dir, %o/%j in a logger, JSON.stringify, spread / Object.entries — the secret is a #private field and the class has both toJSON() and a nodejs.util.inspect.custom hook
Python repr(client), str(client), f-strings, print(client) vars(client) / client.__dict___secret is a plain attribute
Go fmt.Print, %v, %+v, %s, %q, log.Print — through String() %#v (fmt.Sprintf("%#v", c)), which prints the struct literal
PHP var_dump, print_r, json_encode — through __debugInfo(); serialize() refuses (closures) var_export($client, true), where PHP ignores __debugInfo()

What every SDK does for you

Concern Behaviour (identical across languages) Contract
Signing X-API-Key, X-Timestamp (fresh per attempt), X-Signature = HMAC-SHA256 over timestamp\nMETHOD\npath?query\nsha256(body) docs/API-AUTH.md §3
Idempotency Every POST carries Idempotency-Key: yours (e.g. your order id) or a UUID generated once per call and reused on each retry — a retry never creates a second invoice, address or withdrawal docs/API-AUTH.md §7
Retries 429 (after Retry-After, or backoff), 5xx except 501, connection/timeout errors — up to 3 retries, min(8 s, 0.5 s · 2^attempt) with full jitter; a Retry-After above 30 s stops retrying docs/API-AUTH.md §6
Same-second replay X-Timestamp is whole seconds, so no two attempts of one call are ever signed in the same second: before re-signing, the client waits out the second the previous attempt used. A retry after a genuine 5xx therefore comes back as the real error, never as 401 auth.replay. If the server still reports auth.replay (a truly concurrent identical request), the client waits for the next second, signs afresh and retries once — counted against the retry budget docs/API-AUTH.md §5
Errors One error type carrying status, code (stable machine code), message, details, field, retry_after, request_id; 4xx is never retried docs/API.md §5
Amounts Decimal strings ("10.50") — Python exposes Decimal; floats are refused everywhere docs/API.md §6
Pagination list returns {items, next_cursor}; iterate / Each walks every page docs/API.md §6
Webhooks verifyWebhook(headers, rawBody, secret, {tolerance: 300}): sha256= prefix, constant-time compare, timestamp within tolerance, then the parsed event. The options argument is idiomatic per language — TypeScript an object, Python a tolerance= keyword, Go *VerifyOptions{Tolerance: time.Duration} (nil = defaults), PHP the seconds themselves (Webhook::verify($headers, $body, $secret, ?int $tolerance = 300)); the default tolerance is 300 s in all four docs/WEBHOOKS.md §3
Version X-API-Version: v1 on every request; only additive changes within v1 docs/API-CHANGELOG.md

Resources covered: invoices (create / get / list / cancel / export), static addresses, transactions, balances, currencies, rates, withdrawals (+ estimate), webhook endpoints and deliveries, and the checkout-widget allowlist (/v1/embed). Anything newer than your SDK version is reachable through the generic request method with the same signing and retries.

Before you start (all languages)

  1. Create a key in the cabinet: API keys → Create key with create_invoice and read. Copy the secret — it is shown once. Use a ck_test_… key against a dev/stage instance first.
  2. Create a webhook endpoint (cabinet → Webhooks, or webhooks.endpoints.create below) for invoice.paid (or *); store its secret next to the API secret.
  3. Export the three values on your server:
bash
export CRYPTOLIKE_API_KEY_ID=ck_test_…
export CRYPTOLIKE_API_SECRET=…
export CRYPTOLIKE_WEBHOOK_SECRET=whsec_…
# Where the API lives. Defaults to https://api.cryptolike.net — set it whenever your key is not
# a production key: a `ck_test_…` key from a dev or stage instance only works against that host.
export CRYPTOLIKE_API_URL=https://api.stage.example

A payment is priced in fiat (amount + currency) and the payer chooses the coin and network on the checkout page; pass coin_id (from currencies.list) only when you want to fix that choice up front. The rest of the flow is the same everywhere: create the payment → send the customer to checkout_url → receive invoice.paid on your webhook (verify, deduplicate by event_id, fulfil) — or poll invoices.get until the status is final (paid, expired, cancelled).

Installing from the repository (pre-release)

Until the packages are published, every SDK is installed from a checkout. Clone the repository once (access is granted per integrator while it is private) — $CL below is the path to that clone:

bash
CL=~/src/cryptolike                                    # anywhere you like
git clone git@github.com:Petya88/Cryptolike.git "$CL"

Then, in your project:

bash
# TypeScript / Node.js — build a tarball once, install it like any other package
(cd "$CL/sdk/typescript" && npm ci && npm run pack)   # → pack/cryptolike-sdk-0.1.0.tgz
npm install "$CL/sdk/typescript/pack/cryptolike-sdk-0.1.0.tgz"

# Python (into your virtualenv)
pip install "$CL/sdk/python"

# Go — the module is not on the proxy, so resolve it from disk
go mod edit -replace github.com/Petya88/Cryptolike/sdk/go="$CL/sdk/go"
go mod tidy

# PHP — a path repository next to Packagist (run where your composer.json is)
composer config repositories.cryptolike path "$CL/sdk/php"
composer require cryptolike/sdk:@dev

Every import, class name and snippet below is the same as after the release; switching over later means replacing the tarball with @cryptolike/sdk, dropping the replace line, or removing the path repository. All four recipes are verified against an empty project; the installed SDK is the same build the test suites and the reference vectors run against.

TypeScript / Node.js

bash
# pre-release (see above); after the release: npm install @cryptolike/sdk
npm install "$CL/sdk/typescript/pack/cryptolike-sdk-0.1.0.tgz"
ts
import { CryptoLike, ApiError } from "@cryptolike/sdk";

const api = new CryptoLike(); // credentials from the environment

// 1. Create the payment on your server
const invoice = await api.invoices.create(
  { amount: "10.50", currency: "USD", order_id: "A-1001", return_url: "https://shop.example/orders/A-1001" },
  "order-A-1001", // Idempotency-Key
);
// 2. Send the customer there
res.redirect(invoice.checkout_url);
ts
// 3. Webhook (Express) — RAW body, verify, dedupe, 2xx fast
import express from "express";
import { verifyWebhook, WebhookVerificationError } from "@cryptolike/sdk";

app.post("/hooks/cryptolike", express.raw({ type: "application/json" }), (req, res) => {
  try {
    const event = verifyWebhook(req.headers, req.body, process.env.CRYPTOLIKE_WEBHOOK_SECRET!);
    if (event.type === "invoice.paid" && !seen(event.event_id)) fulfil(event.data.invoice);
    res.sendStatus(200);
  } catch (e) {
    if (e instanceof WebhookVerificationError) return res.sendStatus(401);
    throw e;
  }
});
ts
// or poll
const detail = await api.invoices.get(invoice.id);
if (detail.status === "paid") fulfil(detail);

Errors: catch (e) { if (e instanceof ApiError && e.code === "validation_error") … }. Full README and examples: sdk/typescript.

Python

bash
# pre-release (see above); after the release: pip install cryptolike
pip install "$CL/sdk/python"
python
from cryptolike import Client, ApiError

api = Client()  # credentials from the environment

# 1. Create the payment on your server
invoice = api.invoices.create(
    amount="10.50", currency="USD", order_id="A-1001",
    return_url="https://shop.example/orders/A-1001",
    idempotency_key="order-A-1001",
)
# 2. Send the customer there
return redirect(invoice.checkout_url)
python
# 3. Webhook (Flask) — RAW body, verify, dedupe, 2xx fast
from flask import request, abort
from cryptolike import verify_webhook, WebhookVerificationError

@app.post("/hooks/cryptolike")
def hook():
    try:
        event = verify_webhook(request.headers, request.get_data(), os.environ["CRYPTOLIKE_WEBHOOK_SECRET"])
    except WebhookVerificationError:
        abort(class="tok-num">401)
    if event.type == "invoice.paid" and not seen(event.event_id):
        fulfil(event.data["invoice"])
    return "", class="tok-num">200
python
# or poll
detail = api.invoices.get(invoice.id)
if detail.status == "paid":
    fulfil(detail)

Amounts come back as decimal.Decimal; errors are ApiError (.status, .code, .field). Full README and examples: sdk/python.

Go

bash
# pre-release (see above); after the release: go get github.com/Petya88/Cryptolike/sdk/go
go mod edit -replace github.com/Petya88/Cryptolike/sdk/go="$CL/sdk/go" && go mod tidy
go
import cryptolike "github.com/Petya88/Cryptolike/sdk/go"

api, err := cryptolike.New(cryptolike.Options{}) // credentials from the environment

// 1. Create the payment on your server
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
// 2. Send the customer there
http.Redirect(w, r, inv.CheckoutURL, http.StatusFound)
go
// 3. Webhook — RAW body, verify, dedupe, 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 event.Type == "invoice.paid" && !seen(event.EventID) {
        var data struct{ Invoice cryptolike.Invoice `json:"invoice"` }
        _ = json.Unmarshal(event.Data, &data)
        fulfil(data.Invoice)
    }
    w.WriteHeader(http.StatusOK)
})
go
// or poll
detail, err := api.Invoices.Get(ctx, inv.ID)
if err == nil && detail.Status == "paid" { fulfil(*detail) }

Errors: var apiErr *cryptolike.APIError; if errors.As(err, &apiErr) { apiErr.Code … }. Full README and examples: sdk/go.

PHP

bash
# pre-release (see above); after the release: composer require cryptolike/sdk
composer config repositories.cryptolike path "$CL/sdk/php" && composer require cryptolike/sdk:@dev
php
use CryptoLike\Client;

$api = new Client(); // credentials from the environment

// 1. Create the payment on your server
$invoice = $api->createInvoice(
    ['amount' => '10.50', 'currency' => 'USD', 'order_id' => 'A-1001', 'return_url' => 'https://shop.example/orders/A-1001'],
    'order-A-1001', // Idempotency-Key
);
// 2. Send the customer there
header('Location: ' . $invoice['checkout_url'], true, class="tok-num">302);
php
// 3. Webhook — RAW body, verify, dedupe, 2xx fast
use CryptoLike\Webhook;
use CryptoLike\WebhookVerificationException;

try {
    $event = Webhook::verify(getallheaders(), file_get_contents('php://input'), getenv('CRYPTOLIKE_WEBHOOK_SECRET'));
} catch (WebhookVerificationException $e) {
    http_response_code(class="tok-num">401); exit;
}
if ($event['type'] === 'invoice.paid' && !seen($event['event_id'])) { fulfil($event['data']['invoice']); }
http_response_code(class="tok-num">200);
php
// or poll
$detail = $api->getInvoice($invoice['id']);
if ($detail['status'] === 'paid') { fulfil($detail); }

Laravel / Symfony: Webhook::verify($request->headers->all(), $request->getContent(), $secret). Errors: catch (CryptoLike\ApiException $e) { $e->status, $e->apiCode, $e->field() }. Full README and examples: sdk/php.

Checklist for the first payment

  • [ ] Key created with create_invoice + read; secret in your server's environment only.
  • [ ] Webhook endpoint created (https://, public host) with its secret next to the API secret.
  • [ ] Payment created with your order id as Idempotency-Key; customer sent to checkout_url.
  • [ ] Webhook verified over the raw body, event_id stored, invoice.paid fulfils the order.
  • [ ] expired / cancelled handled (show "Payment expired. Create a new payment.").
  • [ ] Clock synced (NTP): signatures are valid for ±5 minutes.

Reference vectors and tests

docs/sdk/vectors.json holds request-signing vectors (query strings, unicode bodies, PUT / DELETE) and webhook vectors — five valid, four that must be rejected (tampered body, wrong secret, changed timestamp, missing sha256=) plus an upper-case-hex case. Every SDK's test suite pins them; the file itself is regenerated from the platform's signer by go test ./docs -run TestSDKVectors -update, so a change in the signing contract turns every SDK red at once. Use the same file to test an SDK of your own.

Run the suites: sdk/typescriptnpm ci && npm test; sdk/pythonpip install -e '.[test]' && pytest; sdk/gogo test -race ./...; sdk/phpphp tests/run.php. CI runs all four (.github/workflows/ci-sdk.yml) and keeps the built npm package as an artifact; nothing is published automatically — and nothing is published at all yet, so integrators install from the repository (see "Installing from the repository (pre-release)").

Versioning

SDK versions follow SemVer independently of the API contract; each SDK's User-Agent names its version. Within API v1 only additive changes ship (docs/API-CHANGELOG.md); a field added to the API appears in the raw response of every SDK immediately (TypeScript: regenerate the types; Python: .raw; Go: add the field; PHP: arrays) and in the typed surface with the next SDK release.