# CryptoLike developer guide

> How to accept crypto payments with CryptoLike: your server creates a payment with one signed REST call, the customer pays on a hosted page or on the same page embedded in your checkout, and a signed webhook reports the confirmed result. HTML version: https://cryptolike.net/docs

## Quickstart — about ten minutes to a confirmed payment

1. Create an API key in the cabinet under Developers → API keys with the permissions `create_invoice` and `read`. The secret is shown once and belongs in your server's environment or secret manager. A key prefixed `ck_test_` is created the same way in the cabinet of the test environment and works with test networks through its API URL; CryptoLike support gives you access to both. The production cabinet issues live keys only, and on mainnet a test key is refused with `401 auth.test_key`.
2. Create the payment from your server: `POST https://api.cryptolike.net/v1/invoices` with `amount` (a decimal string), `currency` (a fiat code such as USD, or a coin ticker such as BTC), your `order_id` and a `return_url`. Send your order id in the `Idempotency-Key` header: a retry returns the same payment for 24 hours instead of creating a second one.
3. Send the customer to the `checkout_url` from the response, or embed the same page in your checkout with the widget loader. For a fiat price without `coin_id` the page first lists the coins offered for invoices with approximate amounts (`allowed_coin_ids` narrows the list for one payment) and locks the rate when the payer picks one; then it shows the address, the QR code, the exact amount, the network warning and the live status.
4. Pay it on a test network with test coins; they have no value, and CryptoLike support can send you some.
5. Receive the result: verify the webhook signature over the raw body, skip an `event_id` you have already stored, answer 2xx within 10 seconds, then fulfil the order. Polling `GET /v1/invoices/{id}` every few seconds works too.

Official server-side SDKs cover all of this in TypeScript, Python, Go and PHP; any other language needs four signing lines. The SDK packages are not published to npm, PyPI, Packagist or the Go module proxy yet: until the public release they are installed from the repository checkout, as described on the SDK page.

## The API secret never goes to a browser

The API secret signs requests as the merchant: whoever reads it can create payments, read balances and change webhook endpoints. Your server holds it and creates the payment; your page receives only the invoice id. The official SDKs refuse to run in a browser for this reason, and the embedded widget reads only public payment data. Fulfil orders on the webhook, never on a browser event.

## Payment status model

- `new` — created, nothing received yet. Not terminal.
- `pending` — a transfer was detected, waiting for confirmations. Not terminal.
- `paid` — confirmed and credited to the merchant balance. Terminal. An overpayment is still `paid`, with `overpaid_amount` above zero.
- `underpaid` — less than the amount arrived; the same address stays open for a top-up until `ttl_expires_at`, and `remaining_amount` says how much is missing. Terminal once the TTL has passed.
- `expired` — the time limit passed with nothing confirmed. Terminal.
- `cancelled` — cancelled by the merchant, possible only from `new`. Terminal.

Terminal states are `paid`, `expired`, `cancelled` and `underpaid` after the TTL. Ship on `paid`; a chain reorganisation that rolls a confirmation back sends `invoice.reverted` and fulfilment should stop until a new `invoice.paid` arrives. Funds confirmed after a payment closed are still credited: the payment carries `late_payment: true` and the event is `invoice.late_payment`, while the status does not change. `payer_amount` is what the customer sends, `amount_crypto` is what the merchant receives; they differ only when the platform fee is paid by the payer.

## Webhooks

`X-Signature` is `"sha256="` plus the HMAC-SHA256 of `X-Timestamp` + `"."` + the raw request body, computed with the endpoint secret. Verify over the bytes as received — key order in the JSON is not guaranteed — compare in constant time, and reject a timestamp more than a few minutes off your clock in either direction.

Delivery is at least once: the same event can arrive again with the same `event_id`, the same body and a fresh signature. Store the id and acknowledge a repeat without touching the order twice. Answer any 2xx within 10 seconds; anything else is retried after 1 min, 5 min, 15 min, 1 h, 6 h and 24 h — seven attempts over about 31 hours — and then marked exhausted, after which it can be resent from the cabinet. Deliveries are independent, so a retried `invoice.pending` can arrive after `invoice.paid`: trust `created_at` and the status inside `data`, not the arrival order.

