Skip to content

Docs

Host-to-host (H2H): show the crypto payment in your own interface

Show a CryptoLike payment on your own screen: address, exact amount, network, QR and timer from the invoice, coin choice via the API, orders confirmed by webhook.

Updated

Host-to-host (H2H) means your server creates the payment through the CryptoLike API and your own interface shows it: the payer never opens a CryptoLike page. Every invoice response carries what the screen needs — the address, the exact amount, the network, the QR payload and the deadline — and a signed webhook tells your server when the payment is confirmed. This page lists when to choose H2H, the flow, what the screen must show and how to handle the cases where the payer sends too little, too much or too late.

When to choose H2H, the hosted page or the widget

All three show the same invoice with the same statuses and webhooks; they differ in who draws the payment screen.

Way Who draws the screen Choose it when What you build
Hosted page CryptoLike, at checkout_url You want the shortest path: a redirect and a webhook A redirect from your backend
Checkout widget CryptoLike, in a frame on your page The customer should stay on your checkout, and the CryptoLike page may appear inside it Your origin on the allowlist, a script and a CSP rule
H2H You A mobile app, a kiosk, a messenger bot or a checkout that must look entirely like your product The payment screen with every element listed below

The hosted page and the widget already show the network warning, the timer, the live status and the texts for an underpayment or an expired payment in English, Russian and Spanish. With H2H that becomes your work, so start with the hosted page or the widget unless your interface needs to be your own. The widget is described in the checkout widget guide.

The flow

  1. Your server creates the invoice — POST /v1/invoices, signed with your API key, with an Idempotency-Key header. Pass coin_id to fix the coin now; omit it to let the payer choose in your interface.
  2. For an invoice without a coin, the payer chooses. Your server reads GET /v1/invoices/{id}/coin-options, your interface lists the coins, and your server fixes the choice with POST /v1/invoices/{id}/coin.
  3. Your interface shows the payment from the invoice response: network, address, payer_amount, QR, timer to ttl_expires_at, status.
  4. Your server tracks the status through the invoice.* webhooks and, for the confirmation counter, GET /v1/invoices/{id} — no more often than every few seconds. Your page asks your server, never the CryptoLike API.
  5. Your server confirms the order only on a verified invoice.paid webhook, then your interface shows the confirmation.

The API secret stays on your server. The browser or the app receives only what your server decides to show; it never signs requests. Signing is described in authentication.

Create the invoice

The request below fixes the coin at once (USDT on TRON). Without coin_id the response has the coin fields, address and the payment block set to null until the choice.

http
POST /v1/invoices HTTP/1.1
Host: api.cryptolike.net
Content-Type: application/json
Idempotency-Key: order-10042
X-API-Key: <key_id>
X-Timestamp: <unix seconds>
X-Signature: <HMAC-SHA256 of the request>

{"amount": "49.90", "currency": "USD", "coin_id": "6f1c2a3e-8d4b-4c5a-9e7f-0a1b2c3d4e5f", "order_id": "10042", "description": "Order #10042 — annual plan", "ttl_seconds": 1800, "fee_paid_by": "merchant"}

The response is the invoice (example values from the API reference):

json
{
  "id": "4e2bc88c-cb79-4afc-b864-c1ba8e9d50c9",
  "status": "new",
  "currency": "USD",
  "amount": "49.90",
  "coin_id": "6f1c2a3e-8d4b-4c5a-9e7f-0a1b2c3d4e5f",
  "ticker": "USDT",
  "network": "tron",
  "address": "TXYZopYRdj2D9XRtbG411XZZ3kM5VW9t7g",
  "amount_crypto": "49.90",
  "payer_amount": "49.90",
  "paid_amount": "0",
  "remaining_amount": "49.90",
  "overpaid_amount": "0",
  "amount_fiat": "49.90",
  "fiat_currency": "USD",
  "rate_locked": "1.0004",
  "rate_locked_at": "2026-09-16T12:00:00Z",
  "late_payment": false,
  "fee_paid_by": "merchant",
  "order_id": "10042",
  "description": "Order #10042 — annual plan",
  "return_url": null,
  "webhook_url": null,
  "metadata": null,
  "allowed_coin_ids": null,
  "checkout_url": "https://cryptolike.net/pay/4e2bc88c-cb79-4afc-b864-c1ba8e9d50c9",
  "ttl_expires_at": "2026-09-16T12:30:00Z",
  "expired_at": null,
  "created_at": "2026-09-16T12:00:00Z",
  "updated_at": "2026-09-16T12:00:00Z",
  "payment_uri": "tron:TXYZopYRdj2D9XRtbG411XZZ3kM5VW9t7g?amount=49.9&token=TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t",
  "qr_payload": "TXYZopYRdj2D9XRtbG411XZZ3kM5VW9t7g",
  "confirmations": null,
  "required_confirmations": 20
}

