Integration Guide
Everything you need to start accepting USDT payments on Ethereum and Tron. No KYC required.
https://api.pay.kyc.rip —
For testing use https://testnet.pay.kyc.rip
Getting Started
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.
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"}'
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>
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:
| Header | Description |
|---|---|
X-Webhook-Signature | HMAC-SHA256 hex digest of the body |
X-Webhook-Event | Event name (e.g. invoice.confirmed) |
Content-Type | application/json |
Events:
| Event | When |
|---|---|
invoice.detected | Payment transaction seen on-chain |
invoice.confirming | Transaction has some confirmations |
invoice.confirmed | Fully confirmed — safe to fulfill order |
invoice.expired | Invoice expired without payment |
payout.completed | Payout 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)
}
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:
| Status | Meaning |
|---|---|
pending | Waiting for payment |
detected | Transaction seen on-chain, not yet confirmed |
confirming | Has some confirmations, waiting for threshold |
confirmed | Fully confirmed — safe to deliver |
expired | Expired without payment |
failed | Payment failed |
Payouts (Optional)
Send USDT to one or more recipients. Payouts require a two-step challenge confirmation for security.
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
| Practice | Why |
|---|---|
| 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.
https://testnet.pay.kyc.rip
Get Testnet Tokens
| Chain | What | How |
|---|---|---|
| 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
- Register on the testnet merchant dashboard at pay.kyc.rip/access (switch to Testnet mode)
- Set your testnet wallet addresses in Settings
- Create a test invoice via API or dashboard
- Pay with testnet USDT — the system detects and confirms automatically
- Verify the webhook was delivered and the status is confirmed
- Switch to mainnet when ready — same code, different API URL
- 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.
| Language | Status |
|---|---|
| Node.js / TypeScript | Coming soon |
| Python | Coming soon |
| PHP | Coming soon |
| Go | Coming soon |
For the full API reference covering all endpoints, parameters, and response schemas, see the API Documentation.