Mist Network
Follow channel
Support
Developers · API v1

API documentation

REST + JSON. Create an invoice, send the customer to the payment page, receive the result via a signed webhook.

Getting started

The API key and secret are created in the merchant dashboard under API; the secret is shown only once. The webhook URL and webhook secret are managed on the same page. To open a merchant account, message @mistnetwork on Telegram.

Base URL: https://mistket.com/api/v1

Authentication

Send three headers with every request:

HeaderValue
X-Api-KeyKey from the dashboard (mp_live_…)
X-Api-TimestampUnix timestamp (seconds); may differ from server time by at most ±300 s
X-Api-Signaturehex( HMAC-SHA256( "{timestamp}.{raw body}", API_SECRET ) )

The signed string is the timestamp + a dot + the raw request body (send the JSON exactly as you signed it). For requests without a body (GET and body-less POST) the body is the empty string ""; query parameters are not part of the signature.

Replay protection: for state-changing requests (POST etc.) the same signature is not accepted twice within ±5 minutes. Take a fresh timestamp and recompute the signature for every request.

Signature version 2 (recommended): send X-Api-Signature-Version: 2 and a fresh X-Api-Nonce (8–64 chars [A-Za-z0-9_-]) on every request; the signature is hex( HMAC-SHA256( "{timestamp}.{nonce}.{METHOD}.{path?query}.{raw body}", API_SECRET ) ). A nonce cannot be reused within ±5 minutes. Version 1 keeps working.

If you set an IP allowlist in the dashboard, only requests from those addresses are accepted.

Example client

function mistket(string $method, string $path, array $body = []): array
{
    $key    = getenv('MISTKET_KEY');
    $secret = getenv('MISTKET_SECRET');

    $payload   = $body ? json_encode($body, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) : '';
    $timestamp = (string) time();
    $signature = hash_hmac('sha256', $timestamp.'.'.$payload, $secret);

    $ch = curl_init('https://mistket.com/api/v1'.$path);
    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_CUSTOMREQUEST  => $method,
        CURLOPT_POSTFIELDS     => $payload !== '' ? $payload : null,
        CURLOPT_HTTPHEADER     => [
            'Content-Type: application/json',
            'X-Api-Key: '.$key,
            'X-Api-Timestamp: '.$timestamp,
            'X-Api-Signature: '.$signature,
        ],
    ]);

    return json_decode(curl_exec($ch), true);
}

$invoice = mistket('POST', '/invoices', [
    'amount'       => '49.90',
    'currency'     => 'USD',
    'order_id'     => 'ORDER-1001',
    'callback_url' => 'https://example.com/webhook/mistket',
    'success_url'  => 'https://example.com/thanks',
]);

header('Location: '.$invoice['data']['checkout_url']);

Endpoints

POST /invoices Create an invoice
GET /invoices/{uuid} Get invoice status
GET /invoices List invoices
POST /invoices/{uuid}/cancel Cancel an open invoice
GET /assets Supported coins / networks
GET /rate Rate and amount to pay
GET /health Service health (no authentication)

POST/invoices — Create an invoice

