TypeScript / Node.js
@cryptolike/sdk
Official server-side SDK for the CryptoLike merchant API (Node.js ≥ 18, TypeScript types included).
- 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 - Typed resources generated from docs/openapi.yaml" target="_blank" rel="noopener noreferrer">
docs/openapi.yaml verifyWebhook()— signature and timestamp check of webhook deliveries (WEBHOOKS)- No runtime dependencies
The secret stays on your server
Never load this SDK in a browser or a mobile app. The API secret signs requests as you: whoever can read your page's JavaScript could create payments, read balances and change your webhooks. The constructor throws
BrowserSecretErrorwhen it detects a browser. Your page needs only the invoice id /checkout_urlthat your server obtained — see WIDGET §1.
Keep the key id and secret in your environment or secret manager; the SDK reads
CRYPTOLIKE_API_KEY_ID and CRYPTOLIKE_API_SECRET when you do not pass them and never logs them.
The secret lives in a #private field, so it is redacted from console.log(client) /
util.inspect(client) (which ignore toJSON()) as well as from JSON.stringify(client).
Install
Pre-release: npm 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.
(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"
# after the release:
npm install @cryptolike/sdkQuickstart — to the first confirmed payment
import { CryptoLike, ApiError } from "@cryptolike/sdk";
const api = new CryptoLike(); // CRYPTOLIKE_API_KEY_ID + CRYPTOLIKE_API_SECRET from the environment
// 1. Create a payment on your server; send the customer to checkout_url.
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 — a retry never creates a second payment
);
redirect(invoice.checkout_url);
// 2. Learn the outcome from the webhook (below) — or poll:
const detail = await api.invoices.get(invoice.id);
if (detail.status === "paid") fulfil(detail.order_id);// 3. Webhook receiver (Express) — verify over the RAW body, dedupe by event_id, answer 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 (!alreadySeen(event.event_id)) handle(event); // event.type === "invoice.paid" → fulfil
res.sendStatus(200);
} catch (e) {
if (e instanceof WebhookVerificationError) return res.sendStatus(401);
throw e;
}
});Full examples: examples/create-payment.ts, examples/express-webhook.ts.
Resources
| Resource | Methods |
|---|---|
api.invoices |
create(body, idempotencyKey?), get(id), list(query), iterate(query), cancel(id), exportCsv(query) |
api.addresses |
create, get(id, {limit, cursor}), list, iterate, archive |
api.transactions |
list, iterate, get, exportCsv |
api.balances / api.currencies / api.rates |
list() |
api.withdrawals |
estimate(body), create(body), get, list, iterate, cancel |
api.webhooks.endpoints |
list, create, get, update, delete, rotateSecret |
api.webhooks.deliveries |
list, get, retry |
api.embed |
get(), set({ allowed_origins }) — checkout widget allowlist |
api.request(method, path, opts) |
any endpoint, signed |
Amounts are decimal strings ("10.50"), never floats. Lists return { items, next_cursor };
iterate() walks every page for you.
Errors
Every non-2xx response is an ApiError with status, code (stable machine code), message,
details, field, retryAfter and requestId. Transport failures after all retries are a
NetworkError. Codes: API-AUTH §5, API §5.
try { await api.invoices.create(body); }
catch (e) {
if (e instanceof ApiError && e.code === "validation_error") console.log(e.field, e.message);
else throw e;
}Retries and idempotency
429 (after Retry-After, or backoff), 5xx except 501, and transport errors are retried up to
maxRetries (default 3) with min(retryMaxMs, retryBaseMs · 2^attempt) full-jitter backoff. Every
POST carries an Idempotency-Key (yours, or a random 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. A 401 auth.replay — two identical requests signed within the
same second share a signature — waits for the next second and signs afresh.
Options
new CryptoLike({
keyId, secret, // default: CRYPTOLIKE_API_KEY_ID / CRYPTOLIKE_API_SECRET
baseUrl, // default: CRYPTOLIKE_API_URL or https://api.cryptolike.net
timeoutMs: 30_000, maxRetries: 3, retryBaseMs: 500, retryMaxMs: 8_000, retryAfterMaxMs: 30_000,
apiVersion: "v1", // X-API-Version pin
appInfo: "my-shop/2.3", // appended to User-Agent: cryptolike-sdk-typescript/<version>
fetch, // custom fetch (proxies, tests)
});Webhook verification
verifyWebhook(headers, rawBody, secret, { tolerance = 300, now }) returns the parsed event or
throws WebhookVerificationError (reason: missing_header | invalid_timestamp |
timestamp_out_of_tolerance | invalid_signature | invalid_body). headers may be Node's
req.headers, a Headers instance or a plain object; rawBody must be the bytes as received.
Answer 401/400 on failure, 2xx on success, and deduplicate by event_id — deliveries are
at least once and may arrive out of order.
Development
npm ci
npm run generate # docs/openapi.yaml → src/schema.d.ts (commit the result)
npm run typecheck
npm test # vectors of docs/sdk/vectors.json + client behaviour (node --test)
npm run build # dist/
npm run pack # pack/cryptolike-sdk-<version>.tgz (CI artifact; not published automatically)