> For the complete documentation index, see [llms.txt](https://docs-sdk.crypto-chief.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs-sdk.crypto-chief.com/processing/go/guides/payouts.md).

# Send a payout

Send single crypto payouts in Go — estimate, execute idempotently, and wait for confirmation.

A payout sends crypto from your project balance to any address. The flow is **estimate** (optional) → **execute** → **wait for confirmation** (optional).

## Estimate

Preview the network fee and the net amount the recipient receives before sending:

```go
est, err := c.Payouts.Estimate(ctx, &cryptochief.EstimatePayoutRequest{
    Network:   cryptochief.ChainEthMainnet,
    Coin:      "USDT",
    Amount:    "25.0",
    ToAddress: "0xRecipient...",
})
// est.AmountToReceive, est.FeeInfo.EstimatedFiat, est.Sources
```

{% hint style="warning" %}
**On TRON, the fee fields are not the whole cost.** `FeeInfo.EstimatedFiat` — and the `total_fee_paid_fiat` that turns up in the payout webhook — cover **on-chain gas only**. Energy rented for a TRON transfer is billed to your API credits instead, under every fee mode, so you will see a credits charge that no fee field accounts for. It is one cost billed once, not a double charge: reconcile the rental from your credits ledger. A sweep is the opposite case — there the rental *is* inside `TotalFeeUSD`. See [Auto-sweep settings](/processing/go/guides/auto-sweep-settings.md#tron-what-the-transfer-is-paid-with).
{% endhint %}

## Execute

```go
payout, err := c.Payouts.Execute(ctx, &cryptochief.ExecutePayoutRequest{
    OrderID:     "order-42",   // idempotency key — safe to retry
    UserID:      "u-7",
    Network:     cryptochief.ChainEthMainnet,
    Coin:        "USDT",
    Amount:      "25.0",
    ToAddress:   "0xRecipient...",
    URLCallback: "https://your.app/webhooks/payout",
})
```

{% hint style="info" %}
`OrderID` is an **idempotency key**: re-submitting the same `order_id` returns the same payout instead of creating a second one. That is what makes the SDK's automatic retries safe.
{% endhint %}

## Wait for confirmation

```go
final, err := cryptochief.WaitForPayout(ctx, c, payout.UUID, cryptochief.PollOptions{})
if err != nil {
    return err
}
if final.Succeeded() {
    for _, s := range final.Sources {
        log.Printf("paid: source=%s tx=%s", s.Address, s.TxID)
    }
}
```

Or react to the `payout.*` [webhook](/processing/go/guides/webhooks.md) instead of polling.

### Confirmations

A payout stays `confirm_check` until the transaction of every source reaches `RequiredConfirmations`. Then it becomes `paid` and `payout.paid` is sent. The default `WaitForPayout` timeout is 90 minutes. A timeout is not a failure: the last state is returned with the error, so read its `Status` and do not resubmit the payout.

```go
p, err := c.Payouts.Info(ctx, payout.UUID)
if err != nil {
    return err
}
if p.Confirmations != nil {
    log.Printf("%s: %d of %d", p.Status, *p.Confirmations, p.RequiredConfirmations)
}
for _, src := range p.Sources {
    if src.Confirmations == nil {
        continue // not on chain yet
    }
    log.Printf("%s: %d", src.Address, *src.Confirmations)
}
```

| Field                               | Type   | Meaning                                                                                                                                                                                                     |
| ----------------------------------- | ------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `Sources[].Confirmations`           | `*int` | Confirmations of the source's transaction; `nil` until it is on chain.                                                                                                                                      |
| `ServiceOperations[].Confirmations` | `*int` | Confirmations of a service transaction, such as a gas top-up, recorded once when it is included in a block; not updated afterwards. The payout waits for the top-up to be included, not for finality depth. |
| `Confirmations`                     | `*int` | The lowest count among the sources; `nil` while no source has a transaction.                                                                                                                                |
| `RequiredConfirmations`             | `int`  | The network's finality depth, at least `1`. Optional; `0` when not sent.                                                                                                                                    |

The first `Execute` response has no counts yet. Decide on `Status`, not on the counts. Withdrawals follow the same rule — see [Manual withdrawals](/processing/go/guides/withdrawals.md#confirmations).

## Pay out in a different asset (swap)

{% hint style="warning" %}
**Not available yet.** The payout request carries an `auto_convert` field and this SDK exposes it, but the platform refuses any payout that sets it, answering `AUTO_CONVERT_NOT_IMPLEMENTED`. `auto_convert_policy` is reserved alongside it. Convert the asset yourself and pay out what the master wallet already holds.
{% endhint %}

## Handle errors

```go
_, err := c.Payouts.Execute(ctx, req)
var apiErr *cryptochief.APIError
if errors.As(err, &apiErr) {
    switch apiErr.Code {
    case cryptochief.CodeInsufficientFunds:    // top up and retry
    case cryptochief.CodeAssetNotEnabled:      // coin/network not enabled
    case cryptochief.CodeInsufficientCredits:  // 402 — API credits exhausted
    case cryptochief.CodeDebtLimitExceeded:    // 402 — postpaid debt cap reached
    case "NO_MASTER_WALLETS":                  // no eligible master wallet
    case "FEE_LIMIT_EXCEEDED":                 // MaxFeeAmountFiat was too tight
    }
}
```

`errors.Is(err, cryptochief.ErrInsufficientFunds)` works too, and so does `errors.Is(err, cryptochief.ErrDebtLimitExceeded)`.

The two billing refusals are both `402`, and they are worth separating: the first means the account has run out of credits, the second that a postpaid account has reached its debt cap. `Code` tells them apart. For how the code is read from the error envelope, see [Errors & retries](/processing/go/concepts/errors.md#where-the-code-comes-from).

{% content-ref url="/pages/gXLLVElZ97mSEv9wuIJh" %}
[Mass payouts](/processing/go/guides/mass-payouts.md)
{% endcontent-ref %}
