{"openapi":"3.1.0","info":{"title":"Nour Wallet Payments API","description":"\n## Payment flow\n\nCreate a payment, send its `checkout_url` to the customer, then receive the\nresult using either:\n\n1. **Webhook (recommended):** Nour Wallet pushes `payment.paid` or\n   `payment.cancelled` to the merchant service.\n2. **HTTP polling (fallback):** call `GET /v1/payments/{payment_id}` every 3-5\n   seconds. Polling needs no public incoming endpoint.\n\nPayment statuses:\n\n| Status | Meaning |\n|---|---|\n| `pending` | Waiting for customer confirmation |\n| `awaiting_deposit` | Customer confirmed but needs more available balance |\n| `paid` | Payment completed |\n| `cancelled` | Customer cancelled |\n| `expired` | Checkout lifetime ended |\n\n## API fee and settlement\n\nThe customer is charged exactly `amount`. The API fee is deducted from the\nmerchant's proceeds:\n\n`merchant_net_amount = amount - fee_amount`\n\nThe response exposes `fee_percent`, `fee_amount`, `total_amount` (the amount\npaid by the customer), and `merchant_net_amount`. The fee rate is snapshotted\nwhen the checkout is created, so changing the admin setting never changes an\nexisting checkout.\n\n## Python example: create and poll\n\n<pre><code class=\"language-python\">import asyncio&#10;import httpx&#10;&#10;API_URL = &quot;https://nwallet.online&quot;&#10;API_KEY = &quot;nw_live_...&quot;&#10;&#10;async def create_and_wait():&#10;    headers = {&quot;Authorization&quot;: f&quot;Bearer {API_KEY}&quot;}&#10;    async with httpx.AsyncClient(timeout=15) as client:&#10;        response = await client.post(&#10;            f&quot;{API_URL}/v1/payments&quot;,&#10;            headers=headers,&#10;            json={&#10;                &quot;external_id&quot;: &quot;order-20260809-1007&quot;,&#10;                &quot;amount&quot;: &quot;10.00&quot;,&#10;                &quot;description&quot;: &quot;Order 1007&quot;,&#10;                &quot;metadata&quot;: {&quot;customer_id&quot;: 42},&#10;                &quot;expires_in_minutes&quot;: 30,&#10;            },&#10;        )&#10;        response.raise_for_status()&#10;        payment = response.json()&#10;        print(&quot;Send this link to the customer:&quot;, payment[&quot;checkout_url&quot;])&#10;&#10;        while True:&#10;            response = await client.get(&#10;                f&quot;{API_URL}/v1/payments/{payment[&#x27;id&#x27;]}&quot;,&#10;                headers=headers,&#10;            )&#10;            response.raise_for_status()&#10;            payment = response.json()&#10;            if payment[&quot;status&quot;] in {&quot;paid&quot;, &quot;cancelled&quot;, &quot;expired&quot;}:&#10;                return payment&#10;            await asyncio.sleep(5)&#10;&#10;result = asyncio.run(create_and_wait())&#10;print(result[&quot;status&quot;], result[&quot;id&quot;])</code></pre>\n\n## API Key vs Webhook Secret\n\n- **API Key (`nw_live_...`)** authenticates outgoing requests from the merchant\n  bot to Nour Wallet, such as creating a payment or polling its status.\n- **Webhook Secret (`whsec_...`)** verifies incoming webhook signatures. It\n  proves that `payment.paid` or `payment.cancelled` was sent by Nour Wallet and\n  that the exact request body was not modified.\n- A polling-only integration does not need to use the Webhook Secret.\n\nAlways compute the HMAC from the exact raw HTTP body before parsing or changing\nthe JSON, and compare signatures with `hmac.compare_digest`.\n\n### Python example: verify a webhook\n\n<pre><code class=\"language-python\">import hashlib&#10;import hmac&#10;from fastapi import FastAPI, Header, HTTPException, Request&#10;&#10;app = FastAPI()&#10;WEBHOOK_SECRET = &quot;whsec_...&quot;&#10;&#10;@app.post(&quot;/webhooks/nour&quot;)&#10;async def nour_webhook(&#10;    request: Request,&#10;    x_nour_signature: str = Header(),&#10;):&#10;    raw_body = await request.body()&#10;    expected = &quot;sha256=&quot; + hmac.new(&#10;        WEBHOOK_SECRET.encode(),&#10;        raw_body,&#10;        hashlib.sha256,&#10;    ).hexdigest()&#10;&#10;    if not hmac.compare_digest(expected, x_nour_signature):&#10;        raise HTTPException(401, &quot;Invalid signature&quot;)&#10;&#10;    event = await request.json()&#10;    if event[&quot;type&quot;] == &quot;payment.paid&quot;:&#10;        # Deliver the product or activate the service exactly once.&#10;        pass&#10;    elif event[&quot;type&quot;] == &quot;payment.cancelled&quot;:&#10;        # Cancel the merchant order.&#10;        pass&#10;    return {&quot;received&quot;: True}</code></pre>\n\nClick **Authorize** and enter only the key beginning with `nw_live_`; Swagger\nadds the `Bearer` prefix automatically.\n","version":"2.0.0"},"servers":[{"url":"https://nwallet.online","description":"Production server"}],"paths":{"/health":{"get":{"summary":"Health","operationId":"health_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/v1/payments":{"post":{"tags":["Payments"],"summary":"Create a Telegram payment checkout","description":"Creates an idempotent payment and returns `checkout_url`. Send that URL to the customer. Each `external_id` must identify exactly one order.","operationId":"create_payment_v1_payments_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreatePayment"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreatePaymentResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"Nour Wallet API Key":[]}]}},"/v1/payments/{payment_id}":{"get":{"tags":["Payments"],"summary":"Get payment status (HTTP polling)","description":"Polling fallback for merchants that cannot receive webhooks. Call every 3-5 seconds and stop when status is `paid`, `cancelled`, or `expired`.","operationId":"get_payment_v1_payments__payment_id__get","security":[{"Nour Wallet API Key":[]}],"parameters":[{"name":"payment_id","in":"path","required":true,"schema":{"type":"string","title":"Payment Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PaymentResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}}},"components":{"schemas":{"CreatePayment":{"properties":{"external_id":{"type":"string","maxLength":128,"minLength":1,"title":"External Id"},"amount":{"anyOf":[{"type":"number","exclusiveMinimum":0.0},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*(?:\\d{0,12}|(?=[\\d.]{1,21}0*$)\\d{0,12}\\.\\d{0,8}0*$)"}],"title":"Amount"},"description":{"anyOf":[{"type":"string","maxLength":500},{"type":"null"}],"title":"Description"},"metadata":{"additionalProperties":true,"type":"object","title":"Metadata"},"expires_in_minutes":{"type":"integer","maximum":1440.0,"minimum":5.0,"title":"Expires In Minutes","default":30}},"type":"object","required":["external_id","amount"],"title":"CreatePayment","examples":[{"amount":"10.00","description":"Order 1007","expires_in_minutes":30,"external_id":"order-20260808-1007","metadata":{"customer_id":42}}]},"CreatePaymentResponse":{"properties":{"id":{"type":"string","title":"Id","description":"Nour Wallet payment identifier"},"external_id":{"type":"string","title":"External Id","description":"Merchant's unique order identifier"},"amount":{"type":"string","title":"Amount","description":"Gross order amount paid by the customer"},"currency":{"type":"string","title":"Currency","default":"USDT"},"status":{"type":"string","title":"Status","description":"pending, awaiting_deposit, paid, cancelled, or expired"},"tx_hash":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tx Hash"},"paid_amount":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Paid Amount","description":"Amount paid by the customer"},"fee_percent":{"type":"string","title":"Fee Percent","description":"Snapshotted API fee percentage"},"fee_amount":{"type":"string","title":"Fee Amount","description":"API fee deducted from the merchant"},"total_amount":{"type":"string","title":"Total Amount","description":"Total charged to the customer; equals amount"},"merchant_net_amount":{"type":"string","title":"Merchant Net Amount","description":"Net amount credited to the merchant"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"metadata":{"additionalProperties":true,"type":"object","title":"Metadata"},"created_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created At"},"expires_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Expires At"},"paid_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Paid At"},"payer_user_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Payer User Id"},"checkout_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Checkout Url"},"created":{"type":"boolean","title":"Created","description":"True when created; false for an idempotent replay"}},"type":"object","required":["id","external_id","amount","status","fee_percent","fee_amount","total_amount","merchant_net_amount","created"],"title":"CreatePaymentResponse","examples":[{"amount":"100.00","checkout_url":"https://t.me/nour_wallet_bot?start=pay_token","created_at":"2026-08-10T12:00:00+00:00","currency":"USDT","description":"Order 1007","expires_at":"2026-08-10T12:30:00+00:00","external_id":"order-20260808-1007","fee_amount":"0.10000000","fee_percent":"0.1","id":"pay_8f2c1a9e4b7d","merchant_net_amount":"99.90000000","metadata":{"customer_id":42},"status":"pending","total_amount":"100.00"}]},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"PaymentResponse":{"properties":{"id":{"type":"string","title":"Id","description":"Nour Wallet payment identifier"},"external_id":{"type":"string","title":"External Id","description":"Merchant's unique order identifier"},"amount":{"type":"string","title":"Amount","description":"Gross order amount paid by the customer"},"currency":{"type":"string","title":"Currency","default":"USDT"},"status":{"type":"string","title":"Status","description":"pending, awaiting_deposit, paid, cancelled, or expired"},"tx_hash":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tx Hash"},"paid_amount":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Paid Amount","description":"Amount paid by the customer"},"fee_percent":{"type":"string","title":"Fee Percent","description":"Snapshotted API fee percentage"},"fee_amount":{"type":"string","title":"Fee Amount","description":"API fee deducted from the merchant"},"total_amount":{"type":"string","title":"Total Amount","description":"Total charged to the customer; equals amount"},"merchant_net_amount":{"type":"string","title":"Merchant Net Amount","description":"Net amount credited to the merchant"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"metadata":{"additionalProperties":true,"type":"object","title":"Metadata"},"created_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created At"},"expires_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Expires At"},"paid_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Paid At"},"payer_user_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Payer User Id"},"checkout_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Checkout Url"}},"type":"object","required":["id","external_id","amount","status","fee_percent","fee_amount","total_amount","merchant_net_amount"],"title":"PaymentResponse","examples":[{"amount":"100.00","checkout_url":"https://t.me/nour_wallet_bot?start=pay_token","created_at":"2026-08-10T12:00:00+00:00","currency":"USDT","description":"Order 1007","expires_at":"2026-08-10T12:30:00+00:00","external_id":"order-20260808-1007","fee_amount":"0.10000000","fee_percent":"0.1","id":"pay_8f2c1a9e4b7d","merchant_net_amount":"99.90000000","metadata":{"customer_id":42},"status":"pending","total_amount":"100.00"}]},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"}},"securitySchemes":{"Nour Wallet API Key":{"type":"http","description":"Enter the merchant API key. Swagger adds the Bearer prefix automatically.","scheme":"bearer","bearerFormat":"nw_live_..."}}},"tags":[{"name":"Payments","description":"Create a Telegram checkout and retrieve its status. Use the GET endpoint for HTTP polling when webhooks are unavailable."}]}