USDT · BEP20 (BSC) + Binance Pay HMAC-SHA512 signed API v1 Updated 23 Oct 2026

MK Pay Merchant API

Integrate USDT payments into your website in minutes. Create invoices from your server, let your customers pay on the MK Pay checkout page, and receive a signed webhook the moment the payment is confirmed.

How it works

The API is intentionally tiny: one endpoint to create an invoice and one webhook to receive confirmations. No SDKs, no complex handshakes — plain HTTPS, JSON and HMAC signatures.

1
Create invoiceYour server calls the Create Invoice endpoint, signed with your API secret.
2
CheckoutThe customer is redirected to the MK Pay checkout page and pays the exact amount shown (BEP20 transfer, or Binance Pay with the 6-digit note).
3
DetectionMK Pay watches the BSC chain and Binance Pay 24/7 — payments are detected automatically, no manual confirmation.
4
ConfirmationThe invoice becomes PAID. The credited amount is what actually arrived after network fees.
5
WebhookMK Pay sends a signed JSON webhook to your notify URL — you credit the customer.

Key facts

ItemValue
CurrencyUSDT only (BEP20 / BSC and Binance Pay C2C)
Confirmation timeUsually within a few minutes of the customer paying
Request authenticationHMAC-SHA512 headers (section 03)
Webhook authenticationX-GW-Signature — HMAC-SHA512 of the raw body (section 06)
Rate limit60 requests per minute per merchant

02 · Getting Started

  1. Log in to your MK Pay merchant panel.
  2. Open Account & API — copy your api_key and api_secret.
  3. Open the Webhook page and save your site's webhook URL. It is used for any invoice that does not carry its own notify_url.
  4. Check Plugins & Modules first — ready-made modules exist for popular platforms. Use the raw API below only for custom integrations.
Keep your secret safeThe api_secret signs every request you send and every webhook you receive. Store it server-side only — never in browser JavaScript, mobile apps or public repositories. Requests can only be made while your merchant subscription is active; expired plans receive 403 subscription_required.

03 · Authentication

Every request must carry four HTTP headers:

HeaderValue
X-Merchant-KeyYour API key — identifies the merchant.
X-TimestampCurrent Unix time in milliseconds (e.g. 1792764428123).
X-NonceA random unique string per request — e.g. 16 hex characters.
X-SignatureHMAC-SHA512 signature, computed as shown below.

Signature recipe

Signature scheme
string_to_sign  =  X-Timestamp  +  "\n"  +  X-Nonce  +  "\n"  +  raw_request_body

X-Signature     =  lowercase_hex( HMAC_SHA512( key = api_secret,
                                               message = string_to_sign ) )

Rules that prevent bad_signature

PHP — computing the signature
// $body = the exact JSON string you are about to POST
$ts  = (string) round(microtime(true) * 1000);   // milliseconds
$nc  = bin2hex(random_bytes(8));                 // random nonce
$sig = hash_hmac('sha512', $ts . "\n" . $nc . "\n" . $body, $SECRET);

04 · Create Invoice

POST https://mkpay.win/api/v1/create.php

Creates a pending invoice and returns a checkout URL. Send the customer to redirect — everything else (amount display, method choice, payment detection) is handled by MK Pay.

Request body (JSON)

FieldTypeRequiredDescription
user_idstringYesUnique customer identifier on your site. Allowed characters: A-Z a-z 0-9 _ - @ . — anything else is stripped.
amountnumberYesInvoice amount in USDT, up to 2 decimals. Must be within the platform's allowed range, otherwise bad_amount.
gateway_idintegerNoForce a specific gateway (must be one of your active gateways). Omit to let MK Pay choose automatically.
return_urlstring (URL)NoWhere the customer is sent after completing the payment.
notify_urlstring (URL)NoPer-invoice webhook override. If omitted, the webhook URL saved on your panel's Webhook page is used.
referencestringNoYour own order/reference ID. Allowed: A-Z a-z 0-9 _ -. Returned unchanged in the webhook.
customer_namestringNoCustomer display name (max 100 chars).
customer_emailstringNoCustomer email (must be a valid email format).
Method selection — who picks the payment method? If gateway_id is provided, that gateway is used. If omitted and you have exactly one payable method (a BEP20 gateway, or Binance Pay with a QR configured), it is assigned automatically. If you have more than one, the invoice is created unassigned and the customer chooses the method on the checkout page — the choice then locks the invoice.

Minimal request

Request body
{
  "user_id": "1007",
  "amount": 10.00
}

Success response — HTTP 200

