PAYchat

Documentation

Payment system for partners and AI bots, with added monetization features

Part 1: Introduction & Authentication


🚀PAYchat Partner API

Build seamless USDT payments into your app with our simple REST API.


👋Welcome

PAYchat Partner API lets you:

  • Accept payments — payment links, QR codes, and escrow
  • Send money — instantly transfer USDT to any Telegram user
  • Track everything — real-time webhooks and detailed analytics

Base URL: https://paychat.info/api/partner


To receive callbacks (webhooks): Partner Dashboard → Webhooks tab → enter your HTTPS endpoint → click Test.

📦Official SDK

bash
npm install paychat-sdk
javascript
import PAYchatClient from 'paychat-sdk';

const client = new PAYchatClient('pk_your_api_key');

const payment = await client.createPayment({
  amount: 10,
  description: 'Limited Edition T-Shirt'
});

console.log(payment.payment.payment_url);

SDK v2.0.1 — https://www.npmjs.com/package/paychat-sdk


🔑Authentication

Your Credentials

KeyFormatPurpose
API Keypk_xxxx...Authenticate API requests
Secret Keysk_xxxx...Verify webhook signatures

The Secret Key is never sent in requests — it only verifies webhook signatures.

How to Authenticate

code
x-api-key: pk_your_api_key_here

This is not a Bearer token.

Example Request

bash
curl <https://paychat.info/api/partner/balance> \
  -H "x-api-key: pk_abc123..."

Access Conditions

ConditionResponse
Header missing or key unknown401 INVALID_API_KEY
Account disabled403 PARTNER_INACTIVE
Free plan, balance below minimum403 BALANCE_TOO_LOW
Free plan, over 1,000 calls this month429 MONTHLY_LIMIT_EXCEEDED

🔒Keep Your Keys Safe

  • Never expose keys in client-side code
  • Don't commit keys to version control
  • Use environment variables
  • Regenerate immediately if compromisedQ: What are the fees

Part 2: Plans & Pricing


💎Plans

PlanMonthly callsRate multiplier
Free1,000×1
Founding MemberUnlimited×5

💰Pricing

Fees are charged per API call, not as a percentage of transaction volume, and are deducted from your balance. A volume discount applies based on your balance plus the combined balance of users you referred.

Full pricing, discount tiers, and worked examples

For your live rate and current discount tier, call GET /api/partner/fee-info. For plan details, GET /api/partner/plans.



🎁Volume Discount (Based on Total Holdings)

Total Holdings = Your Balance + Referred Users' Balances

Total Holdings (USDT)DiscountTotal Holdings (USDT)Discount
0 – 1000%20,000 – 100,00060%
100 – 20010%100,000 – 500,00070%
200 – 50020%500,000 – 1,000,00080%
500 – 1,50030%1,000,000 – 2,000,00090%
1,500 – 5,00040%2,000,000 +100%
5,000 – 20,00050%

Live figures: GET /fee-info and GET /plans.

🔄How It Works

  1. Pay once → lifetime API access
  2. API fees deducted from your balance per call
  3. More holdings = higher discount = lower fees
  4. Refer users → their balances count toward your discount

Part 3: Rate Limits & Spend Limits


⚡Rate Limits

Limits are per endpoint. All GET endpoints share a single counter.

EndpointFreeFounding Member
POST /payments100/min500/min
POST /withdrawals50/min250/min
All GET requests300/min1,500/min

Response Headers

HeaderMeaning
X-RateLimit-LimitMax requests per window
X-RateLimit-RemainingRequests left
X-RateLimit-ResetSeconds until reset

When You Hit the Limit

json
{
  "success": false,
  "error": {
    "code": "RATE_LIMIT_EXCEEDED",
    "message": "Too many requests. Please retry later."
  }
}

Tip: check the Retry-After header to know when to retry.


🛡Spend Limits

Optional per-partner caps. A value of 0 means unlimited.

ControlError codeStatus
Per-transaction capTX_LIMIT_EXCEEDED403
Daily capDAILY_LIMIT_EXCEEDED403

The daily counter resets on the UTC date change. The amount is reserved at check time — if the withdrawal later fails, the reserved amount stays counted until the next reset.


🔁Idempotency

Pass idempotency_key in the request body — not as a header.

Supported on POST /payments, POST /withdrawals, POST /escrows, POST /qr-payments.

BehaviourDetail
Retention24 hours
ReplayReturns the original stored response
WithdrawalsKey is claimed before funds move. Concurrent retry gets REQUEST_IN_PROGRESS. If the transfer fails, the claim is released so you can retry.

Part 4: API Endpoints Overview


📡All Endpoints (37)

Payments

MethodEndpointDescription
POST/paymentsCreate a payment link
GET/payments/:link_idCheck payment status

QR Payments

