Payout API
Verstuur uitbetalingen vanaf je merchantsaldo naar elk blockchain-adres.
Met de Payout API kun je programmatisch geld vanaf je merchantsaldo uitbetalen naar elk blockchain-adres.
Voor alle Payout-endpoints moet je een aparte Payout API key gebruiken om de sign-handtekening te genereren. Deze key verschilt van je gewone API key en moet worden aangemaakt in je projectinstellingen.
Uitbetaling aanmaken
Maakt een uitbetalingsverzoek aan vanaf je merchantsaldo.
/v1/payoutVerzoekparameters
| Veld | Type | Vereist | Beschrijving |
|---|---|---|---|
currency | string | ja | Uitbetalingsvaluta (zie References) |
network | string | ja | Netwerkcode (zie References) |
amount | string | ja | Uitbetalingsbedrag |
to_address | string | ja | Blockchain-adres van de ontvanger |
order_id | string | nee | Idempotentie-key — uniek binnen een project. Een herhaalde POST met dezelfde order_id maakt geen nieuwe uitbetaling aan — in plaats daarvan wordt de bestaande teruggegeven |
url_callback | string | nee | URL voor uitbetalingswebhooks. Laat weg om webhooks voor deze uitbetaling uit te schakelen |
memo | string | null | nee | Destination tag / memo. Wordt momenteel alleen gebruikt door de TON- en SOL-netwerken; max. 255 tekens |
from_currency | string | nee | Bronsaldo dat op het moment van de uitbetaling wordt gedebiteerd en automatisch naar currency wordt omgezet. Hiermee kun je uitbetalen in volatiele activa (BTC, ETH, …) terwijl je saldo in een stablecoin als USDT blijft staan — je hoeft de volatiele crypto niet zelf aan te houden. Geef "USDT" mee om het USDT-saldo te debiteren |
fee_option | string | nee | Hoe fees worden berekend. deduct (standaard) — netwerk- + platformfees worden van amount afgetrokken, de ontvanger krijgt amount - fees. add — fees worden bovenop berekend, de merchant wordt gedebiteerd met amount + fees, de ontvanger krijgt precies amount |
Idempotentie. Binnen een project is een uitbetaling uniek op order_id. Dezelfde POST opnieuw versturen met dezelfde order_id is veilig — de API geeft de bestaande uitbetaling terug in plaats van een duplicaat aan te maken. Geef in productie altijd een order_id mee bij uitbetalingen.
Voorbeelden van verzoeken
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
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);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();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()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()
}Voorbeeld van een response
{
"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"
}
}Fees. Standaard fee_option: deduct — netwerk- + platformfees worden van amount afgetrokken (de ontvanger krijgt amount - fees). Geef fee_option: add mee om fees bovenop te berekenen — de ontvanger krijgt precies amount en de merchant wordt gedebiteerd met amount + fees.
Uitbetaling berekenen
Schat uitbetalingsbedragen en kosten zonder een uitbetaling aan te maken of je saldo te debiteren. Gebruik dit om de gebruiker vóór bevestiging het exacte bedrag te tonen dat hij ontvangt (of betaalt).
/v1/payout/calcVerzoekparameters
Identiek aan Uitbetaling aanmaken — dezelfde velden, dezelfde ondertekening. order_id, url_callback, to_address en memo worden geaccepteerd maar genegeerd: er wordt geen uitbetaling opgeslagen en er worden geen callbacks verzonden.
Voorbeeld van een verzoek
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"}'Voorbeeld van een response
{
"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"
}
}Alleen preview. Dit endpoint is alleen-lezen — er wordt geen saldo gedebiteerd en geen uitbetalingsrecord aangemaakt. Roep het zo vaak aan als nodig is om de kostenspecificatie in je UI te tonen.
Status van uitbetaling
Haal de status van een uitbetalingsverzoek op.
/v1/payout/status/{uuid}Padparameters
| Veld | Type | Vereist | Beschrijving |
|---|---|---|---|
uuid | string | ja | Payout UUID (uit result.uuid bij aanmaken) |
Voorbeeld van een response
{
"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"
}
}Voor dit GET-verzoek wordt de handtekening berekend over een lege body:
hash_hmac('sha256', base64_encode(''), $apiKey)
Responsevelden
Velden die in result worden teruggegeven door POST /v1/payout en GET /v1/payout/status/{uuid}:
| Veld | Type | Beschrijving |
|---|---|---|
uuid | string | Payout UUID toegewezen door het systeem |
order_id | string | Je interne uitbetalingsidentificatie (uniek binnen het project) |
status | string | Huidige uitbetalingsstatus (zie hieronder) |
currency | string | Uitbetalingsvaluta |
network | string | Netwerkcode |
amount | string | Uitbetalingsbedrag zoals aangevraagd |
merchant_amount | string | Bedrag dat van het merchantsaldo is afgeschreven |
network_amount | string | Bedrag dat daadwerkelijk on-chain is verstuurd (na netwerk- + platformfees) |
amount_usd | string | USD-equivalent van het uitbetalingsbedrag |
to_address | string | Blockchain-adres van de ontvanger |
memo | string | null | Destination tag / memo (TON, SOL). Anders null |
txid | string | null | Hash van de blockchain-transactie. null totdat de transactie is verstuurd |
block_number | int | null | Blocknummer waarin de transactie is opgenomen. null totdat opgenomen |
error_type | string | null | Reden van mislukking als status = failed (zie Foutsoorten hieronder). Anders null |
created_at | string (ISO 8601) | Tijd van aanmaak van de uitbetaling |
updated_at | string (ISO 8601) | Tijd van laatste statuswijziging |
from_currency | string | null | Bronsaldo waarvan de uitbetaling is gedebiteerd wanneer automatische conversie is gebruikt (bijv. USDT voor een uitbetaling in BTC). null als er geen conversie heeft plaatsgevonden |
debited_amount | string | null | Bedrag dat na conversie daadwerkelijk van het bronsaldo is afgeschreven. Alleen aanwezig wanneer automatische conversie wordt gebruikt |
debited_currency | string | null | Valuta van debited_amount — het saldo waarvan geld is afgeschreven |
Uitbetalingsstatussen
Het veld status kan de volgende waarden aannemen:
| Status | Beschrijving |
|---|---|
pending | Aangemaakt, wacht op verwerking |
completed | Succesvol voltooid — txid is ingesteld |
failed | Verzendfout — zie error_type |
cancelled | Geannuleerd |
Foutsoorten
Wanneer status = failed, beschrijft het veld error_type de reden:
| Code | Beschrijving |
|---|---|
aml_risk | Uitbetaling geblokkeerd door AML-risicocontroles (ontvangeradres gemarkeerd als hoog risico) |
Webhook-meldingen
Wanneer de status van een uitbetaling verandert, stuurt het systeem een POST-webhook naar de url_callback-URL die bij het aanmaken van de uitbetaling is meegegeven. Als url_callback niet is opgegeven, worden er geen webhooks voor die uitbetaling verstuurd.
- Methode:
POST - Content-Type:
application/json - Handtekening:
sign-veld in de body van het verzoek, berekend met de Payout API key (dezelfde key die wordt gebruikt om uitbetalingsverzoeken te ondertekenen).
De payload spiegelt het result-object van GET /v1/payout/status/{uuid} plus een sign-veld voor verificatie.
Payload
{
"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"
}De handtekening verifiëren. Gebruik hetzelfde algoritme als bij betalingswebhooks, maar onderteken met je Payout API key in plaats van de gewone API key. Verwijder het sign-veld, encodeer de overgebleven payload als JSON, encodeer in base64 en bereken vervolgens hash_hmac('sha256', $base64, $payoutApiKey) en vergelijk met de ontvangen sign.