Paysell

क्रिप्टो भुगतान स्वीकार करें

Paysell TON नेटवर्क पर TON और USDT का निपटान करता है। आप एक इनवॉइस बनाते हैं, हम आपको एक लिंक देते हैं, और जैसे ही पैसा चेन पर पुष्टि होकर आपके बैलेंस में जमा होता है, आपको एक हस्ताक्षरित कॉलबैक मिलता है।

अवलोकन#

Paysell क्या करता है, और क्या नहीं।

Paysell एक पेमेंट प्रोसेसर है, वॉलेट नहीं। आप कभी भी प्राइवेट की को हैंडल नहीं करते, ब्लॉकचेन पर नज़र नहीं रखते, या यह तय नहीं करते कि लेन-देन कब अंतिम है — यह हिस्सा हम संभालते हैं।

हर इनवॉइस को अपना खुद का रिसीविंग एड्रेस मिलता है। जब कोई खरीदार उसका भुगतान करता है, हम नेटवर्क से ट्रांसफर की पुष्टि का इंतज़ार करते हैं, अपनी फ़ीस काटते हैं, और बाकी राशि आपके बैलेंस में जमा कर देते हैं। आप किसी भी पते पर निकाल सकते हैं।

बैलेंस हमारे पास रहते हैं और सत्य का एकमात्र स्रोत हैं। इन्हें दिखाएं, लेकिन दूसरी कॉपी को कभी अधिकृत मानकर न रखें — दो काउंटर हमेशा अंततः अलग हो जाते हैं, और फिर किसी को पता नहीं चलता कि कौन सा सही है।

भुगतान कैसे काम करता है#

छह चरण, ज़्यादातर हमारी तरफ़ से।

छह चरण, ज़्यादातर हमारी तरफ़ से:

  1. 1

    आपका ग्राहक पे पर क्लिक करता है

    आपका सर्वर राशि और आपके अपने ऑर्डर रेफ़रेंस के साथ हमारे API को कॉल करता है।

  2. 2

    हम एक पता देते हैं

    एक ताज़ा रिसीविंग एड्रेस पहले से तैयार पूल से लिया जाता है और इस इनवॉइस से जोड़ा जाता है। एक पता ठीक एक खुले इनवॉइस का होता है, इसी तरह भुगतान उससे मिलाया जाता है।

  3. 3

    ग्राहक कॉइन भेजता है

    वह QR कोड स्कैन करता है या पता कॉपी करता है। उसे उस payment_url पर भेजें जो हम लौटाते हैं और पेज आपके लिए सब कुछ संभाल लेता है — राशि, पता, QR, काउंटडाउन, लाइव स्टेटस।

  4. 4

    हम ट्रांसफ़र पहचानते हैं

    ब्लॉकचेन डेटा के दो स्वतंत्र स्रोतों से पूछा जाता है, और उनके जवाबों की तुलना की जाती है। अगर वे असहमत हों, तो हम सुविधाजनक जवाब चुनने के बजाय रुक जाते हैं।

  5. 5

    हम फ़ाइनलिटी का इंतज़ार करते हैं

    मास्टरचेन में शामिल होना, साथ में ऊपर तीन ब्लॉक। लगभग पंद्रह सेकंड — एक भुगतान जो सेटल दिखे और बाद में गायब हो जाए, वह आपका नुकसान होगा, इसलिए हम यह जोखिम नहीं लेते।

  6. 6

    जमा हो गया, और आपको बताया जाता है

    फ़ीस काट ली जाती है, बाकी आपके बैलेंस में आता है, और आपके order_id के साथ एक हस्ताक्षरित वेबहुक आपके सर्वर पर जाता है।

भुगतान से कॉलबैक तक: लगभग एक मिनट — लगभग पंद्रह सेकंड नेटवर्क पुष्टिकरण के, बाकी निगरानी किए गए पतों का हमारा स्वीप है।

पैसा कहाँ जाता है#

फ़ीस, और यह किस पर आधारित है।

फ़ीस 0.2% है, आपकी दुकान के रजिस्टर होने के समय तय हो जाती है। अगर बाद में मानक दर बदलती है, तो आपकी नहीं बदलती — यह हर इनवॉइस में एक संख्या के रूप में लिखी जाती है, किसी सेटिंग के संदर्भ के रूप में नहीं।

फ़ीस जो वास्तव में आता है उस पर ली जाती है, इनवॉइस में मांगी गई राशि पर नहीं। 5 USDT का बिल बनाएं और 20 मिलें, तो फ़ीस 20 पर गणना होती है। कम भुगतान हो, तो जो आया उस पर गणना होती है।

example
Invoice:   5.000000 USDT
Received: 20.000000 USDT   (the buyer sent more)
Fee 0.2%:  0.040000 USDT   (on 20, not on 5)
Credited: 19.960000 USDT

अतिरिक्त भुगतान पूरी तरह जमा किया जाता है — हम अंतर नहीं रखते। कम भुगतान इनवॉइस को खुला छोड़ देता है ताकि खरीदार उसी पते पर बाकी राशि जमा कर सके।

रिसीविंग एड्रेस से कॉइन निकालने में नेटवर्क गैस लगती है, और वह हम चुकाते हैं — वह हिस्सा आपके बैलेंस को कभी नहीं छूता। अपने पते पर निकासी अलग बात है: उसकी अपनी फ़ीस होती है, जो आपकी माँगी गई राशि से काटी जाती है, और सटीक आंकड़े शुल्क तालिका में दिए गए हैं।

क्विक स्टार्ट#

आपके पहले इनवॉइस तक पाँच मिनट।

पाँच कदम। दो आपके अकाउंट एरिया में क्लिक हैं, एक आपके सर्वर से भेजी गई एक ही रिक्वेस्ट है, और आख़िरी दो अपने आप हो जाते हैं।

  1. 1

    एक दुकान बनाएं

    अपने अकाउंट एरिया में। यह तुरंत भुगतान स्वीकार करना शुरू कर देती है — समीक्षा का इंतज़ार किए बिना। सत्यापन चुपचाप बैकग्राउंड में होता है और केवल निकासी को सीमित करता है, आने वाले भुगतान को नहीं।

  2. 2

    एक API की जारी करें

    आपकी दुकान → API कीज़ → नई की। की और वेबहुक सीक्रेट एक ही बार दिखाए जाते हैं, फिर कभी नहीं। इन्हें डेटाबेस पासवर्ड की तरह सुरक्षित रखें, और इन्हें कभी ब्राउज़र में न भेजें।

  3. 3

    एक इनवॉइस बनाएं

    आपके सर्वर से एक रिक्वेस्ट, वापसी में एक लिंक। नीचे दिए गए चारों स्निपेट बिल्कुल एक ही चीज़ भेजते हैं।

  4. 4

    खरीदार को payment_url पर भेजें

    यही पूरा चेकआउट है — राशि, पता, QR कोड, काउंटडाउन, लाइव स्टेटस — और बनाने के लिए कुछ नहीं है। खरीदार को असल में क्या दिखता है, इसके लिए देखें चेकआउट

  5. 5

    वेबहुक का इंतज़ार करें

    जैसे ही पैसा चेन पर पुष्ट होकर जमा हो जाता है, हम आपके सर्वर पर हस्ताक्षरित payment.credited इवेंट POST करते हैं। हस्ताक्षर सत्यापित करें, फिर ऑर्डर को भुगतान किया गया चिह्नित करें — लेकिन तभी जब data.status paid या overpaid हो। देखें वेबहुक