MethodEndpointDescription
POST/qr-paymentsCreate a QR payment
GET/qr-paymentsList QR payments
GET/qr-payments/:idCheck QR status
DELETE/qr-payments/:idDeactivate a QR payment

Escrow

MethodEndpointDescription
POST/escrowsCreate an escrow
GET/escrowsList escrows
GET/escrows/:idCheck escrow status
POST/escrows/:id/releaseRelease funds to seller
POST/escrows/:id/cancelCancel and refund buyer

Withdrawals & Deposits

MethodEndpointDescription
POST/withdrawalsSend USDT to a user
POST/depositsCreate a deposit request
GET/depositsList deposits
GET/deposits/:idCheck deposit status
DELETE/deposits/:idCancel a pending deposit
GET/deposit-addressGet deposit address

Balance & Reporting

MethodEndpointDescription
GET/balanceCheck your balance
GET/fee-infoCurrent fee rate and discount tier
GET/transactionsList transactions
GET/dashboardDashboard summary
GET/statsDetailed statistics
GET/users/:telegram_idLook up a user

Webhooks

MethodEndpointDescription
PUT/webhookSet webhook URL
POST/webhook-testSend a test webhook
GET/webhooksList webhook delivery logs
POST/webhooks/:id/retryRetry a failed delivery

Account

MethodEndpointDescription
PUT/settingsUpdate name / webhook URL
POST/regenerate-keyRotate API and Secret keys
GET/transfer/:requestIdCheck a transfer

Public Endpoints (No API Key Required)

MethodEndpointDescription
GET/plansView available plans
GET/me?telegram_id=Check partner status
POST/registerRegister as a partner
POST/register-freeRegister on the Free plan
POST/upgradeUpgrade to Founding Member
POST/resetReset partner account
POST/transferSession-authenticated transfer

📏Minimum Amounts

These differ per resource — a common source of INVALID_AMOUNT.

ResourceMinimum
Payment link0.000001 USDT
Withdrawal0.000001 USDT
QR payment (fixed amount)0.01 USDT
Escrow1 USDT
Deposit1 USDT

Part 5: Create Payment


💵Create a Payment

Endpoint: POST /api/partner/payments

Request Body

FieldTypeRequiredDescription
amountnumberAmount in USDT (min: 0.000001)
descriptionstringWhat the payment is for
metadataobjectCustom data, stored and returned on lookup
idempotency_keystringPrevent duplicate payments (24h)

Example Request

bash
curl -X POST <https://paychat.info/api/partner/payments> \
  -H "x-api-key: pk_your_key" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": 29.99,
    "description": "Limited Edition T-Shirt",
    "metadata": {
      "user_id": "user_123",
      "item": "tshirt_black_L"
    }
  }'

Example Response

json
{
  "success": true,
  "payment": {
    "id": "x7k2m9",
    "link_id": "x7k2m9",
    "amount": 29.99,
    "currency": "USDT",
    "description": "Limited Edition T-Shirt",
    "status": "pending",
    "payment_url": "<https://t.me/paychat_aibot?start=x7k2m9>",
    "expires_at": "2026-08-14T12:00:00.000Z",
    "metadata": {
      "user_id": "user_123",
      "item": "tshirt_black_L"
    },
    "created_at": "2026-08-13T12:00:00.000Z"
  }
}
⚠️ id is the 6-character link_id. Use this value for GET /payments/:link_id. Earlier SDK versions returned a separate pay_xxx identifier that was not queryable — it has been removed.

⚠️ expires_at is informational. No background job closes the link and payment is not blocked after that time. Enforce a cutoff in your own code if you need one.

What to Do Next

  1. Redirect your customer to payment_url
  2. They complete payment in Telegram
  3. You receive a payment.completed webhook
  4. Funds are added to your balance instantly

🔍Check Payment Status

Endpoint: GET /api/partner/payments/:link_id

bash
curl <https://paychat.info/api/partner/payments/x7k2m9> \
  -H "x-api-key: pk_your_key"

Example Response (Completed)

json
{
  "success": true,
  "payment": {
    "id": "x7k2m9",
    "link_id": "x7k2m9",
    "amount": 29.99,
    "currency": "USDT",
    "description": "Limited Edition T-Shirt",
    "metadata": { "user_id": "user_123" },
    "status": "completed",
    "fee": 0.0004,
    "net_amount": 29.9896,
    "payer": { "telegram_id": "987654321" },
    "paid_at": "2026-08-13T12:30:00.000Z",
    "created_at": "2026-08-13T12:00:00.000Z"
  }
}

fee and net_amount are 0 and full amount respectively while status is pending.

Payment Status Values

StatusMeaning
pendingWaiting for payment
completedPayment received

Part 6: QR Payments


📱Create a QR Payment

Endpoint: POST /api/partner/qr-payments

Request Body