FieldTypeRequiredDescription
amountstring✓Amount, e.g. "49.90" (greater than zero)
currencystring✓Price currency: USD, EUR, TRY, RUB, GBP, AED or a crypto symbol directly (e.g. USDT; 1:1 when it matches the paid asset)
order_idstringYour own order number. Idempotency key: calling again with the same order_id does not create a new invoice; the existing one is returned with "idempotent": true.
pay_currency
network
stringTo fix the network up front, e.g. USDT + TRC20. If omitted the customer picks the network on the payment page (recommended).
asset_idintAlternative: the id from /assets
callback_urlurlWebhook URL for this invoice (defaults to the store's webhook URL)
success_url
cancel_url
urlWhere to send the customer after paying / cancelling
ttl_minutesint5–1440 minutes; defaults to the store setting (60 by default)
description
customer_email
meta
Free-form fields (description ≤191 chars, meta object)

Success returns 201; redirect the customer to data.checkout_url.

{
  "success": true,
  "data": {
    "uuid": "9b1c…",
    "order_id": "ORDER-1001",
    "status": "new",
    "price_amount": "49.90",
    "price_currency": "USD",
    "pay_amount": null,
    "pay_currency": null,
    "network": null,
    "pay_address": null,
    "checkout_url": "https://mistket.com/pay/9b1c…",
    "expires_at": "2026-09-12T22:51:00+00:00",
    "seconds_remaining": 3600
  }
}

GET/invoices/{uuid} — Get invoice status

{
  "success": true,
  "data": {
    "uuid": "9b1c…",
    "status": "confirmed",
    "pay_amount": "49.912345",
    "paid_amount": "49.912345",
    "remaining_amount": "0",
    "pay_currency": "USDT",
    "network": "TRC20",
    "chain": "TRON",
    "pay_address": "T…",
    "confirmations": 21,
    "required_confirmations": 19,
    "is_underpaid": false,
    "can_pay_remaining": false,
    "under_review": false,
    "checkout_url": "https://mistket.com/pay/9b1c…",
    "transactions": [
      {
        "tx_hash": "f3a…",
        "amount": "49.912345",
        "confirmations": 21,
        "status": "confirmed",
        "explorer_url": "https://tronscan.org/#/transaction/f3a…"
      }
    ]
  }
}

GET/invoices — List invoices

Query parameters: status, order_id, per_page (max 100), page. The response includes meta.current_page, meta.last_page, meta.total.

POST/invoices/{uuid}/cancel — Cancel an open invoice

Closes an open invoice and returns its payment address to the pool; an invoice.cancelled webhook is sent.

GET/assets — Supported coins / networks

Each item: id, symbol, name, network, chain, contract, decimals, min_amount, confirmations, available. available: false → no free address on that network right now.

GET/rate?asset_id=1&currency=USD&amount=100

Shows the rate and crypto amount without creating an invoice. Parameters: asset_id (required), currency (required), amount.

GET/health — Service health (no authentication)

Invoice object

All invoice responses and the webhook data share the same shape. Key fields:

status
status_label
Status (see table below) and its label in the customer's language
*_amountpay_amount: crypto amount to pay · paid_amount / received_amount: total received at the address · remaining_amount: what is left
pay_currency
network
chain
pay_address
Payment asset, network, chain code and address (null until a network is chosen)
confirmations
required_confirmations
Current / required block confirmations
is_underpaid …is_underpaid, can_pay_remaining (customer may send the rest to the same address), under_review (expired with a partial payment; manual review), expiry_extensions
transactions[]Transactions seen on chain: tx_hash, amount, confirmations, status, explorer_url, detected_at

Invoice statuses

newCreated; the customer has not chosen a network yet
pendingNetwork chosen, address and amount locked; waiting for payment
underpaidLess than expected arrived; the rest can be sent to the same address (expiry extends automatically while open)
paidAmount seen, waiting for confirmations — do not fulfil the order yet
confirmedPaid and confirmed
overpaidPaid more than expected and confirmed
expiredExpired without a completed payment
cancelledCancelled

Webhooks

Every time an invoice status changes, a JSON POST is sent to the invoice's callback_url (or the store's webhook URL).

Headers

Content-Type:        application/json
User-Agent:          mistket-webhook/1.0
X-Mistket-Event:     invoice.confirmed
X-Mistket-Timestamp: 1789456123
X-Mistket-Signature: sha256=<hex>
X-Mistket-Delivery:  4412

Body

{
  "event": "invoice.confirmed",
  "sent_at": "2026-09-12T21:55:03+00:00",
  "data": { "uuid": "9b1c…", "order_id": "ORDER-1001", "status": "confirmed", "…": "…" }
}

Events

invoice.pending, invoice.underpaid, invoice.paid, invoice.confirmed, invoice.overpaid, invoice.expired, invoice.cancelled

Verifying the signature

The signature is HMAC-SHA256( "{X-Mistket-Timestamp}.{raw body}" ) with your webhook secret, sent with a sha256= prefix. Verify the raw body before decoding JSON, compare in constant time and reject stale timestamps.

$payload   = file_get_contents('php://input');
$timestamp = $_SERVER['HTTP_X_MISTKET_TIMESTAMP'] ?? '';
$signature = $_SERVER['HTTP_X_MISTKET_SIGNATURE'] ?? '';

$expected = 'sha256='.hash_hmac('sha256', $timestamp.'.'.$payload, WEBHOOK_SECRET);

if (! hash_equals($expected, $signature) || abs(time() - (int) $timestamp) > 300) {
    http_response_code(400);
    exit('invalid signature');
}

$event = json_decode($payload, true);

if (in_array($event['event'], ['invoice.confirmed', 'invoice.overpaid'], true)) {
    // fulfil the order: check data.order_id and data.paid_amount
}

http_response_code(200);
echo 'ok';

Fulfil the order only on invoice.confirmed or invoice.overpaid; invoice.paid means “transaction seen, waiting for confirmations”. The same event may arrive more than once; make your handler idempotent (use X-Mistket-Delivery and uuid).

Retries

Non-2xx responses (or a 15 s timeout) are retried with increasing delays: 10 s · 30 s · 2 min · 10 min · 30 min · 1 h · 2 h · 3 h — after that the delivery is marked failed. The delivery history of each invoice is visible in the dashboard.

Errors and limits

Errors return success: false with an error message. Validation errors also include per-field details in errors.

{ "success": false, "error": "…", "errors": null }
CodeMeaning
401Missing/invalid key, bad signature or timestamp outside the window
403Request IP is not allowed
404Invoice not found (another store's invoice also returns 404)
422Validation error, unsupported coin/network or no free address
429Rate limit: 120 requests / 1 min per API key
503Rate provider unreachable; retry in a few seconds

Ready to integrate?

Message us on Telegram to open a merchant account or ask questions.

Message on Telegram Merchant login
Mist NetworkMist Network