The same request, four ways

curl -X POST https://paysell.me/api/merchant/v1/invoices \
  -H "Authorization: Bearer sk_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "asset": "USDT_TON",
    "amount": "5",
    "order_id": "order-1042",
    "idempotency_key": "order-1042"
  }'

खरीदार को रिस्पॉन्स में मिले payment_url पर रीडायरेक्ट करें। बस हो गया — बाकी वेबहुक के रूप में आएगा।

What to do next

प्रमाणीकरण#

आपकी API की, और इसका उपयोग कैसे होता है।

हर रिक्वेस्ट Authorization हेडर में आपकी की ले जाती है:

http
Authorization: Bearer sk_live_IH4SNdnYsv-yabTuPCVln4vOvvGoEyxA

यहाँ जारी की गई हर की sk_live_ से शुरू होती है। sk_test_ प्रीफ़िक्स केवल टेस्ट नेटवर्क पर चलने वाली डिप्लॉयमेंट में मौजूद है, और ऐसी कोई डिप्लॉयमेंट पेश नहीं की जाती — देखें टेस्टिंग। हम एक तरफ़ा हैश स्टोर करते हैं, की खुद नहीं, इसलिए कोई भी, हम सहित, इसे आपको दोबारा नहीं दिखा सकता। खो दी? नई जारी करें और पुरानी रद्द करें।

दुकान की से निकाली जाती है, इसलिए कोई भी रिक्वेस्ट दुकान आईडी नहीं लेती। एक की केवल अपनी ही दुकान पर काम कर सकती है।

पते में संस्करण होता है: /api/merchant/v1/…। एक संस्करण के भीतर हम केवल फ़ील्ड जोड़ते हैं — कुछ भी नाम नहीं बदलता और चुपचाप अर्थ नहीं बदलता। जो बदलाव आपका कोड तोड़ देगा, उसे नया उपसर्ग /v2 मिलता है, और /v1 घोषित अवधि तक चलता रहता है।

यह की आपके नाम से इनवॉइस बनाती है। इसे सर्वर-साइड रखें। ब्राउज़र JavaScript में कुछ भी पब्लिक होता है, चाहे वह कितनी भी अच्छी तरह छिपा हो।

Endpoints at a glance#

Four calls, three of them authenticated.

This is the whole merchant API. Balances, payouts and history are not in it — they live in your account area, where a person is looking at them.

EndpointMethodAuthWhat it does
/invoicesPOSTAPI keyOpen an invoice and get a payment link. Details.
/invoices/{invoice_id}GETAPI keyRead one invoice's current state. Details.
/invoices/{invoice_id}/cancelPOSTAPI keyClose an invoice that is still open and free its address. Details.
/public/invoices/{invoice_id}GETnoneWhat the hosted checkout page reads. Only needed if you build your own. Details.

Every path is relative to https://paysell.me/api/merchant/v1. There is no list endpoint and no refund endpoint — see Refunds.

इनवॉइस बनाएं#

POST /api/merchant/v1/invoices

POST/api/merchant/v1/invoices

रिक्वेस्ट बॉडी

फ़ील्डटाइपअनिवार्यविवरण
assetstringहाँया तो TON या USDT_TON
amountstringहाँकॉइन की सामान्य इकाइयाँ, स्ट्रिंग के रूप में: "5" यानी 5 USDT। कॉइन के पास जितने दशमलव स्थान हैं, उससे अधिक नहीं। देखें राशियाँ
order_idstringनहींआपका अपना रेफ़रेंस, अधिकतम 200 वर्ण। हर वेबहुक में वापस आता है — इस तरह आप भुगतान को ऑर्डर से मिलाते हैं।
descriptionstringनहींअधिकतम 1000 वर्ण। भुगतान पेज पर खरीदार को दिखाया जाता है।
ttl_minutesnumberनहींइनवॉइस कितनी देर भुगतान योग्य रहता है, मिनटों में। 1–1440; इसे छोड़ दें तो डिफ़ॉल्ट लागू होता है — फ़िलहाल 2 घंटे
idempotency_keystringनहींअधिकतम 200 वर्ण। दोबारा कोशिश करते समय वही वैल्यू भेजें और आपको दूसरे के बजाय वही इनवॉइस मिलेगा। यह बॉडी की फ़ील्ड है, Idempotency-Key हेडर नहीं — वह हेडर यहाँ पढ़ा ही नहीं जाता।

रिस्पॉन्स · 201

json
{
  "invoice_id": "12c22c1a-a496-4c1e-abe3-72661ef8706e",
  "payment_url": "https://paysell.me/pay/12c22c1a-a496-4c1e-abe3-72661ef8706e",
  "address": "UQAvDJp7QDwqRcuNQBiK2GhBt71Xh1_UMYPCzMkQAoBPmZKl",
  "asset": "USDT_TON",
  "amount": "5",
  "amount_minor": "5000000",
  "status": "pending",
  "paid": "0",
  "paid_minor": "0",
  "order_id": "order-1042",
  "description": "Pro subscription",
  "expires_at": "2026-09-06T17:20:55Z",
  "created_at": "2026-09-06T15:20:55Z"
}

इसे अपने ऑर्डर में कैसे इस्तेमाल करें

फ़ील्डइसके साथ क्या करें
invoice_idइसे अपने ऑर्डर के साथ स्टोर करें। यह वही है जो हर जगह भुगतान की पहचान करता है।
payment_urlखरीदार को यहां रीडायरेक्ट करें। और कुछ बनाने की ज़रूरत नहीं।
addressकेवल अगर आप अपना खुद का चेकआउट बना रहे हैं। इसे बिल्कुल वैसे ही दिखाएं जैसे मिला था — नीचे दी चेतावनी देखें।
amountसामान्य इकाइयों में राशि, ठीक वैसी जैसी आपने भेजी। दिखाने के लिए यही लें।
amount_minorवही राशि सबसे छोटी इकाई में पूर्णांक के रूप में। गणना इसी से करें।
expires_atएक काउंटडाउन दिखाएं। इसके बीत जाने के बाद इस इनवॉइस के लिए पता निगरानी में नहीं रहता।
statusयहां हमेशा pending होता है। असली बदलाव वेबहुक से आते हैं।
अगर आप अपना पेज बनाते हैं, तो पता बिल्कुल वैसे ही प्रिंट करें जैसे लौटाया गया था। यह नॉन-बाउंसेबल फ़ॉर्म में है (मेननेट पर UQ…, टेस्टनेट पर 0Q…)। इसे बदलना, सजाना, या उसी पते की किसी अन्य एन्कोडिंग से बदलना — और अभी तक डिप्लॉय न हुए वॉलेट पर भेजे गए कॉइन भेजने वाले को वापस लौट जाएंगे।

इनवॉइस पढ़ें#

GET /api/merchant/v1/invoices/{invoice_id}

GET/api/merchant/v1/invoices/{invoice_id}

ऊपर जैसा ही रूप, जिसमें status, paid और paid_minor वर्तमान स्थिति दिखाते हैं: paid बताता है कि सामान्य इकाइयों में कितना आ चुका है, और paid_minor वही रकम सबसे छोटी इकाई में पूर्णांक के रूप में। वेबहुक छूट जाने पर बैकअप के रूप में, या धन्यवाद पेज पर उपयोगी।

