# Payout API

> अपने merchant बैलेंस से किसी भी blockchain address पर निकासी भेजें।

Payout API आपको अपने merchant बैलेंस से किसी भी blockchain address पर programmatically फंड निकालने की सुविधा देता है।

> **WARNING:** सभी Payout endpoints के लिए, आपको `sign` सिग्नेचर generate करने के लिए एक अलग **Payout API key** का उपयोग करना होगा। यह key आपकी सामान्य API key से अलग है और इसे आपके project settings में generate किया जाना चाहिए।

## निकासी बनाएँ

आपके merchant बैलेंस से निकासी request बनाता है।

`POST /v1/payout`

### Request parameters

| Field | Type | आवश्यक | Description |
|-------|------|----------|-------------|
| `currency` | string | हाँ | निकासी currency (देखें [References](/docs/references)) |
| `network` | string | हाँ | Network code (देखें [References](/docs/references)) |
| `amount` | string | हाँ | निकासी राशि |
| `to_address` | string | हाँ | प्राप्तकर्ता का blockchain address |
| `order_id` | string | नहीं | **Idempotency key** — project के भीतर unique। समान `order_id` के साथ दोहराए गए `POST` से नया payout नहीं बनता — मौजूदा payout वापस लौटाया जाता है |
| `url_callback` | string | नहीं | payout webhook के लिए URL। इस payout के लिए webhook बंद करने हेतु इसे न दें |
| `memo` | string \| null | नहीं | Destination tag / memo. वर्तमान में केवल **TON** और **SOL** networks द्वारा उपयोग किया जाता है; अधिकतम 255 chars |
| `from_currency` | string | नहीं | Source बैलेंस जिसे payout के समय debit करके स्वतः ही `currency` में convert किया जाएगा। आप अपने बैलेंस को `USDT` जैसी stablecoin में रखते हुए volatile assets (`BTC`, `ETH`, …) में payout कर सकते हैं — आपको खुद volatile crypto रखने की जरूरत नहीं है। USDT बैलेंस से debit करने के लिए `"USDT"` pass करें |
| `fee_option` | string | नहीं | Fees कैसे लगाए जाते हैं। `deduct` (default) — network + platform fees `amount` से घटाए जाते हैं, प्राप्तकर्ता को `amount - fees` मिलता है। `add` — fees ऊपर से जोड़े जाते हैं, merchant से `amount + fees` debit किया जाता है, प्राप्तकर्ता को ठीक `amount` मिलता है |

> **INFO:** **Idempotency.** Project के भीतर, एक payout `order_id` से unique होता है। समान `order_id` के साथ वही `POST` दोबारा भेजना **सुरक्षित** है — API duplicate बनाने के बजाय मौजूदा payout लौटाता है। Production payout के लिए हमेशा एक `order_id` pass करें।

### Request examples

<CodeTabs langs="curl,php,js,python,go">

```bash
curl -X POST https://api.2328.io/api/v1/payout \
  -H "Content-Type: application/json" \
  -H "User-Agent: MyShop/1.0 (+https://myshop.example)" \
  -H "project: YOUR_PROJECT_UUID" \
  -H "sign: YOUR_HMAC_SIGNATURE" \
  -d '{"currency":"TRX","network":"TRX-TRC20","amount":"1.00","to_address":"TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t","order_id":"9ed25264-8be4-439f-acf5-2a8732538d27","url_callback":"https://your-site.com/webhook/payout","memo":null,"fee_option":"deduct"}'
```

```php
<?php
function apiSign(string $body, string $apiKey): string {
    return hash_hmac('sha256', base64_encode($body), $apiKey);
}

$project = 'YOUR_PROJECT_UUID';
$apiKey  = 'YOUR_PAYOUT_API_KEY';

$data = [
    'currency'     => 'TRX',
    'network'      => 'TRX-TRC20',
    'amount'       => '1.00',
    'to_address'   => 'TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t',
    'order_id'     => '9ed25264-8be4-439f-acf5-2a8732538d27',
    'url_callback' => 'https://your-site.com/webhook/payout',
    'memo'         => null,
    'fee_option'   => 'deduct',
];

$body = json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
$sign = apiSign($body, $apiKey);

$ch = curl_init('https://api.2328.io/api/v1/payout');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST           => true,
    CURLOPT_POSTFIELDS     => $body,
    CURLOPT_HTTPHEADER     => [
        'Content-Type: application/json',
        'User-Agent: MyShop/1.0 (+https://myshop.example)',
        "project: $project",
        "sign: $sign",
    ],
]);
$response = json_decode(curl_exec($ch), true);
```

