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_ سابقہ صرف اُس ڈپلائمنٹ میں موجود ہے جو ٹیسٹ نیٹ ورک پر چلتی ہو، اور ایسی کوئی ڈپلائمنٹ پیش نہیں کی جاتی — دیکھیں ٹیسٹنگ۔ ہم ایک یک طرفہ ہیش محفوظ کرتے ہیں، خود کلید نہیں، اس لیے کوئی بھی، بشمول ہمارے، آپ کو یہ دوبارہ نہیں دکھا سکتا۔ کھو دی؟ نئی جاری کریں اور پرانی منسوخ کر دیں۔

دکان کلید سے اخذ کی جاتی ہے، اسی لیے کوئی درخواست کبھی دکان کی id نہیں لیتی۔ ایک کلید صرف اپنی دکان پر عمل کر سکتی ہے۔

پتے میں ورژن ہوتا ہے: /api/merchant/v1/…۔ ایک ورژن کے اندر ہم صرف فیلڈ شامل کرتے ہیں — کسی کا نام نہیں بدلتا اور نہ ہی خاموشی سے معنی بدلتے ہیں۔ جو تبدیلی آپ کا کوڈ توڑ دے گی، اسے نیا سابقہ /v2 ملتا ہے، اور /v1 اعلان کردہ مدت تک چلتا رہتا ہے۔

یہ کلید آپ کے نام سے انوائسز بناتی ہے۔ اسے سرور کی جانب رکھیں۔ براؤزر جاوا اسکرپٹ میں موجود کوئی بھی چیز عوامی ہے، چاہے وہ کتنی ہی اچھی طرح چھپی ہوئی کیوں نہ لگے۔

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 نہیں، تاکہ id کے وجود کی جانچ نہ کی جا سکے۔

انوائس منسوخ کریں#

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

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

ایسا انوائس بند کرتا ہے جو ابھی کھلا ہے — pending یا underpaid — اور اس کا پتہ آزاد کر دیتا ہے۔ اسے اس وقت استعمال کریں جب گاہک چیک آؤٹ چھوڑ دے: پتے ایک محدود وسیلہ ہیں، اور انہیں واپس کرنا پول کو صحت مند رکھتا ہے۔

جو انوائس اب کھلا نہیں رہا وہ 409 کا جواب دیتا ہے۔ کسی underpaid انوائس کو منسوخ کرنے سے کسی کو سکے واپس نہیں ملتے: جو رقم پہلے ہی جمع ہو چکی ہے وہ آپ کے بیلنس پر رہتی ہے، اور بند صرف یہ ہوتا ہے کہ اب مزید رقم قبول نہیں کی جائے گی۔

ویب ہکس#

کیا آتا ہے، اور اسے کیسے تصدیق کریں۔

کلید بناتے وقت ویب ہک کا پتہ مقرر کریں۔ جب ادائیگی جمع ہوتی ہے — اور جب اضافی جانچ کے لیے روکا گیا کوئی ڈپازٹ مسترد ہوتا ہے — تو ہم وہاں 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 منتقل کرنے کے لیے پہلے اسے اپنی جیب سے گیس کے ساتھ فنڈ کرنا ہوتا ہے۔ چند ڈالر سے کم پر فیس ہینڈلنگ کا احاطہ نہیں کرتی، اور ایسی ادائیگی قبول کرنے کا مطلب یہ ہوگا کہ آپ کو ایسی رقم جمع کی جائے جس کا منتقل کرنا غیر اقتصادی ہو۔

زیادہ سے زیادہ حد بڑے تاجروں کے بارے میں نہیں — یہ اکائیوں کی غلطی کے لیے بچھایا گیا جال ہے۔ "5000000" بھیج دیں جہاں آپ کا مطلب "5" تھا، تو بصورت دیگر آپ کو پچاس لاکھ ڈالر کا انوائس مل جاتا: خریدار مہمل رقم دیکھ کر چلا جاتا ہے۔ حقیقی آرڈر اس چھت تک کبھی نہیں پہنچتا؛ غلطی ہمیشہ پہنچتی ہے۔ دونوں چھتیں سیٹنگز ہیں (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ایسا کوئی انوائس نہیں، یا یہ کسی اور دکان کا ہے۔id چیک کریں۔ دونوں صورتوں کا جواب جان بوجھ کر یکساں ہے، تاکہ کسی id کو ٹٹولا نہ جا سکے۔
409انوائس ایسی حالت میں ہے جو اسے منع کرتی ہے۔پہلے اس کا موجودہ اسٹیٹس پڑھیں۔
422درخواست کی ساخت غلط ہے، یا رقم انوائس کی حدود سے باہر ہے۔پیغام میں بھیجی گئی ویلیو اور حد دونوں کا نام لیا گیا ہے۔
429اس گھنٹے میں بہت زیادہ انوائسز، بیک وقت بہت زیادہ کھلے، یا بہت زیادہ درخواستیں۔Retry-After کا انتظار پورا کریں، پھر دوبارہ کوشش کریں۔
502ہم پروسیسنگ کور تک نہیں پہنچ سکے۔اسی idempotency key کے ساتھ دوبارہ کوشش کریں۔

کوڈز

ہمارے طے کردہ فیصلوں کی شکل یہ ہے: {"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)، اسے اپنے والٹ سے ادا کریں، اور پورا راستہ دیکھیں — ادائیگی کا صفحہ، ویب ہک، دستخط کی جانچ، اور آپ کے آرڈر کا ادا شدہ ہو جانا۔ فیس لگتی ہے، اور سکے واقعی منتقل ہوتے ہیں۔

وہ حصے جنہیں آپ بغیر کچھ خرچ کیے آزما سکتے ہیں: انوائس بنانا اور پڑھنا، اسے منسوخ کرنا، غلط ساخت والی رقم پر 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.

لانچ سے پہلے چیک لسٹ#

آغاز سے پہلے دس باتیں۔

  • کلید صرف سرور کی جانب ہے، کبھی براؤزر جاوا اسکرپٹ میں نہیں۔
  • ویب ہک دستخط کی تصدیق "{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 کا انتظار پورا کر کے ہینڈل کیا جاتا ہے، فوراً دوبارہ کوشش کر کے نہیں۔
  • بیلنس ہم سے پڑھے جاتے ہیں، سچائی کے طور پر الگ سے ٹریک نہیں کیے جاتے۔

کچھ غیر واضح ہے؟

اگر اس صفحے نے آپ کے سوال کا جواب نہیں دیا، تو یہ دستاویزات میں ایک خلا ہے اور ہمیں اس کے بارے میں بتانا ضروری ہے۔ اپنے اکاؤنٹ ایریا سے لکھیں اور ہم صرف جواب نہیں بلکہ صفحہ ہی درست کریں گے۔