The fields your screen uses:

  • payer_amount and ticker — exactly what the payer must send. With fee_paid_by: payer the platform fee is already included, so payer_amount is larger than amount_crypto; show payer_amount, never amount or amount_crypto. Fees are on the pricing page.
  • network — btc, ltc, eth or tron. The same ticker can live on two networks (USDT on TRON and on Ethereum), so the network decides where the payer sends.
  • address — the deposit address of this invoice.
  • payment_uri — a link that opens a wallet with the address and the amount still owed: BIP-21 on Bitcoin and Litecoin, EIP-681 on Ethereum, a tron: link on TRON.
  • qr_payload — what to encode in the QR code: the bare address on every network, with no amount in it. Many payers send from an exchange, and an exchange withdrawal form accepts an address only. If you want a "QR with amount" for wallets, offer it as a switch on Bitcoin and Litecoin and encode payment_uri there.
  • ttl_expires_at — the deadline. ttl_seconds sets it, from 60 seconds to 30 days; without it the account setting applies (30 minutes by default). For a price in USD or another fiat currency the rate in rate_locked stays fixed until then.
  • confirmations and required_confirmations — the confirmation counter; confirmations is null while nothing has arrived.

Keep every amount as the string the API returns: do not round it and do not pass it through a floating-point number.

Let the payer choose the coin in your interface

For an invoice created without coin_id, the coin list and the choice go through two calls — the same list and the same operation as on the hosted page.

http
GET /v1/invoices/4e2bc88c-cb79-4afc-b864-c1ba8e9d50c9/coin-options
json
{
  "coin_options": [
    { "coin_id": "6f1c2a3e-8d4b-4c5a-9e7f-0a1b2c3d4e5f", "ticker": "USDT", "name": "Tether USD", "network": "tron", "network_name": "TRON",
      "decimals": 6, "confirmations": 20, "estimated_amount": "49.90", "rate_as_of": "2026-09-16T12:00:03Z",
      "estimate_reason": null, "estimate_message": null },
    { "coin_id": "a01d6e2b-3c4f-4a5b-8c7d-9e0f1a2b3c4d", "ticker": "BTC", "name": "Bitcoin", "network": "btc", "network_name": "Bitcoin",
      "decimals": 8, "confirmations": 2, "estimated_amount": null, "rate_as_of": null,
      "estimate_reason": "rate_stale",
      "estimate_message": { "key": "checkout.estimate.rate_stale", "default": "The exchange rate is updating. Choose this currency to see the exact amount." } }
  ]
}
  • Show network_name next to every ticker, and estimated_amount with a ≈ sign: it is an estimate. When it is null, show the text of estimate_message instead; the coin can still be chosen.
  • The list contains only the coins you offer for invoices that match the invoice currency, narrowed by allowed_coin_ids when the invoice has them; coins under maintenance are left out.
  • When the coin is already chosen, the call answers 409 with details.coin_id, and the invoice itself already has the address.

Fix the choice from your server, with an Idempotency-Key:

http
POST /v1/invoices/4e2bc88c-cb79-4afc-b864-c1ba8e9d50c9/coin HTTP/1.1
Content-Type: application/json
Idempotency-Key: order-10042-coin

{"coin_id": "6f1c2a3e-8d4b-4c5a-9e7f-0a1b2c3d4e5f"}

The answer is the invoice with address, payer_amount, payment_uri, qr_payload and ttl_expires_at — the same shape as above. The choice prices the payment again and locks the rate, so show payer_amount from this answer, not the estimate. Refusals: 400 with details.field: coin_id for a coin outside the options; 409 when a coin is already chosen (also the same one) or the invoice is no longer new; 503 when the rate or a new address is not available right now — wait a few seconds and send the same request again. A repeat with the same Idempotency-Key returns the first answer. The coin can be chosen once; if the payer has the hosted page open too, it switches to the payment at once.