FieldTypeRequiredDescription
amountnumberconditionalRequired when is_fixed_amount is true. Min 0.01 USDT
descriptionstringPayment description
metadataobjectCustom data, stored and returned
is_fixed_amountbooleanDefault true
min_amountnumberUsed only when is_fixed_amount is false
max_amountnumberUsed only when is_fixed_amount is false
single_usebooleanDefault true
expires_hoursnumberHours, not seconds. Stored only — status is not auto-updated. Omit for no expiry
idempotency_keystringPrevent duplicates (24h)
⚠️ expires_hours is measured in hours. If omitted, the QR code does not expire.

Example Request

bash
curl -X POST <https://paychat.info/api/partner/qr-payments> \
  -H "x-api-key: pk_your_key" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": 15.00,
    "description": "Coffee Shop Order",
    "expires_hours": 1
  }'

Example Response

json
{
  "success": true,
  "qr_payment": {
    "id": "qr_a1b2c3d4e5f6",
    "amount": 15.00,
    "description": "Coffee Shop Order",
    "is_fixed_amount": true,
    "min_amount": null,
    "max_amount": null,
    "single_use": true,
    "status": "active",
    "payment_url": "<https://t.me/paychat_aibot?start=qr_a1b2c3d4e5f6>",
    "qr_image_url": "<https://api.qrserver.com/v1/create-qr-code/?size=300x300&data=>...",
    "metadata": null,
    "expires_at": "2026-08-13T13:00:00.000Z",
    "created_at": "2026-08-13T12:00:00.000Z"
  }
}

Variable Amount (Tip Jar)

bash
curl -X POST <https://paychat.info/api/partner/qr-payments> \
  -H "x-api-key: pk_your_key" \
  -H "Content-Type: application/json" \
  -d '{
    "is_fixed_amount": false,
    "min_amount": 1,
    "max_amount": 100,
    "single_use": false,
    "description": "Tip Jar"
  }'

min_amount greater than max_amount returns INVALID_RANGE.

QR Status Values

StatusMeaningSet by
activeAccepting paymentCreation, or rollback from processing
processingA payment is being executedPayment flow (concurrency lock)
paidPayment receivedPayment flow
inactiveDeactivatedDELETE /qr-payments/:id
⚠️ There is no expired status. expires_at is stored for your reference only — no background job transitions the record, and the payment flow does not check it. A QR code past its expiry still reports active and can still be paid. Compare expires_at yourself, or call DELETE /qr-payments/:id to close it.

processing is a short-lived lock. If the payment fails, the record returns to active.

DELETE only accepts QR codes currently in active status — otherwise QR_NOT_FOUND is returned.

json
{
  "success": true,
  "message": "QR payment deactivated",
  "id": "qr_a1b2c3d4e5f6"
}

Part 7: Escrow


🤝Create an Escrow

Hold buyer funds until the seller delivers.

Endpoint: POST /api/partner/escrows

Request Body

FieldTypeRequiredDescription
buyer_telegram_idstringBuyer's Telegram ID
seller_telegram_idstringSeller's Telegram ID
amountnumberMin 1 USDT
descriptionstringWhat the escrow is for
metadataobjectCustom data, stored and returned
expires_hoursnumberDefault 72
idempotency_keystringPrevent duplicates (24h)

💡Two Things to Know

BehaviourDetail
1% feefee = amount × 0.01, deducted from the seller payout
Seller auto-registrationIf seller_telegram_id has no PAYchat account, one is created automatically

Example Request

bash
curl -X POST <https://paychat.info/api/partner/escrows> \
  -H "x-api-key: pk_your_key" \
  -H "Content-Type: application/json" \
  -d '{
    "buyer_telegram_id": "123456789",
    "seller_telegram_id": "987654321",
    "amount": 100.00,
    "description": "Freelance Project",
    "expires_hours": 72
  }'

Example Response

json
{
  "success": true,
  "escrow": {
    "id": "esc_a1b2c3d4e5f6",
    "buyer_telegram_id": "123456789",
    "seller_telegram_id": "987654321",
    "amount": 100.00,
    "fee": 1.00,
    "net_amount": 99.00,
    "description": "Freelance Project",
    "metadata": null,
    "status": "pending",
    "payment_url": "<https://t.me/paychat_aibot?start=esc_a1b2c3d4e5f6>",
    "expires_at": "2026-08-16T12:00:00.000Z",
    "created_at": "2026-08-13T12:00:00.000Z"
  }
}
Response fields are flatbuyer_telegram_id and seller_telegram_id, not nested buyer / seller objects.

Escrow Lifecycle

StatusMeaning
pendingCreated, waiting for buyer payment
processingBuyer payment being processed
paidBuyer funded the escrow
releasingRelease in progress
completedFunds released to seller
cancellingCancellation in progress
cancelledCancelled, buyer refunded

Release / Cancel

bash
# Release funds to the seller
curl -X POST <https://paychat.info/api/partner/escrows/esc_a1b2c3d4e5f6/release> \
  -H "x-api-key: pk_your_key"