Event types include `invoice.created`, `invoice.pending`, `invoice.paid`, `invoice.underpaid`, `invoice.expired`, `invoice.cancelled`, `invoice.late_payment`, `invoice.reverted`, `deposit.pending`, `deposit.confirmed`, `deposit.reverted` and the `withdrawal.*` progress events.

## Errors

Every error is one envelope: `{"error":{"code","message","details"}}`. Branch on `code`, show `message` to a person, read `details` — `details.field` names the bad field.

- `400 validation_error` — a field is missing or malformed. Fix `details.field` and send again.
- `401 auth.invalid_signature` — the signature does not match the request. Sign the timestamp, the method, the path with its query and the body hash, in that order; check the clock (±5 minutes).
- `401 auth.replay` — this signature was already used. Timestamps are whole seconds, so a repeat must wait for the next second and be signed again; the SDKs do this.
- `401 auth.test_key` — a `ck_test_` key was sent where the assets run on mainnet. Use a live key there.
- `403 auth.permission_denied` — the key lacks the permission in `details.required`. Add it to the key in the cabinet.
- `404 not_found` — no such id, or it belongs to another account; both answer the same way on purpose.
- `409 conflict` — the current state does not allow it, such as cancelling a paid payment.
- `422 idempotency.mismatch` — the same `Idempotency-Key` arrived with a different request; use a new key.
- `429 rate_limited` — too many requests for this key (600 per minute by default). Wait `Retry-After` seconds.
- `503 unavailable` — a rate or a coin is temporarily unavailable. Retry with backoff, or price the payment in crypto.
- `504 timeout` — retry with the same `Idempotency-Key`; it cannot create a second payment.

A 4xx is never worth retrying unchanged. The SDKs retry 429 and 5xx (except 501) up to three times with jittered backoff.

## Sandbox and test networks

A `ck_test_` key signs exactly like a live key and works with test networks; test coins have no value and the flow is identical to production. Test keys come from the test environment: CryptoLike support gives you access to its cabinet and its API URL; set `CRYPTOLIKE_API_URL` to that URL. On mainnet a test key is refused with `401 auth.test_key`. `GET /v1/currencies` lists the assets on the test networks, their networks, decimals and required confirmations. Ask CryptoLike support for test coins and send the exact `payer_amount` to the address on the page, on the network named there — a transfer on the wrong network never reaches the payment.

## Go-live checklist

Live key with the smallest permissions that work; the secret in a secret store, not in the repository. Test keys and the test API URL removed from production configuration. An `Idempotency-Key` on every POST. A webhook endpoint on `https://` and a public host that answers 2xx within 10 seconds. Signature verified over the raw body, timestamp checked against an NTP-synced clock. `event_id` stored, repeats acknowledged without double work. Orders fulfilled on `invoice.paid` and stopped on `invoice.reverted`. A screen and a next action for `expired`, `cancelled` and final `underpaid`. Amounts kept as decimal strings end to end. For the widget: the production origin on the allowlist and the checkout served over https.

## Pages

- [Developer guide](https://cryptolike.net/docs): this document as a page, in English and Russian
- [API overview](https://cryptolike.net/docs/api): resources, status model, static addresses, withdrawals, errors, conventions
- [Authentication](https://cryptolike.net/docs/auth): API keys, permissions, request signing, replay protection
- [Webhooks](https://cryptolike.net/docs/webhooks): request format, signature with a test vector, retries, event types
- [SDKs](https://cryptolike.net/docs/sdk): TypeScript, Python, Go and PHP quickstarts
- [Checkout widget](https://cryptolike.net/docs/widget): embedding the payment page, allowed origins, postMessage events
- [API reference (Swagger UI)](https://api.cryptolike.net/docs): every route, field and error
- [OpenAPI document](https://api.cryptolike.net/docs/openapi.yaml)
