Integration Guide

Everything you need to start accepting USDT payments on Ethereum and Tron. No KYC required.

Base URL: https://api.pay.kyc.rip — For testing use https://testnet.pay.kyc.rip

Getting Started

1

Register

Create a merchant account with a single API call. No email, no identity — just a key.

curl -X POST https://api.pay.kyc.rip/merchant/register

Save the merchant_key and webhook_secret from the response. They are shown only once.

2

Configure wallets

Set your ETH and/or Tron wallet addresses where you will receive payments.

curl -X PUT https://api.pay.kyc.rip/merchant/settings \
  -H "X-Merchant-Key: mk_your-key" \
  -H "Content-Type: application/json" \
  -d '{"eth_address":"0xYOUR_WALLET","tron_address":"TYOUR_WALLET"}'
3

Start accepting payments

Create invoices and redirect customers to the payment page. That's it.

Accept Payments

Create an invoice with POST /invoice/create. The chain field is optional — if omitted, the customer chooses on the payment page.

curl -X POST https://api.pay.kyc.rip/invoice/create \
  -H "X-Merchant-Key: mk_your-key" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": 25.00,
    "description": "Order #1234",
    "order_id": "ORD-1234",
    "expiry_minutes": 30
  }'
const res = await fetch('https://api.pay.kyc.rip/invoice/create', {
  method: 'POST',
  headers: {
    'X-Merchant-Key': 'mk_your-key',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    amount: 25.00,
    description: 'Order #1234',
    order_id: 'ORD-1234',
    expiry_minutes: 30,
  }),
});
const invoice = await res.json();
console.log(invoice.payment_url);
import requests

resp = requests.post(
    "https://api.pay.kyc.rip/invoice/create",
    headers={"X-Merchant-Key": "mk_your-key"},
    json={
        "amount": 25.00,
        "description": "Order #1234",
        "order_id": "ORD-1234",
        "expiry_minutes": 30,
    },
)
invoice = resp.json()
print(invoice["payment_url"])
$ch = curl_init('https://api.pay.kyc.rip/invoice/create');
curl_setopt_array($ch, [
    CURLOPT_POST           => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => [
        'X-Merchant-Key: mk_your-key',
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'amount'        => 25.00,
        'description'   => 'Order #1234',
        'order_id'      => 'ORD-1234',
        'expiry_minutes' => 30,
    ]),
]);
$invoice = json_decode(curl_exec($ch), true);
curl_close($ch);
echo $invoice['payment_url'];
package main

import (
    "bytes"
    "encoding/json"
    "fmt"
    "net/http"
)

func main() {
    body, _ := json.Marshal(map[string]any{
        "amount":         25.00,
        "description":    "Order #1234",
        "order_id":       "ORD-1234",
        "expiry_minutes": 30,
    })
    req, _ := http.NewRequest("POST",
        "https://api.pay.kyc.rip/invoice/create",
        bytes.NewReader(body))
    req.Header.Set("X-Merchant-Key", "mk_your-key")
    req.Header.Set("Content-Type", "application/json")

    resp, _ := http.DefaultClient.Do(req)
    var invoice map[string]any
    json.NewDecoder(resp.Body).Decode(&invoice)
    fmt.Println(invoice["payment_url"])
}

Response:

{
  "invoice_id": "d4e5f6...",
  "payment_url": "https://pay.kyc.rip/pay/d4e5f6...?network=mainnet",
  "adjusted_amount": "25.004817",
  "order_id": "ORD-1234",
  "status": "pending",
  "expires_at": 1711001800
}

The adjusted_amount includes micro-cents for unique identification on-chain. The payment_url is a hosted payment page your customer can use.

Payment Page

Option A: Redirect

Send your customer to the payment_url returned in the invoice response.

// After creating the invoice
window.location.href = invoice.payment_url;

Add a return_url query parameter to redirect the customer back to your site after payment:

https://pay.kyc.rip/pay/INVOICE_ID?network=mainnet&return_url=https://yoursite.com/thanks

Option B: Embed as iframe

<iframe
  src="https://pay.kyc.rip/pay/INVOICE_ID?network=mainnet"
  width="420"
  height="680"
  style="border:none;border-radius:12px"
></iframe>
The payment page handles chain selection, QR codes, real-time status updates, and expiry countdown automatically.

Webhooks

Webhooks notify your server in real time when payment events occur. Configure your webhook_url in the dashboard or via the settings API.

Payload format

Each webhook is a POST request with these headers:

HeaderDescription
X-Webhook-SignatureHMAC-SHA256 hex digest of the body
X-Webhook-EventEvent name (e.g. invoice.confirmed)
Content-Typeapplication/json