# Cancel and refund the buyer
curl -X POST <https://paychat.info/api/partner/escrows/esc_a1b2c3d4e5f6/cancel> \
  -H "x-api-key: pk_your_key" \
  -H "Content-Type: application/json" \
  -d '{"reason": "Buyer requested refund"}'

An escrow already being processed returns ESCROW_PROCESSING.


Part 8: Send Withdrawal


💸Send a Withdrawal

Endpoint: POST /api/partner/withdrawals

Request Body

FieldTypeRequiredDescription
telegram_idstringRecipient's Telegram ID
amountnumberAmount in USDT (min: 0.000001)
memostringMessage shown to recipient
idempotency_keystringPrevent duplicate withdrawals (24h)

Example Request

bash
curl -X POST <https://paychat.info/api/partner/withdrawals> \
  -H "x-api-key: pk_your_key" \
  -H "Content-Type: application/json" \
  -d '{
    "telegram_id": "123456789",
    "amount": 10.00,
    "memo": "Referral bonus - Thanks!",
    "idempotency_key": "wd_order_5521"
  }'

Example Response

json
{
  "success": true,
  "withdrawal": {
    "id": "wd_xyz789abc",
    "telegram_id": "123456789",
    "amount": 10.00,
    "status": "completed",
    "memo": "Referral bonus - Thanks!",
    "created_at": "2026-08-13T12:00:00.000Z"
  },
  "referral_added": true
}

referral_added is true only on the recipient's first transaction with you.

Good to Know

FeatureHow It Works
Instant deliveryFunds arrive immediately
Auto-registrationNew users get a PAYchat account automatically
NotificationsRecipients get a Telegram message
Referral registrationFirst withdrawal registers the user as your referral
Self-transfer blockedReturns CANNOT_WITHDRAW_TO_SELF

Part 9: Deposits


🏦Fund Your Partner Account

Endpoint: POST /api/partner/deposits

Request Body

FieldTypeRequiredDescription
amountnumberMin 1 USDT
networkstringtron or ton. Default tron

Any other network value returns INVALID_NETWORK. If you already have an open request, you get ACTIVE_REQUEST_EXISTS.

Example Request

bash
curl -X POST <https://paychat.info/api/partner/deposits> \
  -H "x-api-key: pk_your_key" \
  -H "Content-Type: application/json" \
  -d '{"amount": 50, "network": "tron"}'

Deposit Status Values

StatusMeaning
pendingWaiting for funds
completedCredited to your balance
expiredRequest expired
cancelledCancelled via DELETE
Deposits do not emit webhooks. Poll GET /deposits/:id instead.

🔑Get Deposit Address

Endpoint: GET /api/partner/deposit-address

Example Response

json
{
  "success": true,
  "deposit": {
    "address": "TXxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
    "network": "TRON (TRC20)",
    "currency": "USDT",
    "memo": "partner_abc123",
    "note": "Include the memo when sending to identify your deposit"
  }
}
This endpoint returns the TRON (TRC-20) address only. To deposit over TON, create a deposit request with POST /deposits and "network": "ton".

Important: always include the memo when sending funds so we can credit your account.


Part 10: Balance, Transactions & Stats


💰Check Your Balance

Endpoint: GET /api/partner/balance

json
{
  "success": true,
  "balance": {
    "available": 1250.50,
    "currency": "USDT",
    "monthly_volume": 48200.00,
    "fee_rate": 0.01
  }
}
FieldDescription
availableUSDT you can withdraw or send
monthly_volumeTotal volume processed this month
fee_rateYour configured overage fee rate
Total holdings and referral balances are not returned here. Use GET /fee-info for your discount tier and referral totals.

📊Fee Info

Endpoint: GET /api/partner/fee-info

Returns your current discount tier, effective per-call fee, total holdings, and how much more balance is needed to reach the next tier.


📋List Transactions

Endpoint: GET /api/partner/transactions

Query Parameters

ParameterTypeDefaultDescription
typestringallpayment, withdraw, escrow, or qr
statusstringallFilter by status
fromstring-Start date (ISO 8601)
tostring-End date (ISO 8601)
limitnumber20Results per page (max 100)
offsetnumber0Pagination offset

Example Response

json
{
  "success": true,
  "transactions": [
    {
      "id": 1,
      "partner_id": "ptn_abc123",
      "type": "payment",
      "telegram_id": "123456789",
      "amount": 29.99,
      "fee": 0.0004,
      "net_amount": 29.9896,
      "status": "completed",
      "created_at": "2026-08-13T12:00:00.000Z"
    }
  ],
  "pagination": {
    "total": 156,
    "limit": 10,
    "offset": 0
  }
}

📈Dashboard & Stats

