Source: https://cryptolike.net/docs/sdk

# 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 [API overview](https://cryptolike.net/docs/api); 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)](#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 7.4 – 8.x, no runtime dependencies; `ext-curl`, the stream wrapper or PSR-18; Composer optional (`autoload.php`) | `sdk/php` |

Every SDK sends `User-Agent: cryptolike-sdk-<language>/<version>`, pins `X-API-Version: v1`,
and passes the same reference vectors ([`vectors.json`](https://cryptolike.net/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 ([Checkout widget](https://cryptolike.net/docs/widget)), 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)` | [Authentication](https://cryptolike.net/docs/auth) §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 | [Authentication](https://cryptolike.net/docs/auth) §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 | [Authentication](https://cryptolike.net/docs/auth) §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 | [Authentication](https://cryptolike.net/docs/auth) §5 |
| Errors | One error type carrying `status`, `code` (stable machine code), `message`, `details`, `field`, `retry_after`, `request_id`; `4xx` is never retried | [API overview](https://cryptolike.net/docs/api) §5 |
| Amounts | Decimal strings (`"10.50"`) — Python exposes `Decimal`; floats are refused everywhere | [API overview](https://cryptolike.net/docs/api) §6 |
| Pagination | `list` returns `{items, next_cursor}`; `iterate` / `Each` walks every page | [API overview](https://cryptolike.net/docs/api) §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 | [Webhooks](https://cryptolike.net/docs/webhooks) §3 |
| Version | `X-API-Version: v1` on every request; only additive changes within v1 | API changelog |

Resources covered: invoices (create / get / list / cancel / export), static addresses (with the
archive: `archiveStaticAddress` / `restoreStaticAddress` / `listArchivedStaticAddresses`, below),
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.

## Static-address archive (all languages)

An archived address is no longer offered and leaves real-time scanning; it is never deleted and
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.
Addresses with no incoming transfers for 180 days (platform setting) are archived automatically,
after a warning 7 days earlier. Restoring is refused with `409 conflict`
(`details.active_address_id`) while the customer has another active address for the currency —
archive that one first.

| | TypeScript | Python | Go | PHP |
|---|---|---|---|---|
| Archive | `api.archiveStaticAddress(id)` | `api.archive_static_address(id)` | `api.ArchiveStaticAddress(ctx, id, key)` | `$api->archiveStaticAddress($id)` |
| Restore | `api.restoreStaticAddress(id)` | `api.restore_static_address(id)` | `api.RestoreStaticAddress(ctx, id, key)` | `$api->restoreStaticAddress($id)` |
| Archive list (`status=archived` forced; `search`, `coin_id`, `customer_id`) | `listArchivedStaticAddresses(q)`, `iterateArchivedStaticAddresses(q)` | `list_archived_static_addresses(**f)`, `iterate_archived_static_addresses(**f)` | `ListArchivedStaticAddresses(ctx, q)`, `EachArchivedStaticAddress(ctx, q, fn)` | `listArchivedStaticAddresses($f)`, `iterateArchivedStaticAddresses($f)` |
| Archive webhooks (`static_address.archived` / `.restored` / `.archive_warning`) | `isWebhookEvent(event, StaticAddressEvent.Restored)` → typed `event.data` | `event.typed_data()` → `StaticAddress*Data` | `event.DecodeData(&cryptolike.StaticAddressRestoredData{})` | `Webhook::STATIC_ADDRESS_*` + documented array shape |
| Example | `examples/static-address-archive.ts` | `examples/static_address_archive.py` | `examples/static-address-archive` | `examples/static-address-archive.php` |

They are the same signed calls as `addresses.archive` / `addresses.restore` / `addresses.list`
(`POST /v1/addresses/{id}/archive|restore`, `GET /v1/addresses?status=archived`); the webhook
`data` types follow `StaticAddress*Data` in [OpenAPI spec](https://api.cryptolike.net/docs/openapi.yaml) ([Webhooks](https://cryptolike.net/docs/webhooks) §5).

## 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. Start with a `ck_test_…` key: test keys work with test networks and are
   created in the cabinet of the test environment (CryptoLike support gives you access and its API URL).
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 only works with test networks, never with the production API.
export CRYPTOLIKE_API_URL=…   # with a ck_test_ key: the test API URL
```

A payment is priced in fiat (`amount` + `currency`) and the **payer** chooses the coin and network
on the checkout page (each option with an informative `≈` amount; the exact amount and rate are
fixed when the payer picks); pass `coin_id` (from `currencies.list`) only when you want to fix that
choice up front, or `allowed_coin_ids` to narrow it for one payment — ids of coins with
`invoice_enabled` (signed `currencies.list`, cabinet → Coins → «For invoices»); omitted = every coin
you offer for invoices; none of them usable → `422 no_allowed_coins`; an empty list is refused by
the SDK before the request, so a filter that found nothing never turns into "every coin". Each
SDK README has a "Payer chooses the coin" section. 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(401)
    if event.type == "invoice.paid" and not seen(event.event_id):
        fulfil(event.data["invoice"])
    return "", 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, 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(401); exit;
}
if ($event['type'] === 'invoice.paid' && !seen($event['event_id'])) { fulfil($event['data']['invoice']); }
http_response_code(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(), $e->reason() }`.
Without Composer (a CMS plugin): copy `autoload.php` + `src/` and `require 'autoload.php'`. Shop
plugins create the payment with `$api->createInvoiceForOrder($orderId, Amount::format($total, 2), $currency)`
— one open invoice per order ([integration guides](https://cryptolike.net/integrations)). Runs on PHP 7.4 – 8.x; tests on 7.4 and 8.3.
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

[`vectors.json`](https://cryptolike.net/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/typescript` — `npm ci && npm test`; `sdk/python` — `pip install -e '.[test]' && pytest`;
`sdk/go` — `go test -race ./...`; `sdk/php` — `php 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 (API changelog); 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.