application/json
{
  "status": "OK",
  "transaction_id": "GP2610231145027F3A9C",
  "note": "483920",
  "redirect": "https://mkpay.win/checkout.php?inv=9f3c8a41d2e74b06a5c1f8e3d2b4a6c8"
}
FieldDescription
statusOK — the invoice was created.
transaction_idUnique invoice ID (GP + timestamp + random). Store it — it comes back in the webhook as transaction_id.
noteA 6-digit numeric note. Customers paying via Binance Pay must include it as the "Note to Payee"; it is the payment's identifier.
redirectThe checkout page URL. Redirect the customer there (HTTP 302 or a link/button).
BEP20 amount tagging — never assume the exact paid amount For BEP20 invoices, MK Pay may add a unique fraction of up to 0.0999 USDT to the amount so the on-chain transfer can be matched automatically. The customer always pays the exact amount displayed on the checkout page. Your integration must therefore never hardcode or assume amounts — always read the credited amount from the webhook (section 06).

05 · Errors

All responses are JSON. Authentication and transport failures use proper HTTP status codes; two validation errors return HTTP 200 — so always check the error / status field in the body.

ErrorHTTPMeaning
missing_headers401One or more of the four authentication headers are missing.
expired_timestamp401X-Timestamp differs from the MK Pay server clock by more than 5 seconds.
unknown_merchant401The API key is invalid or the merchant account is disabled.
bad_signature401The signature does not match — see the rules in section 03.
subscription_required403Your merchant plan has expired — renew it in the panel.
no_active_gateway422You have no active payment gateway. Add/activate one on the Gateways page.
rate_limited429More than 60 requests in 60 seconds — slow down and retry.
user_id_required200 *user_id is missing or empty after sanitisation.
bad_amount200 *amount is outside the platform's allowed range.

* Validation errors return HTTP 200 with an error field — inspect the response body, not just the status code.

Common causes of bad_signature Re-encoding the JSON after signing it · signing the pretty-printed body but sending a minified one · using the API key instead of the API secret · forgetting the two \n separators · millisecond vs second precision in X-Timestamp.

06 · Webhooks

The moment an invoice is confirmed, MK Pay POSTs a signed JSON payload to your webhook URL. This is how you know — with cryptographic certainty — that the payment really happened and was really confirmed by MK Pay.

Which URL receives the webhook?

  1. The notify_url sent when creating that specific invoice (per-invoice override) — used first if present.
  2. Otherwise, the webhook URL saved on your panel's Webhook page.

Delivery facts

ItemValue
MethodPOST, Content-Type: application/json
Signature headerX-GW-Signature: lowercase_hex(HMAC_SHA512(raw_body, api_secret))
Timeout10 seconds — reply fast, defer heavy work
AttemptsOne attempt per confirmation — keep your endpoint reachable, and contact support to re-send if you missed one
Expected answerHTTP 200 with the body OK

Payload fields

FieldTypeDescription
transaction_idstringThe invoice ID — the same one returned by Create Invoice. Use it for idempotency.
notestringThe 6-digit payment note.
referencestringYour reference, exactly as sent ("" if none).
customerstringThe user_id you sent — credit this user.
customer_namestringCustomer name, as sent.
customer_emailstringCustomer email, as sent.
amountnumberThe credited amount — what actually arrived after network fees. Credit this value.
invoice_amountnumberThe original invoice amount (informational only).
currencystringUSDT
statusstringPAID — only process payloads with this status.
tsnumberUnix timestamp of the notification.
Arrived-amount policy — read this before writing your receiver USDT network fees are deducted before the funds reach MK Pay, and MK Pay credits what actually arrived. That is why amount (credited) can be slightly lower than invoice_amount. Always credit amount. If your platform validates amount == invoice total, it will reject perfectly valid payments.

Verifying the signature (PHP)

Signature check — always first
// Compare in constant time; reject anything that does not match
$raw = file_get_contents('php://input');
$sig = $_SERVER['HTTP_X_GW_SIGNATURE'] ?? '';

if (!hash_equals(hash_hmac('sha512', $raw, $SECRET), $sig)) {
    http_response_code(403);
    echo 'invalid signature';
    exit;
}
Never credit before verifying Anyone can POST a forged JSON payload to your webhook URL. Without the X-GW-Signature check above, an attacker credits themselves for free. Verify first, process second, answer 200 OK last.

07 · Security Best Practices

08 · Full Integration Examples

Two small PHP files are all it takes. Adapt the TODO sections to your platform.

1 — Creating an invoice from your server

your-server.php — example
<?php
/* MK Pay — create an invoice (call this from your SERVER, never the browser) */
$API_URL = 'https://mkpay.win/api/v1/create.php';
$API_KEY = 'YOUR_API_KEY';
$SECRET  = 'YOUR_API_SECRET';