EndpointReturns
GET /dashboardPartner info, today/week totals, webhook delivery counts, recent transactions
GET /stats?period=30dDaily breakdown and totals. period accepts 7d, 30d, 90d
GET /escrows, GET /qr-payments, and GET /deposits return a subset of fields and no pagination object. Use the detail endpoint for the full record.

Part 11: Webhooks


🔔Webhooks

Available Events (8)

EventDescriptionWhen It Fires
payment.completedPayment receivedCustomer completes payment link
withdraw.completedWithdrawal sentFunds delivered to user
qr.paidQR payment receivedCustomer pays via QR code
escrow.createdEscrow createdNew escrow order created
escrow.paidEscrow fundedBuyer completes escrow payment
escrow.releasedEscrow releasedFunds released to seller
escrow.cancelledEscrow cancelledCancelled (refunded if paid)
testTest eventYou send a test webhook
There are no qr.created, qr.expired, or deposit events. Poll the relevant GET endpoint instead.

Setting Up Webhooks

1. Set your webhook URL

bash
curl -X PUT <https://paychat.info/api/partner/webhook> \
  -H "x-api-key: pk_your_key" \
  -H "Content-Type: application/json" \
  -d '{"webhook_url": "<https://your-server.com/webhooks/paychat>"}'

2. Test it

bash
curl -X POST <https://paychat.info/api/partner/webhook-test> \
  -H "x-api-key: pk_your_key"

⚠️Webhook URL Requirements

The URL must be a public HTTPS endpoint. The following are rejected with INVALID_WEBHOOK_URL:

  • Any http:// URL
  • localhost or any .local hostname
  • Private ranges: 10.x, 172.16–31.x, 192.168.x
  • Loopback: 127.x, ::1
  • Link-local: 169.254.x (including cloud metadata endpoints)
  • CGNAT: 100.64–127.x
  • Multicast and reserved ranges
  • Bare hostnames with no dot, and integer or hex-encoded IP forms

Webhook Headers

HeaderDescription
Content-Typeapplication/json
X-PAYchat-EventEvent type
X-PAYchat-SignatureHMAC signature for verification
X-PAYchat-Delivery-IDUnique delivery ID

Webhook Payload: payment.completed

json
{
  "event": "payment.completed",
  "data": {
    "payment_id": "x7k2m9",
    "status": "completed",
    "transaction_id": "x7k2m9",
    "amount": 29.99,
    "fee": 0.0004,
    "net_amount": 29.9896,
    "metadata": {
      "user_id": "user_123",
      "item": "tshirt_black_L"
    },
    "payer": { "telegram_id": "987654321" }
  },
  "created_at": "2026-08-13T12:00:00.000Z"
}
Whatever you attached as metadata on creation is echoed back here — no follow-up GET needed. This applies to payment.completed, qr.paid, escrow.created, escrow.paid, escrow.released, and escrow.cancelled. withdraw.completed carries memo instead.

Is my metadata included in webhooks

Webhook Payload: withdraw.completed

json
{
  "event": "withdraw.completed",
  "data": {
    "withdrawal_id": "wd_xyz789",
    "telegram_id": "123456789",
    "amount": 10.00,
    "memo": "Referral bonus"
  },
  "created_at": "2026-08-13T12:00:00.000Z"
}

Part 12: Webhook Security & Retries


🔐Verifying Webhooks

How Signatures Work

  1. We build the message {timestamp}.{JSON.stringify(payload)}
  2. We sign it with your Secret Key using HMAC-SHA256
  3. We send the result in X-PAYchat-Signature

Signature Format

code
X-PAYchat-Signature: t=1704628800,v1=abc123def456...
PartDescription
tUnix timestamp (seconds)
v1HMAC-SHA256 signature (hex)

Reject requests whose timestamp is older than 5 minutes.

Verification Code (Node.js)

javascript
const crypto = require('crypto');

function verifyWebhook(payload, signatureHeader, secretKey) {
  const parts = signatureHeader.split(',');
  const timestamp = parts.find(p => p.startsWith('t=')).split('=')[1];
  const signature = parts.find(p => p.startsWith('v1=')).split('=')[1];

  // Reject stale timestamps
  const now = Math.floor(Date.now() / 1000);
  if (Math.abs(now - parseInt(timestamp)) > 300) return false;

  const message = `${timestamp}.${JSON.stringify(payload)}`;
  const expected = crypto
    .createHmac('sha256', secretKey)
    .update(message)
    .digest('hex');

  return signature === expected;
}

app.post('/webhooks/paychat', (req, res) => {
  const isValid = verifyWebhook(
    req.body,
    req.headers['x-paychat-signature'],
    process.env.PAYCHAT_SECRET_KEY
  );

  if (!isValid) return res.status(401).send('Invalid signature');

  res.status(200).send('OK');
});

Or use the SDK:

javascript
import { PAYchatClient } from 'paychat-sdk';