Events:

EventWhen
invoice.detectedPayment transaction seen on-chain
invoice.confirmingTransaction has some confirmations
invoice.confirmedFully confirmed — safe to fulfill order
invoice.expiredInvoice expired without payment
payout.completedPayout executed on-chain

Example payload:

{
  "event": "invoice.confirmed",
  "invoice_id": "d4e5f6...",
  "merchant_id": "a1b2c3...",
  "chain": "tron",
  "amount": 25.0,
  "adjusted_amount": "25.004817",
  "status": "confirmed",
  "tx_hash": "abc123...",
  "confirmations": 20,
  "required_confirmations": 20,
  "payer_address": "T...",
  "order_id": "ORD-1234",
  "timestamp": 1711001200
}

Verify webhook signature

Always verify the X-Webhook-Signature header using your webhook_secret. Compute HMAC-SHA256 of the raw request body and compare.

# Generate a signature locally to test
BODY='{"event":"invoice.confirmed",...}'
SECRET="your_webhook_secret"
SIG=$(echo -n $BODY | openssl dgst -sha256 -hmac $SECRET | cut -d' ' -f2)
echo $SIG
const crypto = require('crypto');

function verifyWebhook(rawBody, signature, secret) {
  const expected = crypto
    .createHmac('sha256', secret)
    .update(rawBody)
    .digest('hex');
  return crypto.timingSafeEqual(
    Buffer.from(expected),
    Buffer.from(signature)
  );
}

// Express example
app.post('/webhook', express.raw({type: 'application/json'}), (req, res) => {
  const sig = req.headers['x-webhook-signature'];
  if (!verifyWebhook(req.body, sig, WEBHOOK_SECRET)) {
    return res.status(401).send('Invalid signature');
  }
  const event = JSON.parse(req.body);
  // process event...
  res.sendStatus(200);
});
import hmac, hashlib

def verify_webhook(raw_body: bytes, signature: str, secret: str) -> bool:
    expected = hmac.new(
        secret.encode(), raw_body, hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(expected, signature)

# Flask example
@app.route("/webhook", methods=["POST"])
def webhook():
    sig = request.headers.get("X-Webhook-Signature", "")
    if not verify_webhook(request.data, sig, WEBHOOK_SECRET):
        return "Invalid signature", 401
    event = request.get_json()
    # process event...
    return "OK", 200
$rawBody   = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_WEBHOOK_SIGNATURE'] ?? '';
$secret    = 'your_webhook_secret';

$expected = hash_hmac('sha256', $rawBody, $secret);

if (!hash_equals($expected, $signature)) {
    http_response_code(401);
    die('Invalid signature');
}

$event = json_decode($rawBody, true);
// process $event...
http_response_code(200);
echo 'OK';
import (
    "crypto/hmac"
    "crypto/sha256"
    "encoding/hex"
    "io"
    "net/http"
)

func verifyWebhook(body []byte, sig, secret string) bool {
    mac := hmac.New(sha256.New, []byte(secret))
    mac.Write(body)
    expected := hex.EncodeToString(mac.Sum(nil))
    return hmac.Equal([]byte(expected), []byte(sig))
}

func webhookHandler(w http.ResponseWriter, r *http.Request) {
    body, _ := io.ReadAll(r.Body)
    sig := r.Header.Get("X-Webhook-Signature")
    if !verifyWebhook(body, sig, secret) {
        http.Error(w, "Invalid signature", 401)
        return
    }
    // process body...
    w.WriteHeader(200)
}
Important: Always verify the payment via the API after receiving a webhook. Webhooks can be replayed — the API is the source of truth.

Verify Payment

After receiving a webhook (or before fulfilling an order), verify the invoice status via the API using your order_id.

curl https://api.pay.kyc.rip/invoice/by-order/ORD-1234 \
  -H "X-Merchant-Key: mk_your-key"
const res = await fetch(
  'https://api.pay.kyc.rip/invoice/by-order/ORD-1234',
  { headers: { 'X-Merchant-Key': 'mk_your-key' } }
);
const invoice = await res.json();

if (invoice.status === 'confirmed') {
  // fulfill the order
}
resp = requests.get(
    "https://api.pay.kyc.rip/invoice/by-order/ORD-1234",
    headers={"X-Merchant-Key": "mk_your-key"},
)
invoice = resp.json()

if invoice["status"] == "confirmed":
    # fulfill the order
    pass
$ch = curl_init('https://api.pay.kyc.rip/invoice/by-order/ORD-1234');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => ['X-Merchant-Key: mk_your-key'],
]);
$invoice = json_decode(curl_exec($ch), true);
curl_close($ch);