$payload = [
    'user_id'        => '1007',                      // customer ID on YOUR site
    'amount'         => 10.00,                       // USDT, max 2 decimals
    'reference'      => 'ORDER-8891',                // optional: your order id
    'return_url'     => 'https://mysite.com/thanks.php',
    'notify_url'     => 'https://mysite.com/mkpay-webhook.php',
    'customer_name'  => 'Mohamed Ahmed',
    'customer_email' => '[email protected]',
];
$body = json_encode($payload);                     // sign EXACTLY these bytes

$ts  = (string) round(microtime(true) * 1000);     // milliseconds
$nc  = bin2hex(random_bytes(8));                   // random nonce
$sig = hash_hmac('sha512', $ts . "\n" . $nc . "\n" . $body, $SECRET);

$ch = curl_init($API_URL);
curl_setopt_array($ch, [
    CURLOPT_POST           => true,
    CURLOPT_POSTFIELDS     => $body,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT        => 15,
    CURLOPT_HTTPHEADER     => [
        'Content-Type: application/json',
        'X-Merchant-Key: ' . $API_KEY,
        'X-Timestamp: '    . $ts,
        'X-Nonce: '        . $nc,
        'X-Signature: '    . $sig,
    ],
]);
$res = json_decode((string) curl_exec($ch), true);

if (($res['status'] ?? '') === 'OK') {
    header('Location: ' . $res['redirect']);       // send customer to checkout
    exit;
}
http_response_code(400);
exit('MK Pay error: ' . ($res['error'] ?? 'unknown'));

2 — Receiving the payment webhook

mkpay-webhook.php
<?php
/* MK Pay — webhook receiver. Verify first, process second, answer last. */
$SECRET = 'YOUR_API_SECRET';

$raw = file_get_contents('php://input');
$sig = $_SERVER['HTTP_X_GW_SIGNATURE'] ?? '';

/* 1) Verify authenticity (constant-time compare) */
if (!hash_equals(hash_hmac('sha512', $raw, $SECRET), $sig)) {
    http_response_code(403);
    echo 'invalid signature';
    exit;
}

/* 2) Parse the payload */
$d = json_decode($raw, true);

/* 3) Process only confirmed payments, exactly once (idempotency) */
if (($d['status'] ?? '') === 'PAID') {
    $txn  = $d['transaction_id'];      // e.g. GP2610231145027F3A9C — unique key
    $user = $d['customer'];            // the user_id you sent
    $amt  = (float) $d['amount'];      // CREDIT THIS (arrived amount)

    // TODO: skip if $txn was already processed
    // TODO: credit $amt to user $user on your platform
}

/* 4) Always answer 2xx so MK Pay marks the delivery as accepted */
http_response_code(200);
echo 'OK';
Wiring it up Upload mkpay-webhook.php to your site, then either send notify_url with every invoice, or save https://yoursite.com/mkpay-webhook.php once on the Webhook page of your MK Pay panel — every future invoice will use it automatically.

09 · FAQ

Which amount should I credit my customer?

Always amount — the amount that actually arrived after USDT network fees. invoice_amount is informational only. This is MK Pay's arrived-amount policy and it protects both sides from fee surprises.

The checkout amount looks slightly different from what I requested — why?

For BEP20 invoices MK Pay adds a unique fraction of up to 0.0999 USDT so the on-chain transfer can be matched automatically. The customer pays the exact amount shown on the checkout page, and the webhook tells you the final credited value.

How long until the webhook arrives?

Usually within a few minutes of the customer completing the transfer. BEP20 payments wait for the required network confirmations; Binance Pay transfers are detected via the 6-digit note. Detection runs continuously — no manual step.

My site was down and missed the webhook — what now?

Check the invoice status on the Transactions page of your MK Pay panel, then contact support with the transaction_id to have the notification re-sent. To stay safe in the future, design your receiver to answer 200 OK within 10 seconds and defer heavy work.

Why do I keep getting expired_timestamp?

Your server clock drifts more than 5 seconds from MK Pay's clock. Enable NTP time synchronisation on your server and generate the timestamp immediately before each request.

Can I call the API from the browser or a mobile app?

No. The api_secret would be exposed and anyone could create invoices in your name. Always call the API from your server; the browser only ever sees the redirect URL.

Does MK Pay retry failed webhook deliveries?

Currently one attempt is made per confirmation. Keep your endpoint online and fast, monitor your logs, and contact support with the transaction ID if a delivery was missed.

Need help with your integration? Contact MK Pay support through the channels provided with your merchant account — and always include the transaction_id.