```javascript
import { createHmac } from "crypto";

function apiSign(body, apiKey) {
  const base64 = Buffer.from(body, "utf8").toString("base64");
  return createHmac("sha256", apiKey).update(base64).digest("hex");
}

const PROJECT_UUID    = "YOUR_PROJECT_UUID";
const PAYOUT_API_KEY  = process.env.PAYOUT_API_KEY;

const data = {
  currency:     "TRX",
  network:      "TRX-TRC20",
  amount:       "1.00",
  to_address:   "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t",
  order_id:     "9ed25264-8be4-439f-acf5-2a8732538d27",
  url_callback: "https://your-site.com/webhook/payout",
  memo:         null,
  fee_option:   "deduct",
};

const body = JSON.stringify(data);
const sign = apiSign(body, PAYOUT_API_KEY);

const res = await fetch("https://api.2328.io/api/v1/payout", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "User-Agent":   "MyShop/1.0 (+https://myshop.example)",
    project:        PROJECT_UUID,
    sign,
  },
  body,
});
const json = await res.json();
```

```python
import json
import hmac
import hashlib
import base64
import httpx


def api_sign(body: str, api_key: str) -> str:
    b64 = base64.b64encode(body.encode("utf-8")).decode()
    return hmac.new(api_key.encode(), b64.encode(), hashlib.sha256).hexdigest()


PROJECT_UUID    = "YOUR_PROJECT_UUID"
PAYOUT_API_KEY  = "YOUR_PAYOUT_API_KEY"

data = {
    "currency":     "TRX",
    "network":      "TRX-TRC20",
    "amount":       "1.00",
    "to_address":   "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t",
    "order_id":     "9ed25264-8be4-439f-acf5-2a8732538d27",
    "url_callback": "https://your-site.com/webhook/payout",
    "memo":         None,
    "fee_option":   "deduct",
}

body = json.dumps(data, separators=(",", ":"), ensure_ascii=False)
sign = api_sign(body, PAYOUT_API_KEY)

r = httpx.post(
    "https://api.2328.io/api/v1/payout",
    headers={
        "Content-Type": "application/json",
        "User-Agent":   "MyShop/1.0 (+https://myshop.example)",
        "project":      PROJECT_UUID,
        "sign":         sign,
    },
    content=body.encode("utf-8"),
)
response = r.json()
```

```go
package main

import (
    "bytes"
    "crypto/hmac"
    "crypto/sha256"
    "encoding/base64"
    "encoding/hex"
    "encoding/json"
    "net/http"
)

func apiSign(body []byte, apiKey string) string {
    b64 := base64.StdEncoding.EncodeToString(body)
    h := hmac.New(sha256.New, []byte(apiKey))
    h.Write([]byte(b64))
    return hex.EncodeToString(h.Sum(nil))
}

func marshalCanonical(v any) ([]byte, error) {
    var buf bytes.Buffer
    enc := json.NewEncoder(&buf)
    enc.SetEscapeHTML(false)
    if err := enc.Encode(v); err != nil {
        return nil, err
    }
    return bytes.TrimRight(buf.Bytes(), "\n"), nil
}

type CreatePayout struct {
    Currency    string  `json:"currency"`
    Network     string  `json:"network"`
    Amount      string  `json:"amount"`
    ToAddress   string  `json:"to_address"`
    OrderID     string  `json:"order_id"`
    URLCallback string  `json:"url_callback"`
    Memo        *string `json:"memo"`
    FeeOption   string  `json:"fee_option"`
}

func main() {
    const projectUUID   = "YOUR_PROJECT_UUID"
    const payoutAPIKey  = "YOUR_PAYOUT_API_KEY"

    data := CreatePayout{
        Currency:    "TRX",
        Network:     "TRX-TRC20",
        Amount:      "1.00",
        ToAddress:   "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t",
        OrderID:     "9ed25264-8be4-439f-acf5-2a8732538d27",
        URLCallback: "https://your-site.com/webhook/payout",
        Memo:        nil,
        FeeOption:   "deduct",
    }

    body, err := marshalCanonical(data)
    if err != nil {
        panic(err)
    }
    sign := apiSign(body, payoutAPIKey)

    req, _ := http.NewRequest("POST",
        "https://api.2328.io/api/v1/payout",
        bytes.NewReader(body))
    req.Header.Set("Content-Type", "application/json")
    req.Header.Set("User-Agent", "MyShop/1.0 (+https://myshop.example)")
    req.Header.Set("project", projectUUID)
    req.Header.Set("sign", sign)

    resp, err := http.DefaultClient.Do(req)
    if err != nil {
        panic(err)
    }
    defer resp.Body.Close()
}
```