इसे हर कुछ सेकंड में एक बार से ज़्यादा पोल न करें, और वेबहुक को मुख्य चैनल मानें। किसी और दुकान के इनवॉइस 404 का जवाब देते हैं — 403 नहीं, ताकि किसी आईडी के अस्तित्व की जांच न की जा सके।

इनवॉइस रद्द करें#

POST /api/merchant/v1/invoices/{invoice_id}/cancel

POST/api/merchant/v1/invoices/{invoice_id}/cancel

ऐसे इनवॉइस को बंद कर देता है जो अब भी खुला है — pending या underpaid — और उसका पता रिलीज़ कर देता है। इसका उपयोग तब करें जब ग्राहक चेकआउट छोड़ दे: पते एक सीमित संसाधन हैं, और उन्हें लौटाना पूल को स्वस्थ रखता है।

जो इनवॉइस अब खुला नहीं है, वह 409 का जवाब देता है। किसी underpaid इनवॉइस को रद्द करने से किसी को कॉइन वापस नहीं मिलते: जो पैसा पहले ही जमा हो चुका है वह आपके बैलेंस पर बना रहता है, और बंद केवल यह होता है कि अब टॉप-अप स्वीकार नहीं किया जाएगा।

वेबहुक#

क्या आता है, और इसे कैसे सत्यापित करें।

की बनाते समय वेबहुक URL सेट करें। जब भुगतान जमा होता है तब हम वहाँ POST भेजते हैं — और तब भी जब अतिरिक्त जांच के लिए रोकी गई कोई जमा राशि अस्वीकार कर दी जाती है। हर डिलीवरी हस्ताक्षरित होती है, और जब तक आप 2xx का जवाब न दें तब तक हम लगभग डेढ़ दिन तक दोबारा कोशिश करते रहते हैं। माल status: paid या overpaid पर छोड़ें — केवल कॉल आ जाने पर नहीं।

इवेंट

इवेंटकबबॉडी में क्या आता है
payment.creditedट्रांसफ़र चेन पर पुष्ट हो चुका है, हमारा कमीशन काट लिया गया है, और बाकी आपके बैलेंस में है।नीचे सूचीबद्ध फ़ील्ड।
payment.rejectedअतिरिक्त जांच के लिए रोकी गई जमा राशि (देखें स्टेटस संदर्भ) अस्वीकार कर दी गई। पैसा आपके बैलेंस तक नहीं पहुँचेगा।invoice_id, order_id, asset, amount, tx_hash और reason। माल न छोड़ें; अगर इनवॉइस पहले के किसी ट्रांसफ़र से पहले ही paid था, तो यह इवेंट उस अतिरिक्त जमा राशि के बारे में है, उस भुगतान के बारे में नहीं।

क्या आता है

json
{
  "event_id": "99f74f58-efbb-4af1-b0a3-76b0073f9e6b",
  "type": "payment.credited",
  "data": {
    "invoice_id": "12c22c1a-a496-4c1e-abe3-72661ef8706e",
    "order_id": "order-1042",
    "asset": "USDT_TON",
    "amount": "5000000",
    "credited": "4905000",
    "fee": "95000",
    "status": "paid",
    "paid_minor": "5000000",
    "tx_hash": "97a1f0…"
  }
}
json
{
  "event_id": "0a1b2c3d-4e5f-4a6b-8c9d-0e1f2a3b4c5d",
  "type": "payment.rejected",
  "data": {
    "invoice_id": "12c22c1a-a496-4c1e-abe3-72661ef8706e",
    "order_id": "order-1042",
    "asset": "USDT_TON",
    "amount": "5000000",
    "tx_hash": "97a1f0…",
    "reason": "could not be matched to any order"
  }
}

फ़ील्ड मैपिंग

फ़ील्डअर्थ
event_idहर इवेंट के लिए यूनीक; X-Paysell-Event-Id हेडर में भी। इसे स्टोर करें और दोहराव को अनदेखा करें — नीचे देखें।
data.order_idआपका रेफ़रेंस। इससे अपना ऑर्डर खोजें।
data.amountइस ट्रांसफ़र में खरीदार ने कितना भेजा, सबसे छोटी इकाई में — API के विपरीत, जो सामान्य इकाइयाँ लेता है।
data.feeहमने कितना लिया, सबसे छोटी इकाई में।
data.creditedआपके बैलेंस में कितना आया: amount − fee, सबसे छोटी इकाई में।
data.paid_minorइस इनवॉइस पर अब तक कुल कितना मिला, सबसे छोटी इकाई में। underpaid पर यही फ़ील्ड मायने रखती है: स्टेटस कहता है कि कम आया, और यह बताता है कि कितना कम।
data.assetवह कॉइन जो वास्तव में आया। ज़रूरी नहीं कि वही हो जो इनवॉइस ने माँगा था।
data.asset_mismatchकेवल तभी मौजूद और true होता है जब आया हुआ कॉइन इनवॉइस के कॉइन से भिन्न हो। पैसा आपके खाते में जमा हो जाता है, पर इनवॉइस अदत्त रहता है और status कभी paid नहीं होता।
data.invoice_assetasset_mismatch के साथ आता है: वह कॉइन जो इनवॉइस वास्तव में माँगता है।
data.statusइनवॉइस का अभी का स्टेटस: pending, underpaid, paid, overpaid या expired। इसकी तुलना उससे करें जिसकी आपने उम्मीद की थी।
data.tx_hashऑन-चेन लेन-देन, आपके रिकॉर्ड और सपोर्ट के लिए।

हर डिलीवरी के हेडर

http
X-Paysell-Event:      payment.credited
X-Paysell-Event-Id:   99f74f58-efbb-4af1-b0a3-76b0073f9e6b
X-Paysell-Timestamp:  1789000000
X-Paysell-Signature:  sha256=6f1c0e6a…
हेडरअर्थ
X-Paysell-Eventइवेंट का प्रकार: payment.credited या payment.rejected
X-Paysell-Event-Idहर इवेंट के लिए यूनीक। दोहराव हटाने के लिए यही वैल्यू है।
X-Paysell-Timestampहमने कब हस्ताक्षर किया, यूनिक्स सेकंड में। यह हस्ताक्षरित स्ट्रिंग का हिस्सा है।
X-Paysell-Signaturesha256= और उसके बाद हेक्स HMAC। नीचे देखें।

हस्ताक्षर सत्यापित करना

हर रिक्वेस्ट पर उस वेबहुक सीक्रेट से हस्ताक्षर होता है जो की बनाते समय एक ही बार दिखाया गया था। हस्ताक्षर है HMAC-SHA256(secret, "{timestamp}.{raw_body}")X-Paysell-Timestamp से लिया गया टाइमस्टैम्प, एक शाब्दिक बिंदु, और फिर बॉडी के बाइट्स। कुछ भी करने से पहले इसे जांचें: इसके बिना, जो कोई भी आपका URL जान लेगा वह आपको एक भुगतान किया हुआ ऑर्डर थमा सकता है।

Python:

python
import hmac, hashlib, time

def is_ours(body: bytes, signature: str, timestamp: str, secret: str) -> bool:
    if abs(time.time() - int(timestamp)) > 300:      # ±5 minutes
        return False
    signed = timestamp.encode() + b"." + body
    expected = hmac.new(secret.encode(), signed, hashlib.sha256).hexdigest()
    # compare_digest, not ==: a plain comparison leaks the answer through timing
    return hmac.compare_digest("sha256=" + expected, signature)