// Node.js
PAYchatClient.verifyWebhook(payload, signature, 'sk_your_secret_key');

// Browser / Workers
await PAYchatClient.verifyWebhookAsync(payload, signature, 'sk_your_secret_key');

🔄Webhook Retries

If your server doesn't respond with a 2xx status, we retry automatically.

Retry Schedule

AttemptWait Time
1stImmediate
2nd1 minute
3rd5 minutes
4th30 minutes
5th2 hours
FinalMarked as failed

Retries run on a background scheduler. A delivery is abandoned after 5 total attempts and marked failed.

Failed deliveries can be replayed manually with POST /webhooks/:id/retry.

Requirements for Success

RequirementDetails
Status codeAny 2xx (200–299)
TimeoutRespond within 10 seconds
Response bodyNot required

Part 13: Error Handling


❌Error Handling

Error Response Format

json
{
  "success": false,
  "error": {
    "code": "ERROR_CODE",
    "message": "Human readable description"
  }
}

Some errors add context fields — for example DAILY_LIMIT_EXCEEDED also returns daily_limit, daily_used, and amount.

HTTP Status Codes

CodeMeaning
200Success
400Bad request (check your input)
401Authentication failed
403Forbidden (inactive, or limit exceeded)
404Resource not found
429Rate or monthly limit exceeded
500Server error (retry later)

Authentication & Access

CodeMeaning
INVALID_API_KEYMissing or invalid API key
PARTNER_INACTIVEPartner account is inactive
BALANCE_TOO_LOWBalance below the minimum for API access
UNAUTHORIZEDNot authorised for this resource
FORBIDDENAction not permitted
NOT_PARTNERCaller is not a partner
ALREADY_PARTNERAlready registered
ALREADY_UPGRADEDPlan already upgraded
ADMIN_NOT_FOUNDAdmin account not found

Limits

CodeMeaning
RATE_LIMIT_EXCEEDEDToo many requests
MONTHLY_LIMIT_EXCEEDEDFree plan monthly call limit reached
TX_LIMIT_EXCEEDEDPer-transaction cap exceeded
DAILY_LIMIT_EXCEEDEDDaily cap exceeded

Validation

CodeMeaning
INVALID_AMOUNTAmount below minimum or missing
INVALID_PARAMSRequired parameters missing
INVALID_RANGEmin_amount greater than max_amount
INVALID_NETWORKNetwork must be tron or ton
INVALID_URLMalformed URL
INVALID_WEBHOOK_URLWebhook URL must be a public HTTPS endpoint
SAME_USERBuyer and seller are identical
CANNOT_WITHDRAW_TO_SELFCannot withdraw to yourself
NO_UPDATESNo fields to update

Resource State

CodeMeaning
PAYMENT_NOT_FOUNDPayment not found
QR_NOT_FOUNDQR payment not found
ESCROW_NOT_FOUNDEscrow not found
DEPOSIT_NOT_FOUNDDeposit not found
WEBHOOK_NOT_FOUNDWebhook log not found
USER_NOT_FOUNDUser not found
BUYER_NOT_FOUNDBuyer not found
SELLER_NOT_FOUNDSeller not found
NO_WEBHOOK_URLWebhook URL not configured
ACTIVE_REQUEST_EXISTSAn active deposit request already exists
ESCROW_PROCESSINGEscrow is currently being processed

Execution

CodeMeaning
INSUFFICIENT_BALANCENot enough balance
REQUEST_IN_PROGRESSDuplicate idempotency key still processing
RELEASE_FAILEDEscrow release failed
CANCEL_FAILEDEscrow cancellation failed
INTERNAL_ERRORUnexpected server error

Client-Side (SDK only)

CodeMeaning
TIMEOUTRequest exceeded the SDK timeout. Never returned by the server

Part 14: Code Examples


bash
npm install paychat-sdk
javascript
import PAYchatClient from 'paychat-sdk';

const client = new PAYchatClient('pk_your_api_key');

// Payment link
const payment = await client.createPayment({
  amount: 29.99,
  description: 'Limited Edition Hoodie',
  metadata: { user_id: '123' }
});

// QR payment — expiresHours is in HOURS
const qr = await client.createQRPayment({
  amount: 15.00,
  description: 'Coffee Shop Order',
  expiresHours: 1
});
console.log(qr.qr_payment.qr_image_url);

// Escrow — minimum 1 USDT, 1% fee
const escrow = await client.createEscrow({
  amount: 100,
  buyerTelegramId: '123456789',
  sellerTelegramId: '987654321',
  description: 'Freelance Project'
});
await client.releaseEscrow(escrow.escrow.id);

// Withdrawal
const withdrawal = await client.createWithdrawal({
  telegramId: '123456789',
  amount: 5.00,
  memo: 'Referral bonus'
});

// Balance
const balance = await client.getBalance();