</CodeTabs>

### Response example

```json
{
  "state": 0,
  "result": {
    "uuid": "019dea62-1727-72aa-ac2c-eaf2ade193ef",
    "order_id": "9ed25264-8be4-439f-acf5-2a8732538d27",
    "status": "pending",
    "currency": "TRX",
    "network": "TRX-TRC20",
    "amount": "1.00",
    "merchant_amount": "1",
    "network_amount": "0.89",
    "amount_usd": "0.33",
    "to_address": "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t",
    "memo": null,
    "txid": null,
    "block_number": null,
    "error_type": null,
    "created_at": "2026-05-02T23:29:50+03:00",
    "updated_at": "2026-05-02T23:29:50+03:00"
  }
}
```

> **INFO:** **Fees.** Default `fee_option: deduct` — network + platform fees `amount` से घटाए जाते हैं (प्राप्तकर्ता को `amount - fees` मिलता है)। ऊपर से fees लगाने के लिए `fee_option: add` pass करें — प्राप्तकर्ता को ठीक `amount` मिलता है और merchant से `amount + fees` debit किया जाता है।

## Payout calculate करें

**Payout बनाए बिना** और balance debit किए बिना withdrawal amount और fees का estimate देता है। User को confirm करने से पहले उन्हें मिलने (या pay करने) वाली exact amount दिखाने के लिए इसका इस्तेमाल करें।

`POST /v1/payout/calc`

### Request parameters