Node.js:

javascript
const crypto = require("node:crypto")

function isOurs(body, signature, timestamp, secret) {
  if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false  // ±5 min
  const expected = "sha256=" + crypto
    .createHmac("sha256", secret)
    .update(timestamp + ".").update(body)   // body is the raw Buffer, not a parsed object
    .digest("hex")
  const a = Buffer.from(expected), b = Buffer.from(signature)
  // timingSafeEqual throws when the lengths differ, so check that first
  return a.length === b.length && crypto.timingSafeEqual(a, b)
}

कच्चे बॉडी बाइट्स पर हस्ताक्षर करें, बिल्कुल वैसे ही जैसे वे मिले हैं। JSON को पार्स करके दोबारा सीरियलाइज़ करेंगे तो बाइट्स बदल जाएंगे — की का क्रम, स्पेसिंग — और हस्ताक्षर मेल नहीं खाएगा। तुलना नियत समय में करें (hmac.compare_digest, crypto.timingSafeEqual): सादा == गलत पहले बाइट पर जल्दी लौट आता है, और यही अंतर एक-एक बाइट करके हस्ताक्षर का अनुमान लगाने के लिए काफ़ी है।

टाइमस्टैम्प विंडो

जिसका भी टाइमस्टैम्प आपकी अपनी घड़ी से पाँच मिनट से ज़्यादा दूर हो — किसी भी दिशा में — उसे अस्वीकार करें। टाइमस्टैम्प हस्ताक्षरित स्ट्रिंग के अंदर ठीक इसीलिए है कि हस्ताक्षर तोड़े बिना उसे बदला न जा सके; विंडो ही उसे असल सुरक्षा में बदलती है। इसके बिना, एक बार पकड़ी गई रिक्वेस्ट हमेशा के लिए वैध रहती है और कभी भी दोहराई जा सकती है — अकेला हस्ताक्षर कभी एक्सपायर नहीं होता। अपने सर्वर की घड़ी NTP पर रखें, वरना यह जांच अच्छी-भली डिलीवरी को भी अस्वीकार करने लगेगी।

दोहराव

एक ही इवेंट एक से ज़्यादा बार आ सकता है। यह बग नहीं है: हम तब तक दोबारा कोशिश करते हैं जब तक आप 2xx का जवाब न दें, और जो डिलीवरी सफल रही लेकिन जिसका जवाब हम तक कभी नहीं पहुँचा, वह फिर से भेजी जाती है। X-Paysell-Event-Id रिकॉर्ड करें (यह बॉडी में event_id के रूप में भी आता है) और दूसरी बार आने पर कुछ न होने दें।

दोबारा कोशिशें

पहली कोशिश भुगतान जमा होते ही निकल जाती है। अगर वह विफल होती है — टाइमआउट, कनेक्शन अस्वीकार, TLS एरर, कोई रीडायरेक्ट, या कोई भी ग़ैर-2xx स्टेटस — तो हम एक तय शेड्यूल पर दोबारा कोशिश करते हैं:

1 मिनट → 5 मिनट → 15 मिनट → 1 घंटा → 6 घंटे → 24 घंटे

कुल सात कोशिशें, लगभग 31 घंटों में फैली हुई। शुरुआती कोशिशें पास-पास हैं क्योंकि आम वजह यह होती है कि रिसीवर रीस्टार्ट हो रहा था और अब तक लौट भी आया है; बाद वाली छितरी हुई हैं क्योंकि जो सर्वर एक दिन से बंद पड़ा है उस पर हथौड़े बरसाने से किसी का भला नहीं होता।

आख़िरी कोशिश के बाद डिलीवरी को dropped चिह्नित कर दिया जाता है और हम अपने आप रुक जाते हैं। यह खोई नहीं है: आपके अकाउंट एरिया में भुगतान की पंक्ति उसकी स्थिति, कोशिशों की संख्या और एरर का वर्ग दिखाती है, साथ में एक फिर से भेजें बटन जो सातों कोशिशों का नया दौर शुरू कर देता है। आपका दूसरा सहारा है GET /api/merchant/v1/invoices/{invoice_id} — इनवॉइस को अपना स्टेटस हमेशा पता होता है।

वेबहुक URL कैसा होना चाहिए