// Webhook verification
const isValid = PAYchatClient.verifyWebhook(payload, signature, 'sk_your_secret_key');

💻Raw HTTP (any language)

Node.js Setup

javascript
const axios = require('axios');

const paychat = axios.create({
  baseURL: '<https://paychat.info/api/partner>',
  headers: {
    'x-api-key': process.env.PAYCHAT_API_KEY,
    'Content-Type': 'application/json'
  }
});

Create a Payment

javascript
async function createPayment(amount, description, metadata = {}) {
  try {
    const { data } = await paychat.post('/payments', {
      amount,
      description,
      metadata,
      idempotency_key: `pay_${Date.now()}_${Math.random()}`
    });

    console.log('Payment URL:', data.payment.payment_url);
    console.log('Lookup ID:', data.payment.id); // 6-char link_id
    return data.payment;

  } catch (error) {
    console.error('Error:', error.response?.data?.error?.message);
    throw error;
  }
}

Send Money to a User

javascript
async function sendReward(telegramId, amount, memo) {
  try {
    const { data } = await paychat.post('/withdrawals', {
      telegram_id: telegramId,
      amount,
      memo,
      idempotency_key: `wd_${telegramId}_${Date.now()}`
    });
    return data.withdrawal;

  } catch (error) {
    const code = error.response?.data?.error?.code;
    if (code === 'INSUFFICIENT_BALANCE') {
      console.error('Not enough balance! Please deposit more funds.');
    }
    if (code === 'DAILY_LIMIT_EXCEEDED') {
      console.error('Daily spend cap reached.');
    }
    throw error;
  }
}

Handle Webhooks (Express)

javascript
const express = require('express');
const crypto = require('crypto');

const app = express();
app.use(express.json());

app.post('/webhooks/paychat', (req, res) => {
  const signature = req.headers['x-paychat-signature'];
  if (!verifySignature(req.body, signature)) {
    return res.status(401).send('Invalid signature');
  }

  switch (req.headers['x-paychat-event']) {
    case 'payment.completed': handlePayment(req.body.data); break;
    case 'withdraw.completed': handleWithdrawal(req.body.data); break;
    case 'qr.paid': handleQR(req.body.data); break;
    case 'escrow.paid': handleEscrowPaid(req.body.data); break;
    case 'escrow.released': handleEscrowReleased(req.body.data); break;
  }

  res.status(200).send('OK');
});

function verifySignature(payload, header) {
  const parts = header.split(',');
  const timestamp = parts.find(p => p.startsWith('t=')).split('=')[1];
  const signature = parts.find(p => p.startsWith('v1=')).split('=')[1];

  const now = Math.floor(Date.now() / 1000);
  if (Math.abs(now - parseInt(timestamp)) > 300) return false;

  const message = `${timestamp}.${JSON.stringify(payload)}`;
  const expected = crypto
    .createHmac('sha256', process.env.PAYCHAT_SECRET_KEY)
    .update(message)
    .digest('hex');

  return signature === expected;
}

app.listen(3000);

Part 15: FAQ


❓Frequently Asked Questions

General

Q: What if my balance is too low?

A: Auto-charge triggers if enabled. Otherwise the API returns BALANCE_TOO_LOW (Free plan) or INSUFFICIENT_BALANCE.


Q: How do referred users affect my discount?

A: The first withdrawal to a user registers them as your referral. Their balance counts toward your total holdings, increasing your discount rate.


Q: What if my API key is exposed?

A: Regenerate it immediately:

bash
curl -X POST <https://paychat.info/api/partner/regenerate-key> \
  -H "x-api-key: pk_your_current_key"

Your old key becomes invalid instantly. The Secret Key is rotated at the same time — update your webhook verification code too.


Payments

Q: Which ID do I use to check a payment?

A: The payment.id from the create response — a 6-character link_id. It is also returned as payment.link_id.


Q: Is metadata sent in webhooks?

A: No. Webhook payloads carry IDs and amounts only. Fetch metadata with GET /payments/:link_id, GET /escrows/:id, or GET /qr-payments/:id.


Q: What currencies are supported?

A: USDT. Deposits accept the TRON (TRC-20) and TON networks.


Q: When do I receive funds?

A: Instantly — as soon as the customer completes payment.


QR Payments

Q: Why doesn't my QR code expire?

A: expires_hours is measured in hours, and if omitted the QR never expires. Even when set, expires_at is informational — the status is not auto-updated and the payment flow does not block on it. Enforce expiry in your own code, or call DELETE /qr-payments/:id.


Q: What's the minimum QR amount?

A: 0.01 USDT for fixed-amount QR codes — higher than the 0.000001 minimum for payment links.


Escrow

Q: What does escrow cost?

A: 1% of the amount, deducted from the seller payout. A 100 USDT escrow pays the seller 99 USDT.


Q: What if the seller has no PAYchat account?

A: An account is created automatically when the escrow is made.


Withdrawals