[Create payout](#create-payout) से identical — same fields, same signing। `order_id`, `url_callback`, `to_address` और `memo` accept तो होते हैं लेकिन ignore किए जाते हैं: कोई payout persist नहीं होता और कोई callback नहीं भेजा जाता।

### Request example

```bash
curl -X POST https://api.2328.io/api/v1/payout/calc \
  -H "Content-Type: application/json" \
  -H "User-Agent: MyShop/1.0 (+https://myshop.example)" \
  -H "project: YOUR_PROJECT_UUID" \
  -H "sign: YOUR_HMAC_SIGNATURE" \
  -d '{"currency":"USDT","network":"TRX-TRC20","amount":"100","fee_option":"add"}'
```

### Response example

```json
{
  "state": 0,
  "result": {
    "currency": "USDT",
    "network": "TRX-TRC20",
    "amount": "100",
    "fee_option": "add",
    "merchant_amount": "103.00000000",
    "network_amount": "100",
    "total_fee": "3.00000000",
    "total_fee_usd": "3.00000000"
  }
}
```

> **INFO:** **Preview only.** यह endpoint read-only है — कोई balance debit नहीं होता और कोई payout record नहीं बनता। UI में fee breakdown दिखाने के लिए जितनी बार जरूरत हो call कर सकते हैं।

## Payout status

Payout request का status प्राप्त करें।

`GET /v1/payout/status/{uuid}`

### Path parameters

| Field | Type | आवश्यक | Description |
|-------|------|----------|-------------|
| `uuid` | string | हाँ | Payout UUID (creation पर `result.uuid` से) |

### Response example

```json
{
  "state": 0,
  "result": {
    "uuid": "019dff1f-0dbd-7277-8d45-271e7775388f",
    "order_id": "4dfdcc84402b1185b71cbe399321533e",
    "status": "completed",
    "currency": "TRX",
    "network": "TRX-TRC20",
    "amount": "3.00",
    "merchant_amount": "3.00",
    "network_amount": "3.00",
    "amount_usd": "1.04",
    "to_address": "THauRv5tcucQRohXg8NiyGTk16DX1XQG5x",
    "memo": null,
    "txid": "9242e533703704ef3eaba840f70b4a26333e72c943377ee375fea17badb53def",
    "block_number": null,
    "error_type": null,
    "created_at": "2026-05-07T00:08:38+03:00",
    "updated_at": "2026-05-07T00:08:54+03:00",
    "from_currency": "USDT",
    "debited_amount": "1.050735",
    "debited_currency": "USDT"
  }
}
```

> **INFO:** इस GET request के लिए सिग्नेचर खाली body से compute होता है:
`hash_hmac('sha256', base64_encode(''), $apiKey)`

## Response fields

`POST /v1/payout` और `GET /v1/payout/status/{uuid}` के `result` में लौटाए गए fields:

| Field | Type | Description |
|-------|------|-------------|
| `uuid` | string | सिस्टम द्वारा assigned Payout UUID |
| `order_id` | string | आपका आंतरिक payout identifier (project के भीतर unique) |
| `status` | string | वर्तमान payout status (नीचे देखें) |
| `currency` | string | निकासी currency |
| `network` | string | Network code |
| `amount` | string | requested निकासी राशि |
| `merchant_amount` | string | Merchant बैलेंस से debit की गई राशि |
| `network_amount` | string | वास्तव में on-chain भेजी गई राशि (network + platform fees के बाद) |
| `amount_usd` | string | निकासी राशि का USD equivalent |
| `to_address` | string | प्राप्तकर्ता का blockchain address |
| `memo` | string \| null | Destination tag / memo (TON, SOL)। अन्यथा `null` |
| `txid` | string \| null | Blockchain transaction hash. transaction भेजे जाने तक `null` |
| `block_number` | int \| null | Block number जहाँ transaction included था। शामिल होने तक `null` |
| `error_type` | string \| null | `status = failed` होने पर failure का कारण (नीचे Error types देखें)। अन्यथा `null` |
| `created_at` | string (ISO 8601) | Payout creation का समय |
| `updated_at` | string (ISO 8601) | अंतिम status परिवर्तन का समय |
| `from_currency` | string \| null | Auto-conversion का उपयोग होने पर वह source बैलेंस जिससे payout debit हुआ था (जैसे `BTC` payout के लिए `USDT`)। यदि कोई conversion नहीं हुआ तो `null` |
| `debited_amount` | string \| null | Conversion के बाद source बैलेंस से वास्तव में debit की गई राशि। केवल auto-conversion के उपयोग पर उपस्थित |
| `debited_currency` | string \| null | `debited_amount` की currency — वह बैलेंस जहाँ से फंड debit किए गए |

## Payout statuses

`status` field निम्न values ले सकता है:

| Status | Description |
|--------|-------------|
| `pending` | बनाया गया, processing की प्रतीक्षा में |
| `completed` | सफलतापूर्वक पूर्ण — `txid` set है |
| `failed` | भेजने में त्रुटि — `error_type` देखें |
| `cancelled` | रद्द कर दिया गया |

## Error types

जब `status = failed` होता है, तो `error_type` field कारण बताता है:

| Code | Description |
|------|-------------|
| `aml_risk` | AML risk checks द्वारा payout block (प्राप्तकर्ता address को high-risk के रूप में flag किया गया) |

## Webhook notifications

जब किसी payout का status बदलता है, तो सिस्टम payout बनाते समय pass किए गए `url_callback` URL पर एक `POST` webhook भेजता है। यदि `url_callback` प्रदान नहीं किया गया था, तो उस payout के लिए कोई webhook नहीं भेजा जाता।

- **Method:** `POST`
- **Content-Type:** `application/json`
- **Signature:** request body में `sign` field, **Payout API key** के साथ compute किया गया (वही key जो payout request को sign करने के लिए उपयोग की जाती है)।

Payload `GET /v1/payout/status/{uuid}` के `result` object को mirror करता है, साथ में verification के लिए एक `sign` field होता है।

### Payload

```json
{
  "uuid": "019dff1f-0dbd-7277-8d45-271e7775388f",
  "order_id": "4dfdcc84402b1185b71cbe399321533e",
  "status": "completed",
  "currency": "TRX",
  "network": "TRX-TRC20",
  "amount": "3.00",
  "merchant_amount": "3.00",
  "network_amount": "3.00",
  "amount_usd": "1.04",
  "to_address": "THauRv5tcucQRohXg8NiyGTk16DX1XQG5x",
  "memo": null,
  "txid": "9242e533703704ef3eaba840f70b4a26333e72c943377ee375fea17badb53def",
  "block_number": null,
  "error_type": null,
  "created_at": "2026-05-07T00:08:38+03:00",
  "updated_at": "2026-05-07T00:08:54+03:00",
  "from_currency": "USDT",
  "debited_amount": "1.050735",
  "debited_currency": "USDT",
  "sign": "925ad7bf3d6841864101f7cc2c7e30652e70a06cdb04dbe07a0129480000ce4a"
}
```

> **WARNING:** **सिग्नेचर verify करना.** [payment webhooks](/docs/webhooks) के लिए वही algorithm उपयोग करें, लेकिन सामान्य API key के बजाय अपनी **Payout API key** से sign करें। `sign` field हटा दें, बाकी payload को JSON-encode करें, Base64-encode करें, फिर `hash_hmac('sha256', $base64, $payoutApiKey)` compute करें और प्राप्त `sign` से तुलना करें।