Skip to content

Pay out

Paying out is one request, but three things make it safe in production: a key that names the payment, retries that cannot pay twice, and following the payment until the network has made it final.

  • Approve the addresses you pay. In Live, an address your organisation has never paid is refused until someone approves it in the console, and then waits 24 hours. Approve sellers’ or suppliers’ addresses when you learn them, not when you first pay them. See payments, rules and approvals.
  • Fund the network fees. Every payment’s fee is paid from your gas pool on that network. See network fees.
  • Give the key the Integration role, or another with Request payments.

Name each payment with your own id for it, such as the payout’s id, as the Idempotency-Key and the reference. If the answer does not come back, or comes back rate limited or unavailable, send the same request again with the same key: Sotirea answers the payment it already made rather than making another.

// payout pays a seller. payoutID is your own id for the payout: it names
// the payment, so calling this again for the same payout never pays twice.
func payout(
ctx context.Context, c *sotirea.Client,
vault int64, to, amount, payoutID string,
) (sotirea.Payment, error) {
pay := sotirea.PaymentRequest{
VaultID: vault,
ChainID: base,
Asset: usdc,
To: to,
Amount: amount, // USDC has 6 decimals: "25000000" is 25 USDC
Reference: payoutID,
}
var payment sotirea.Payment
var err error
for attempt := 1; attempt <= 3; attempt++ {
payment, _, err = c.SendPayment(ctx, payoutID, pay)
if !retryable(err) {
return payment, err
}
// Lost, rate limited or briefly unavailable: the same key is safe.
time.Sleep(time.Duration(attempt) * 2 * time.Second)
}
return payment, err
}
// retryable says whether sending the same payment again could succeed.
func retryable(err error) bool {
var refusal *sotirea.Error
if !errors.As(err, &refusal) {
return err != nil // no answer at all: the network or a timeout
}
switch refusal.Code {
case sotirea.CodeRateLimited, sotirea.CodeUnavailable, sotirea.CodeInternal:
return true
}
return false
}

Amounts are whole numbers of the asset’s smallest unit, as a string. USDC has 6 decimals, so 25 USDC is "25000000"; ETH has 18, so 0.01 ETH is "10000000000000000".

To check first without sending anything, quote it with the same body: it answers whether your rules allow it, whether it needs approval, and the most its fee can cost.

The answer comes at once, as requested, or awaiting_approval when your rules want people to agree. Follow it to confirmed or failed with the payment.confirmed and payment.failed webhooks, or with GET /v1/payments/{id} using the id it answered, api:<your organisation's id>:<your key>.

Treat a payment as paid only when it is confirmed. If it is stuck, it can be sped up or cancelled.

Each refusal says why in fields.rule. Handle refusals shows how to turn the common ones into what your team should do.