Q: What if the recipient doesn't have PAYchat?

A: We create an account automatically. They receive a Telegram message with instructions.


Q: Is there a minimum withdrawal?

A: 0.000001 USDT.


Webhooks

Q: What if my webhook endpoint is down?

A: We retry up to 5 times over roughly 2.5 hours. You can also replay manually with POST /webhooks/:id/retry.


Q: Why was my webhook URL rejected?

A: It must be a public HTTPS endpoint. See the Webhook URL Requirements table — private, loopback, and link-local addresses are blocked.


Security

Q: What's idempotency_key for?

A: It prevents duplicate transactions if the same request is sent twice. Send it in the request body, not as a header. Keys are retained for 24 hours.


Q: How do I verify webhooks are really from PAYchat?

A: Check X-PAYchat-Signature with your Secret Key, and reject timestamps older than 5 minutes.


📞Need Help?

ChannelContact
Telegram@paychat_mafia
Email[email protected]

📝Changelog

DateVersionChanges
2025-01-071.0.0Initial release
2025-01-102.0.0Holdings-based discount system
2026-08-132.0.1Base URL moved to paychat.info. Documented QR, escrow, and deposit endpoints (37 total). Payment id is now the queryable link_id. metadata persisted for payments, QR, and escrow. Idempotency added to QR and escrow. Free plan, per-endpoint rate limits, spend limits, and the full 38-code error reference published.

PAYchat Partner API — Documentation

Payment system for partners and AI agents. Base URL: https://paychat.info/api/partner

Quick start (no signup, works for AI agents)

  1. POST <https://paychat.info/api/partner/sandbox/start> — no auth. Returns JSON with a claim_url. Limit: 3 issues per IP per day.
  2. GET the claim_url — returns a sandbox api_key (prefix pk_test_, valid 30 days, 1,000 calls/month) plus ready-to-run curl examples.
  3. Call any endpoint with header X-API-Key: <api_key>.

Sandbox keys run in test mode: no real funds move, and payments are completed via the simulator below. Live keys (pk_) require a PAYchat account — register as a partner in the Telegram Mini App.

Authentication

Every request needs the header X-API-Key. Keys starting with pk_test_ are test/sandbox; keys starting with pk_ are live. A missing or wrong key returns 401 INVALID_API_KEY with a hint field pointing back to the sandbox start endpoint. Expired sandbox keys return SANDBOX_EXPIRED.

Idempotency

Write endpoints accept an optional idempotency_key in the body. Repeating a request with the same key within 24 hours returns the original response instead of creating a duplicate.

Payments

POST /payments — create a payment link. Body: amount (USDT, required), description, metadata, idempotency_key. The returned id is a payment link id; the link expires in 24 hours. Test-mode payments are marked api_mode: "test".

GET /payments/:id — fetch one payment.

Escrow

POST /escrows — create an escrow between two Telegram users. Body: buyer_telegram_id (required), seller_telegram_id (required), amount (required, minimum 1 USDT), description, metadata, idempotency_key, expires_hours (default 72). Errors: INVALID_PARAMS (missing required fields), INVALID_AMOUNT (below 1 USDT).

GET /escrows — list escrows. GET /escrows/:id — fetch one. POST /escrows/:id/release — release funds to the seller. POST /escrows/:id/cancel — cancel and refund.

Withdrawals

POST /withdrawals — pay out to a PAYchat user. Body: telegram_id, amount, memo, idempotency_key.

QR payments

POST /qr-payments — create a QR payment. Body: amount, description, metadata, idempotency_key, is_fixed_amount (default true), min_amount, max_amount (for variable-amount QR), single_use (default true), expires_hours. GET /qr-payments, GET /qr-payments/:id, DELETE /qr-payments/:id.

Test simulator (test mode only)

POST /test/simulate-payment — mark a test payment or escrow as paid, firing the same webhooks as a real payment. Body: type ("payment" or "escrow"), id (the object id).

Webhooks

Set your webhook URL via PUT /webhook (HTTPS only). Delivery log: GET /webhooks; retry: POST /webhooks/:id/retry; send a test event: POST /webhook-test. Events: payment.completed, escrow.created, escrow.paid, escrow.released, escrow.cancelled.

Account & info

GET /balance — partner balance. GET /fee-info — per-call costs and volume discounts. GET /dashboard, GET /stats, GET /transactions — activity overviews. GET /plans — plans and pricing (no auth needed).

Not for autonomous agents

POST /transfer moves funds between users and requires the end user's PIN session inside the PAYchat Mini App. AI agents cannot call it standalone; use payments or escrow instead.

Also available

Read-only user-side OpenAPI (MCP tools): https://paychat.info/mcp/openapi.json. MCP API keys are issued by users inside the PAYchat Mini App.

Questions → https://t.me/paychat_group

Start → https://t.me/paychat_aibot?start=partner