URL की जांच तब होती है जब आप उसे सेव करते हैं, और फिर हर एक डिलीवरी से पहले दोबारा। जो URL जांच में विफल होता है, उसे सेव करते समय 422 और code: "webhook_url_rejected" के साथ जवाब मिलता है, और अगर वह बाद में विफल होने लगे तो डिलीवरी failed चिह्नित हो जाती है — बिना किसी दोबारा कोशिश के। नियम ये हैं:

  • केवल `https://`, और पोर्ट 443। वेबहुक में भुगतान का ब्योरा होता है; सादे http में उसे रास्ते का कोई भी पढ़ सकता है।
  • डोमेन नाम, IP पता नहीं। आपको वैसे भी सर्टिफ़िकेट चाहिए, और नंगे IP के लिए सर्टिफ़िकेट जारी नहीं होते।
  • कोई `localhost` नहीं, और न ही .local, .internal, .corp, .lan या .test नाम — हमारे सर्वर आपके नेटवर्क तक पहुँच नहीं सकते, और जो नाम हमारे अपने नेटवर्क के अंदर रिज़ॉल्व होता है, ठीक उसी को हमें कभी कॉल नहीं करना चाहिए।
  • URL में कोई क्रेडेंशियल नहीं (https://user:pass@…)। ज़रूरत हो तो अपना टोकन पाथ में या क्वेरी पैरामीटर में डालें।
  • नाम जिन भी पतों पर रिज़ॉल्व होता है, वे सब सार्वजनिक होने चाहिए — A और AAAA दोनों। प्राइवेट, लूपबैक, लिंक-लोकल और CGNAT रेंज अस्वीकार की जाती हैं, और यह जांच हर डिलीवरी से पहले दोहराई जाती है, इसलिए बाद में रिकॉर्ड को 127.0.0.1 पर मोड़ देना भी काम नहीं करेगा।
  • रीडायरेक्ट एक विफलता है, कोई पड़ाव नहीं। हम उनका पीछा नहीं करते: आपने जो पता दिया था वह जांचा गया था, और Location हेडर वाला नहीं।
सत्यापन जानबूझकर दो बार होता है — एक बार जब आप URL सेव करते हैं, ताकि किसी टाइपो का जवाब तुरंत मिले, न कि चुपचाप डिलीवरी न होने के रूप में; और एक बार हर भेजने से पहले, क्योंकि डोमेन का मालिक उसे किसी भी क्षण किसी आंतरिक पते पर मोड़ सकता है। अगर आपका एंडपॉइंट बदलता है, तो पहले की अपडेट करें: अस्वीकृत URL पर कुछ भी डिलीवर नहीं होता और कतार में भी नहीं लगता।

जल्दी जवाब दें

कोई भी 2xx चलेगा, दस सेकंड के भीतर — कनेक्शन समेत यही हमारा पूरा टाइमआउट है। पहले जवाब दें, धीमा काम उसके बाद करें; जो एंडपॉइंट जवाब देने से पहले अपने ही डेटाबेस का इंतज़ार करता है, वह देर-सबेर टाइमआउट के रूप में दर्ज होगा और उसे दोबारा भेजा जाएगा, और आप एक ही इवेंट दो बार प्रोसेस करेंगे। बाकी सब कुछ — 4xx, 5xx, रीडायरेक्ट, अटक जाना — विफल कोशिश गिना जाता है और ऊपर वाले शेड्यूल में लौट जाता है।

डिलीवरी, ईमानदारी से

जिसकी गारंटी है वह है डिलीवरी तंत्र: लगभग 31 घंटों में सात कोशिशें, आपके अकाउंट एरिया से मैनुअल रीसेंड, और एक इनवॉइस एंडपॉइंट जिसे असली स्टेटस हमेशा पता होता है। अपना फ़्लो ऐसा बनाएं कि कभी न पहुँचने वाले वेबहुक से आपका कुछ न बिगड़े — अपने थैंक-यू पेज पर इनवॉइस पढ़ें, या घंटे में एक बार खुले इनवॉइस का मिलान कर लें। वेबहुक तेज़ रास्ता है, इकलौता रास्ता नहीं।

A complete receiver#

Signature, deduplication and a fast answer, end to end.

The snippets above verify one signature. This is the whole endpoint: raw body, signature check, deduplication by event_id, a fast 2xx, and the one condition that is allowed to mark an order paid.

Node.js with Express. express.raw is the part people get wrong: express.json() hands you a parsed object, and bytes you re-serialise from it are not the bytes we signed.

javascript
const express = require("express")
const crypto = require("node:crypto")

const app = express()
const SECRET = process.env.PAYSELL_WEBHOOK_SECRET

function isOurs(body, signature, timestamp) {
  const sentAt = Number(timestamp)
  if (!Number.isFinite(sentAt)) return false
  if (Math.abs(Date.now() / 1000 - sentAt) > 300) return false   // ±5 minutes

  const expected = "sha256=" + crypto
    .createHmac("sha256", SECRET)
    .update(timestamp + ".").update(body)      // raw Buffer, not a parsed object
    .digest("hex")

  const a = Buffer.from(expected), b = Buffer.from(signature ?? "")
  return a.length === b.length && crypto.timingSafeEqual(a, b)
}

app.post(
  "/paysell/webhook",
  express.raw({ type: "application/json" }),   // NOT express.json()
  async (req, res) => {
    const signature = req.get("X-Paysell-Signature")
    const timestamp = req.get("X-Paysell-Timestamp")
    if (!isOurs(req.body, signature, timestamp)) return res.sendStatus(401)

    const event = JSON.parse(req.body.toString("utf8"))

    // Answer first: 10 seconds is the whole timeout, connection included.
    res.sendStatus(200)

    // Deduplicate. In real code this is a unique column, not a Set.
    if (await alreadyHandled(event.event_id)) return
    await remember(event.event_id)

    if (event.type !== "payment.credited") return
    const { order_id, status, credited, asset, tx_hash } = event.data

    // The only condition that may release the goods.
    if (status !== "paid" && status !== "overpaid") return
    await markOrderPaid(order_id, { credited, asset, tx_hash })
  }
)

Python with Flask. request.get_data() is the raw body; request.form and request.json are not.

python
import hashlib
import hmac
import json
import os
import time

from flask import Flask, request

app = Flask(__name__)
SECRET = os.environ["PAYSELL_WEBHOOK_SECRET"]


def is_ours(body: bytes, signature: str, timestamp: str) -> bool:
    try:
        sent_at = int(timestamp)
    except (TypeError, ValueError):
        return False
    if abs(time.time() - sent_at) > 300:              # ±5 minutes
        return False

    signed = timestamp.encode() + b"." + body
    expected = hmac.new(SECRET.encode(), signed, hashlib.sha256).hexdigest()
    return hmac.compare_digest("sha256=" + expected, signature)


@app.post("/paysell/webhook")
def paysell_webhook():
    body = request.get_data()                          # raw bytes, unparsed
    if not is_ours(body, request.headers.get("X-Paysell-Signature", ""),
                   request.headers.get("X-Paysell-Timestamp", "")):
        return "", 401

    event = json.loads(body)

    # Deduplicate. In real code this is a unique column, not a set.
    if already_handled(event["event_id"]):
        return "", 200                                 # a repeat is still a success
    remember(event["event_id"])

    if event["type"] == "payment.credited":
        data = event["data"]
        # The only condition that may release the goods.
        if data["status"] in ("paid", "overpaid"):
            mark_order_paid(data["order_id"], data)

    return "", 200                                     # 2xx within 10 seconds

What the code is doing, and why

  • Verify before anything acts on the body. An unsigned request that reaches your business logic is a paid order for whoever found your URL.
  • Answer 2xx first, work afterwards. Ten seconds is the whole timeout, connection included. A handler that waits for its own database gets recorded as a timeout and retried, and you process the same event twice.
  • Deduplicate on `event_id` in storage that survives a restart. The in-memory set in the examples keeps them short; a real one is a unique column in your database.
  • Mark the order paid only on `status: paid` or `overpaid`. underpaid means part of the money arrived and the invoice is still open, and a deposit in the wrong coin never makes an invoice paid either.
  • Answer 2xx to a duplicate too. A repeat that gets a 4xx looks like a failure to us and comes back again on the schedule.

payment.rejected arrives at the same endpoint. It means a deposit held for an additional check was declined and the money will not be credited: release nothing, and if the invoice was already paid by an earlier transfer, this event is about the extra deposit, not about that payment.

स्टेटस संदर्भ#

सभी इनवॉइस और भुगतान स्टेटस, समझाए गए।

इनवॉइस

स्टेटसअर्थक्या करें
pendingभुगतान का इंतज़ार।ऑर्डर खुला रखें।
paidपूरा भुगतान हो गया।सामान डिलीवर करें।
overpaidमांगे गए से ज़्यादा आया। अतिरिक्त रकम पूरी तरह आपको जमा की जाती है।सामान डिलीवर करें; चाहें तो अंतर वापस कर दें।
underpaidमांगे गए से कम आया। इनवॉइस खुला रहता है और अपना पता बनाए रखता है: खरीदार उसी जगह पर बाकी रकम भेज सकता है, और paid_minor बताता है कि अब तक कितना आ चुका है। यह अपने बचे हुए जीवनकाल तक, और expires_at के बाद 24 घंटे की छूट अवधि तक भुगतान-योग्य रहता है।टॉप-अप का इंतज़ार करें, या ग्राहक से मामला निपटा लें। सामान डिलीवर न करें — इनवॉइस का भुगतान नहीं हुआ है।
expiredविंडो बंद हो गई, छूट अवधि समेत। इसमें पैसा फिर भी हो सकता है: जो कुछ आया वह आपके बैलेंस पर ही रहा, और paid_minor बताता है कि कितना।नया इनवॉइस दें। पुराने पते पर भुगतान स्वीकार न करें: इनवॉइस के एक्सपायर होते ही वह पता वापस पूल में चला जाता है, और बहुत देर से आया ट्रांसफ़र अपने आप जमा होने के बजाय सपोर्ट का मामला बन जाता है। ग्राहक से यह कहने से पहले कि कुछ नहीं मिला, paid_minor जांच लें।
cancelledआपके द्वारा रद्द। पता वापस पूल में छोड़ दिया जाता है।कुछ नहीं।

भुगतान

आपके अकाउंट एरिया में दिखता है; भुगतान के दौरान ग्राहक को सपोर्ट करते समय उपयोगी।

स्टेटसअर्थ
detectedचेन पर देखा गया, पुष्टिकरण का इंतज़ार।
confirmedनेटवर्क ने पुष्टि की। अगला, जमा करना।
creditedआपके बैलेंस में। यही वह पल है जब वेबहुक चलता है।
reviewअतिरिक्त जांच के लिए रोका गया — उदाहरण के लिए, बिना खुले इनवॉइस वाले पते पर आने वाले कॉइन।
rejectedजमा नहीं किया गया। कारण दर्ज है।

जब कोई भुगतान `review` में जाता है

कुछ जमा तुरंत क्रेडिट होने के बजाय अतिरिक्त जांच के लिए रोक लिए जाते हैं: असामान्य रूप से बड़ी रकम, ऐसे पते पर आने वाले कॉइन जिस पर कोई खुला इनवॉइस नहीं है, या हम जिन दो ब्लॉकचेन स्रोतों को पोल करते हैं उनका इस बारे में आपस में मेल न खाना कि हुआ क्या। कुछ खोता नहीं है — पैसा फ़ैसले का इंतज़ार करता है, और फ़ैसला होते ही वेबहुक चल जाता है, जो मिनटों बाद भी हो सकता है और घंटों बाद भी। इसलिए review दिख रहे भुगतान पर कॉलबैक का न आना विफलता नहीं, सामान्य बात मानें। अगर यह किसी ऑर्डर के लिए मायने रखता है, तो tx_hash बताकर सपोर्ट से पूछें।

राशियाँ#

भेजते समय सामान्य इकाइयाँ, वापसी में सबसे छोटी।

राशियाँ कॉइन की सामान्य इकाइयों में, स्ट्रिंग के रूप में भेजें — "1.5" का अर्थ डेढ़ है। न JSON संख्या, न सबसे छोटी इकाई।

एसेटदशमलवआप भेजते हैंउत्तर में amount_minor
TON9"1.5""1500000000"
USDT_TON6"1.5""1500000"

संख्या नहीं बल्कि स्ट्रिंग, क्योंकि JSON संख्याएँ IEEE-754 डबल होती हैं और नैनोटन में बड़ी राशि उनमें ठीक-ठीक नहीं समाती। कॉइन के पास जितने दशमलव स्थान हैं उससे अधिक देने पर 422 मिलता है — आपके पैसे को चुपचाप गोल नहीं किया जाता। वेबहुक में उल्टा है: वहाँ amount, fee और credited सबसे छोटी इकाई में पूर्णांक हैं, क्योंकि उस ओर को कोड पढ़ता है, इंसान नहीं।

javascript
// Send amounts in the coin's normal units, as a string:
const amount = "1.5"   // one and a half TON or USDT

// In responses, amount is that same human string; amount_minor is the
// integer in smallest units — use it for exact maths, as a string or BigInt:
BigInt(invoice.amount_minor)  // e.g. 1500000n

सीमाएं#

न्यूनतम, अधिकतम, और रेट सीमाएं।

सीमावैल्यूउल्लंघन पर
न्यूनतम इनवॉइस0.1 TON · 3 USDT422
अधिकतम इनवॉइस7000 TON · 10000 USDT422
प्रति दुकान प्रति घंटा इनवॉइस60429
एक साथ खुले इनवॉइस20, हर भुगतान किए गए इनवॉइस के साथ बढ़ते हुए, 200 तक429
इनवॉइस का जीवनकाल1 मिनट – 24 घंटे (डिफ़ॉल्ट 2 घंटे)422
प्रति की API रिक्वेस्ट120 प्रति मिनट429 + Retry-After

न्यूनतम कोई नौकरशाही नहीं है। हमारी फ़ीस प्रतिशत में है, लेकिन भुगतान लेने में एक निश्चित राशि खर्च होती है: रिसीविंग एड्रेस से USDT निकालने का मतलब है पहले उसे गैस से फंड करना — हमारी अपनी जेब से। कुछ डॉलर से कम पर फ़ीस हैंडलिंग का खर्च भी नहीं निकालती, और ऐसा भुगतान स्वीकार करने का मतलब होगा आपको ऐसा पैसा जमा करना जिसे हिलाना ही अलाभकारी है।

अधिकतम सीमा बड़े व्यापारियों के बारे में नहीं है — यह इकाइयों की गलती के लिए बिछाया गया जाल है। जहाँ आपका मतलब "5" था वहाँ "5000000" भेजें, तो वरना आपको पचास लाख डॉलर का इनवॉइस मिल जाता: खरीदार बेतुका आंकड़ा देखकर चला जाता है। असली ऑर्डर इस छत तक कभी नहीं पहुँचता; गलती हमेशा पहुँचती है। दोनों छतें सेटिंग्स हैं (invoice_max_ton, invoice_max_usdt) और आपकी दुकान के लिए बढ़ाई जा सकती हैं — बस कह दीजिए।

प्रति घंटा सीमा और एक साथ खुले इनवॉइस की सीमा, दोनों पता पूल की रक्षा करती हैं। हर खुला इनवॉइस एक रिसीविंग एड्रेस रोके रखता है, और किसी एक साइट पर बेकाबू लूप वरना सबके लिए पूल खाली कर देगा। नई दुकान एक साथ 20 इनवॉइस खुले रख सकती है; हर उस इनवॉइस के लिए जिसका भुगतान उसने वास्तव में वसूला है, यह भत्ता एक बढ़ जाता है, अधिकतम 200 तक। underpaid खुला ही गिना जाता है — वह अब भी अपना पता रोके हुए है, बाकी रकम का इंतज़ार कर रहा है। छोड़ दिए गए इनवॉइस को रद्द करने से उसका पता तुरंत लौट आता है। एक ही idempotency_key के साथ दोबारा कोशिशें प्रति घंटा सीमा में नहीं गिनी जातीं।

रिक्वेस्ट की सीमा प्रति API की 120 प्रति मिनट है — दो कॉल प्रति सेकंड, किसी भी असली ऑर्डर प्रवाह से कहीं ज़्यादा। 429 के साथ Retry-After हेडर सेकंडों में आता है: तंग लूप में दोबारा कोशिश करने के बजाय उतनी देर रुकें, क्योंकि लूप विंडो को और आगे ही खिसकाता है।

एरर#

स्टेटस कोड जो आप वास्तव में देखेंगे।

एरर JSON के रूप में वापस आते हैं, दो रूपों में। जो कुछ हम या प्रोसेसिंग कोर तय करते हैं, वह detail के नीचे {code, message} जोड़ी रखता है। जिस रिक्वेस्ट बॉडी का सत्यापन विफल होता है, उसके लिए वहाँ इसके बजाय फ़ील्ड एररों की सूची आती है। detail.code पढ़ने से पहले जांचें कि आपको कौन सा रूप मिला — और `code` पर शाखा बनाएं, कभी `message` पर नहीं: शब्द कभी भी बदल सकते हैं, कोड नहीं बदलेगा।

json
{
  "detail": {
    "code": "invalid_input",
    "message": "invoice amount below the minimum: 0.010000 USDT_TON, minimum 3.000000 USDT_TON"
  }
}
json
{
  "detail": [
    {
      "type": "string_type",
      "loc": ["body", "amount"],
      "msg": "Input should be a valid string",
      "input": 5
    }
  ]
}
स्टेटसकबक्या करें
401की गायब, गलत, या रद्द है।हेडर जांचें। अगर रद्द थी तो नई की जारी करें।
404ऐसा कोई इनवॉइस नहीं, या यह किसी और दुकान का है।आईडी जांचें। दोनों मामलों का जवाब जानबूझकर एक जैसा है, ताकि किसी आईडी को टटोला न जा सके।
409इनवॉइस ऐसी स्थिति में है जो इसकी मनाही करती है।पहले इसका मौजूदा स्टेटस पढ़ें।
422रिक्वेस्ट का रूप गलत है, या राशि इनवॉइस की सीमाओं से बाहर है।मैसेज में भेजी गई वैल्यू और सीमा, दोनों बताई जाती हैं।
429इस घंटे बहुत ज़्यादा इनवॉइस, एक साथ बहुत ज़्यादा खुले, या बहुत ज़्यादा रिक्वेस्ट।Retry-After जितनी देर कहे उतना रुकें, फिर दोबारा कोशिश करें।
502हम प्रोसेसिंग कोर तक नहीं पहुँच सके।उसी आइडेम्पोटेंसी की के साथ दोबारा कोशिश करें।

कोड

हमारे तय किए हुए फ़ैसलों का रूप यह है: {"detail": {"code": …, "message": …}}। मर्चेंट API ये कोड लौटाता है।

कोडस्टेटसअर्थ
invalid_api_key401की गायब है, गलत ढंग की है, अनजान है या रद्द है। चारों का जवाब एक जैसा है, ताकि किसी की को टटोला न जा सके।
not_found404ऐसा कोई ऑब्जेक्ट नहीं, या यह किसी और दुकान का है।
invalid_input422रिक्वेस्ट कोर में सत्यापन पास नहीं कर सकी — गलत राशि, दशमलव के बाद बहुत ज़्यादा अंक, या इनवॉइस की सीमाओं से बाहर की राशि।
conflict409कार्रवाई मौजूदा स्थिति के विरुद्ध है, जैसे ऐसे इनवॉइस को रद्द करना जो अब खुला नहीं है।
too_many_requests429कोई रेट सीमा: प्रति घंटा इनवॉइस, खुले इनवॉइस, या प्रति मिनट रिक्वेस्ट। Retry-After बताता है कि कितनी देर रुकना है।
cbc_unreachable502हम प्रोसेसिंग कोर तक नहीं पहुँच सके। उसी idempotency_key के साथ दोबारा कोशिश करें।
webhook_url_rejected422केवल की सेव करते समय: वेबहुक URL ऊपर दी गई जांचों में विफल रहा। detail.reason बताता है कि कौन सा नियम — scheme, port, ip_literal, local_hostname, private_address, dns_error, credentials, इत्यादि।

502 का मतलब यह नहीं कि इनवॉइस नहीं बना — रिक्वेस्ट सफल हो सकती थी और जवाब वापसी के रास्ते में खो गया। उसी idempotency_key से दोबारा कोशिश करें और आपको या तो मौजूदा इनवॉइस मिलेगा या एक नया, कभी दो नहीं।

Checkout: what the buyer sees#

The hosted payment page, and when to build your own.

payment_url points at https://paysell.me/pay/{invoice_id}. One page, no account, no login, mobile first, and nothing for you to build.

On the page

  • Your shop's name, the amount and the coin, large, with the invoice's description underneath.
  • A countdown to expires_at — plus the 24-hour grace period when the invoice is underpaid.
  • The invoice's short id with a copy button, so a buyer can quote it to your support.
  • Wallet buttons: Tonkeeper and MyTonWallet open with the address and the amount already filled in. Other reveals a QR code and the address with a copy button.
  • A warning that only this invoice's coin, on the TON network, may be sent — anything else is lost.

How the page reacts

InvoiceWhat the buyer sees
pending“Waiting for payment”, with the wallet choices and the countdown. The page re-reads the invoice every five seconds.
underpaid“Received X of Y”, the exact remainder still owed, and the same address to send it to. The wallet link is prefilled with what is missing, not with the original total — otherwise the buyer would pay twice.
paid · overpaid“Payment received”, and a button back to your shop if the shop has a URL.
expired“Payment window closed”. If money did arrive, the amount is named with a note to contact you — silence here would send the buyer looking for their coins.
cancelled“Payment cancelled”, with a link back to your shop.

If you build your own

You gain your own branding and take on all of the above: the exact address string, the right coin, the countdown with its grace period, the underpayment case, and polling. GET /api/merchant/v1/public/invoices/{invoice_id} is the same unauthenticated read the hosted page uses — rate limited per IP, so poll it no more often than every few seconds. Print the address exactly as returned.

Typical integration mistakes#

The handful that account for most broken integrations.

None of these are exotic. Every one of them has cost somebody a day.

  • Sending the amount as a number

    {"amount": 5} is a 422. It has to be the string "5": JSON numbers are IEEE-754 doubles, and a large sum in nanotons stops being exactly representable in one.

  • Sending the smallest unit

    "5000000" for 5 USDT is a mistake in units, and the upper invoice limit exists to catch it. Smallest units are what comes back in webhooks, not what goes out in requests.

  • Treating the webhook's arrival as payment

    Read data.status. underpaid is not paid, and a deposit in a coin the invoice did not ask for never makes it paid either. Release the goods on paid or overpaid, on nothing else.

  • Not checking the timestamp

    A signature on its own never expires. Without the ±5 minute window on X-Paysell-Timestamp, a delivery captured once can be replayed at any time and will still verify.

  • Verifying the signature over re-serialised JSON

    Parse the body and serialise it again and the bytes change — key order, spacing — and the HMAC no longer matches. Sign the raw bytes exactly as received.

  • No deduplication

    The same event_id will arrive twice sooner or later: we retry until you answer 2xx, and a response lost on the way back looks like a failure from here. The second arrival must do nothing.

  • Using the Idempotency-Key header

    This API reads idempotency_key from the request body; the header is not read at all. A retry without the field opens a second invoice for the same order.

  • Assuming detail is always an object

    It is {code, message} for anything we or the core decide, and a list of field errors when the body itself fails validation. Check which one you got before reading detail.code.

रिफ़ंड#

ग्राहक को रिफ़ंड कैसे दें।

रिफ़ंड सपोर्ट के ज़रिए होते हैं, API कॉल से नहीं। रिफ़ंड का मतलब है उस पते पर एक नया ट्रांसफ़र जो किसी व्यक्ति ने बताया है, और जो पेमेंट प्रोसेसर API कॉल पर अपने आप पैसा वापस भेज देता है, उससे किसी हमलावर के पते पर भी पैसा भिजवाया जा सकता है। इसलिए यह जानबूझकर मैनुअल रखा गया है।

किसी खरीदार को रिफ़ंड देने के लिए, अपने अकाउंट एरिया से सपोर्ट टिकट खोलें और उसमें invoice_id या tx_hash, राशि, और जिस पते पर भेजना है वह दें। ऑपरेटर भुगतान की जांच करता है, आपके बैलेंस से पैसा निकालता है, और उसी टिकट में जवाब देता है। इसमें एक कार्य-दिवस लगने की उम्मीद रखें, एक मिनट की नहीं।

दो नतीजे ऐसे हैं जिन्हें ध्यान में रखकर डिज़ाइन करना चाहिए। अधिक भुगतान पूरा का पूरा आपको जमा होता है — हम उसमें से कुछ नहीं रखते — इसलिए जिस खरीदार ने ज़्यादा भेज दिया उसे अंतर लौटाना आपका फ़ैसला है, और वह इसी रास्ते से होता है। और कम भुगतान वाला इनवॉइस, जब तक खुला है, रिफ़ंड का मामला नहीं है: पैसा आपके बैलेंस पर है, पते पर अब भी नज़र रखी जा रही है, और खरीदार बस बाकी रकम भेज सकता है। फ़ैसला करने की नौबत तभी आती है जब छूट अवधि के बाद इनवॉइस पैसे समेत expired हो जाए।

टेस्टिंग#

लॉन्च से पहले अपने इंटीग्रेशन की जांच कैसे करें।

यहाँ की कीज़ लाइव हैं: जारी की गई हर की sk_live_ की है, जो प्रोडक्शन कोर और TON मेननेट के विरुद्ध चलती है। अलग से कोई टेस्ट एनवायरनमेंट नहीं है, और इसका एक फ़ायदा है: आप ठीक उसी रास्ते से गुज़रते हैं जिससे आपके असली ऑर्डर गुज़रेंगे।

इसलिए इसे वैसे ही टेस्ट करें जैसे असली पैसे को छूने वाली किसी भी चीज़ को करेंगे: छोटी रकम पर। न्यूनतम राशि (0.1 TON या 3 USDT) का इनवॉइस बनाएं, उसे अपने ही वॉलेट से चुकाएं, और पूरा रास्ता देखें — पेमेंट पेज, वेबहुक, हस्ताक्षर की जांच, और आपके ऑर्डर का paid में बदलना। फ़ीस लागू होती है, और कॉइन सचमुच हिलते हैं।

जिन हिस्सों को आप बिना कुछ खर्च किए आज़मा सकते हैं: इनवॉइस बनाना और पढ़ना, उसे रद्द करना, गलत रूप वाली राशि पर 422, गलत की पर 401, और आपका अपना हस्ताक्षर-सत्यापन — अपने सीक्रेट से एक नमूना बॉडी साइन करें और उसे अपने ही हैंडलर को खिलाएं। असली भुगतान वाकई सिर्फ़ आख़िरी चरण के लिए चाहिए: एक वास्तविक payment.credited वेबहुक।

इंटीग्रेशन की योजना ऐसी बनाएं कि वह सैंडबॉक्स या नकली भुगतान पर निर्भर न हो: लाइव रास्ता जल्दी — और ज़्यादा सच्चाई से — जांचा जाता है।

अपने पहले लाइव ऑर्डर को ही असली टेस्ट मानें: छोटी रकम चुनें, इनवॉइस को डैशबोर्ड में खुला रखें, और असली ग्राहकों को उधर भेजने से पहले भुगतान की पंक्ति और वेबहुक की स्थिति जांच लें।

For AI agents and LLMs#

Machine-readable copies of this page, and a prompt to start from.

Everything on this page also exists in a form a model can read directly. Point your assistant at one of these instead of pasting screenshots of documentation into a chat.

The three files

FileWhat it isUse it for
/llms-full.txtThe whole documentation as one markdown file: endpoints, fields, statuses, limits, errors, webhooks with working verification code, the fee, the checkout page, the checklist.Pasting into a model's context, or letting an agent fetch it. Start here.
/llms.txtA short index in the llms.txt format: what Paysell is, the five rules that decide whether an integration works, and links to everything else.Letting an agent discover the rest on its own.
/openapi.jsonOpenAPI 3.1, generated from the running application's own models, both webhook events included.Generating a client, or loading into anything that speaks OpenAPI.

A prompt to start from

Copy this, replace the stack, and hand it to your assistant. It names the four things that go wrong most often, so the answer does not have to be corrected afterwards.

prompt
Read https://paysell.me/llms-full.txt and implement Paysell payments in my <stack>:
create invoices (POST /api/merchant/v1/invoices, Bearer sk_live_ key, amount as a
decimal string in normal units), redirect the buyer to payment_url, verify webhook
signatures (HMAC-SHA256 over "{timestamp}.{raw_body}", header X-Paysell-Signature,
reject anything whose X-Paysell-Timestamp is more than 300 seconds off), deduplicate
by event_id, answer 2xx within 10 seconds, and mark orders paid only on a
payment.credited event whose data.status is "paid" or "overpaid".

Feeding it to a specific tool

  • Agents with web access — Claude Code, Cursor, Windsurf and the like: give them the /llms-full.txt link. One fetch, no setup.
  • A chat window — ChatGPT, Claude, Gemini: paste the contents of /llms-full.txt into the conversation or attach it as a file. It is written to fit in one message.
  • OpenAPI tooling — client generators, Postman, an agent's tool schema: point it at https://paysell.me/openapi.json. Its servers entry already carries the production base URL, so generated calls go to the right place.

गो-लाइव चेकलिस्ट#

शुरू करने से पहले दस बातें।

  • की केवल सर्वर-साइड है, कभी ब्राउज़र JavaScript में नहीं।
  • वेबहुक हस्ताक्षर "{timestamp}.{raw_body}" के विरुद्ध, नियत समय में सत्यापित होता है।
  • पाँच मिनट से पुरानी डिलीवरी अस्वीकार की जाती हैं, और सर्वर की घड़ी NTP पर है।
  • दोहराया गया X-Paysell-Event-Id दूसरी बार कुछ नहीं करता।
  • वेबहुक दस सेकंड के भीतर 2xx का जवाब देता है; धीमा काम उसके बाद होता है।
  • वेबहुक URL पोर्ट 443 पर एक https:// डोमेन है, और उसके आगे कोई रीडायरेक्ट नहीं है।
  • छूटा हुआ वेबहुक झेला जा सकता है: थैंक-यू पेज पर या मिलान-सफ़ाई के दौरान इनवॉइस एंडपॉइंट पढ़ा जाता है।
  • idempotency_key प्रति ऑर्डर एक बार जनरेट होती है और दोबारा कोशिशों में फिर से इस्तेमाल होती है।
  • राशियाँ सामान्य इकाइयों में स्ट्रिंग के रूप में जाती हैं; वेबहुक के अंक सबसे छोटी इकाई के रूप में पढ़े जाते हैं।
  • पता बिल्कुल वैसे ही दिखाया जाता है जैसे लौटाया गया, बिना बदलाव के।
  • overpaid और underpaid को संभाला जाता है, न सिर्फ़ paid को; expired में भी paid_minor हो सकता है।
  • माल status: paid या overpaid पर छोड़ा जाता है, केवल कॉलबैक आ जाने पर कभी नहीं।
  • 429 को Retry-After जितनी देर कहे उतना रुककर संभाला जाता है, तुरंत दोबारा कोशिश करके नहीं।
  • बैलेंस हमसे पढ़े जाते हैं, सत्य के रूप में अलग से ट्रैक नहीं किए जाते।

कुछ स्पष्ट नहीं है?

अगर इस पेज ने आपके सवाल का जवाब नहीं दिया, तो यह दस्तावेज़ीकरण में एक कमी है और इसे बताना उचित है। अपने अकाउंट एरिया से लिखें और हम पेज को ठीक करेंगे, सिर्फ जवाब नहीं।