if ($invoice['status'] === 'confirmed') {
    // fulfill the order
}
req, _ := http.NewRequest("GET",
    "https://api.pay.kyc.rip/invoice/by-order/ORD-1234", nil)
req.Header.Set("X-Merchant-Key", "mk_your-key")

resp, _ := http.DefaultClient.Do(req)
var invoice map[string]any
json.NewDecoder(resp.Body).Decode(&invoice)

if invoice["status"] == "confirmed" {
    // fulfill the order
}

Invoice statuses:

StatusMeaning
pendingWaiting for payment
detectedTransaction seen on-chain, not yet confirmed
confirmingHas some confirmations, waiting for threshold
confirmedFully confirmed — safe to deliver
expiredExpired without payment
failedPayment failed

Payouts (Optional)

Send USDT to one or more recipients. Payouts require a two-step challenge confirmation for security.

Vault Required: Payouts are signed locally using the PayGate Vault desktop app. The Vault holds your private keys and signs transactions — funds never pass through our servers. Download the Vault, connect it to your merchant account, and it will automatically receive and process payout tasks.

Single payout

curl -X POST https://api.pay.kyc.rip/payout/create \
  -H "X-Merchant-Key: mk_your-key" \
  -H "Content-Type: application/json" \
  -d '{
    "chain": "tron",
    "recipients": [
      {"address": "TXyz...","amount": 100.00, "memo": "Withdrawal #567"}
    ]
  }'
const res = await fetch('https://api.pay.kyc.rip/payout/create', {
  method: 'POST',
  headers: {
    'X-Merchant-Key': 'mk_your-key',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    chain: 'tron',
    recipients: [
      { address: 'TXyz...', amount: 100.00, memo: 'Withdrawal #567' },
    ],
  }),
});
const payout = await res.json();
console.log(payout.task_id);

Batch payout (mixed chains)

curl -X POST https://api.pay.kyc.rip/payout/batch \
  -H "X-Merchant-Key: mk_your-key" \
  -H "Content-Type: application/json" \
  -d '{
    "payouts": [
      {"chain":"eth",  "address":"0xABC...", "amount":200},
      {"chain":"tron", "address":"TXyz...", "amount":150}
    ]
  }'
const res = await fetch('https://api.pay.kyc.rip/payout/batch', {
  method: 'POST',
  headers: {
    'X-Merchant-Key': 'mk_your-key',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    payouts: [
      { chain: 'eth', address: '0xABC...', amount: 200 },
      { chain: 'tron', address: 'TXyz...', amount: 150 },
    ],
  }),
});

Best Practices

PracticeWhy
Always verify webhooks server-side Check X-Webhook-Signature with HMAC-SHA256 to prevent spoofing
Always verify via API before fulfilling Call GET /invoice/by-order/:orderId to confirm status is confirmed
Use order_id for idempotency Duplicate order_id returns the existing invoice instead of creating a new one
Use return_url for better UX Redirect customers back to your site after payment completes
Enable request signing in production Set require_signature: true to prevent unauthorized API usage
Configure IP whitelist Restrict API access to your server IPs via ip_whitelist in settings
Set up Telegram notifications Add your telegram_chat_id in settings for real-time alerts
Use testnet first Develop against testnet.pay.kyc.rip before going live

Testing on Testnet

Test the full payment flow without real money using our testnet environment.

Testnet API: https://testnet.pay.kyc.rip

Get Testnet Tokens

ChainWhatHow
Sepolia ETH Gas for transactions sepolia-faucet.pk910.de (PoW mining)
Sepolia USDT Test USDT (free mint) Call mint(1000000000) on our TestUSDT contract for 1,000 USDT
Nile TRX Energy for transactions nileex.io
Nile USDT Test TRC-20 USDT Enter your Tron address on nileex.io

Test Flow

  1. Register on the testnet merchant dashboard at pay.kyc.rip/access (switch to Testnet mode)
  2. Set your testnet wallet addresses in Settings
  3. Create a test invoice via API or dashboard
  4. Pay with testnet USDT — the system detects and confirms automatically
  5. Verify the webhook was delivered and the status is confirmed
  6. Switch to mainnet when ready — same code, different API URL
  7. Use @saggy_ai_bot on Telegram to track testnet orders. For mainnet use @rip_pay_bot

SDKs & Libraries

Official SDKs are coming soon. In the meantime, the API is a standard REST API — use any HTTP client in your language of choice.

LanguageStatus
Node.js / TypeScriptComing soon
PythonComing soon
PHPComing soon
GoComing soon

For the full API reference covering all endpoints, parameters, and response schemas, see the API Documentation.