What your payment screen must show

These elements are on the hosted page; your screen needs each of them.

Element Field Why
The network in words, with the standard for a token (TRON, TRC20) network, ticker The same address format and ticker exist on several networks
Wrong-network warning next to the address ticker, network "Send only USDT on the TRON network to this address. Funds sent on another network cannot be recovered."
The address, with a copy button address The payer copies it into a wallet; funds sent to a mistyped address cannot be recovered
The exact amount, with a copy button payer_amount, ticker A transfer below the amount becomes an underpayment
The QR code, with the amount printed next to it qr_payload The QR carries only the address on every network
An "open in wallet" link payment_uri One tap on a phone with a wallet installed
The time left ttl_expires_at After it the invoice expires, and a later transfer is a late payment
The status, with confirmations while it waits status, confirmations, required_confirmations "Payment detected, 3 of 20 confirmations" tells the payer not to send twice
The outcome screen status, paid_amount, remaining_amount Paid, underpaid with the remaining amount, expired or cancelled, each with the next step

Count the timer down against your server's clock rather than the payer's device. When it reaches zero, hide the address and the QR and offer to create a new payment. The statuses are new, pending, paid, underpaid, expired and cancelled; confirmations explains why a transfer waits before it counts.

Underpayment, overpayment and late payment

  • Underpayment. A confirmed transfer below payer_amount sets the status to underpaid and sends invoice.underpaid; a shortfall within the underpayment tolerance of your account still counts as paid. The invoice stays open for a top-up to the same address until ttl_expires_at: show remaining_amount and the updated payment_uri, which now asks for the remainder. After the deadline the underpayment is final — tell the payer to contact you. More: underpaid payment.
  • Overpayment. The status is paid with overpaid_amount above zero; invoice.paid carries it and is sent again when another transfer raises it. The whole sum is credited to your balance; settle the difference with the customer.
  • Late payment. A transfer confirmed after the invoice closed (expired, cancelled or a final underpaid) is still credited to your balance: the invoice gets late_payment: true, you receive invoice.late_payment, and the status does not change. This also happens when a transfer was seen but not yet confirmed at the deadline: the invoice expires, and the transfer arrives as a late payment. Decide whether to fulfil the order or refund. More: expired payment.
  • Reversal. If a network reorganisation removes a confirmed transfer, you receive invoice.reverted with the recomputed amounts. Stop fulfilment until a new invoice.paid arrives.

Confirm the order only on the webhook

Fulfil an order when your server receives invoice.paid with a valid webhook signature — not when the payer presses "I have paid", and not on a status your page has seen. Delivery is at least once: store event_id, answer 2xx within 10 seconds and skip repeats. Webhooks are the same for H2H, the hosted page and the widget, and they do not carry payment_uri, qr_payload or the confirmation counter — read GET /v1/invoices/{id} when you need them. Event types, retries and a test vector are in the webhooks reference; step by step: verify crypto payment webhooks.

Before you go live, run one payment through every case on a test network: paid, underpaid then topped up, expired, and a late transfer. The test environment is described in the developer guide.

FAQ

Do I need the widget for H2H?

No. With H2H your interface draws the screen from the invoice fields, and nothing from CryptoLike loads in the payer's browser. The widget is the other option: the CryptoLike payment page inside a frame on your checkout.

Can I still send the payer to the hosted page?

Yes. Every invoice has checkout_url, and the hosted page shows the same invoice. A coin chosen through the API switches an open page to the payment, so you can offer the page as a fallback, for example to a payer without a wallet on the same device.

Does H2H change the webhooks?

No. The events, the signature and the retries are the same as with the hosted page. The payment block of the invoice — payment_uri, qr_payload, confirmations and required_confirmations — is not in the webhook body; read the invoice with GET /v1/invoices/{id}.

Which amount does the payer send when the payer pays the fee?

payer_amount. With fee_paid_by: payer it already includes the platform fee, and amount_crypto is what you receive. Show payer_amount and keep it as the string the API returns.