Payouts
Payouts
Send funds from your Morph-hosted wallet in three write steps, then poll the read endpoints:
- Preview Payout — quote gas; no order is created.
- Create Payout — create the order and receive
param_json(TRX also returnsraw_txs). - Authorize Payout — stamp
param_json(Ed25519). TRX must also echoraw_txs. Morph submits on-chain. There is no publicsubmitAPI.
Read endpoints:
- List Payouts — page through history.
- Get Payout — full detail by
payout_order_id. - Get Balance — on-chain balance of the default receiving wallet.
scene:
payout— pay a third party (default).withdraw— move funds to your own address. Exactly one recipient.
EVM, SOL, and TRX are supported. You stamp one param_json string on every
chain — see Merchant stamp.
All requests must be authenticated with your
API Key. Do not send merchant_id.
Base URL
https://pay-openapi.morph.network/pro/merchant/payout/v1
Endpoint paths in this page are relative to that base URL.
Response envelope
{
"error_code": 0,
"msg": "success",
"data": {},
"trace": "..."
}
Business success or failure is determined by error_code (error_code == 0
means success), not by the HTTP status code alone. See
Error Codes.
Preview Payout
POST /payouts/preview
Quotes gas tokens for a transfer intent. No order is created; you may call this repeatedly.
Copy support_networks / support_tokens from
Support tokens. source_wallet.address must be a hosted
wallet under your account (List wallets).
Request fields
| Field | Type | Required | Description |
|---|---|---|---|
scene | string | No | payout (default) or withdraw (exactly one recipient). |
tx_intent.chain | string | Yes | Single chain, for example morph. |
tx_intent.source_wallet.address | string | Yes | Hosted wallet: EVM 0x, SOL base58, or TRX T…. |
tx_intent.recipients[] | object[] | Yes | index, address, token.contract, amount. Optional note (max 30 characters). |
tx_intent.recipient_count | integer | Yes | Must equal recipients.length. |
support_networks | string[] | Yes | From Support tokens; every entry must equal tx_intent.chain. |
support_tokens | object[] | Yes | {network, address, symbol} subset of the support-tokens catalog. |
dryrun | boolean | No | Omit. When true, SOL/TRX do not persist a preview_id and cannot be used to create. |
Request example
{
"scene": "payout",
"tx_intent": {
"chain": "morph",
"source_wallet": { "address": "0xabc1...def0" },
"recipients": [
{
"index": 0,
"address": "0xdef2...7890",
"token": { "contract": "0xToken..." },
"amount": "10.50"
}
],
"recipient_count": 1
},
"support_networks": ["morph"],
"support_tokens": [
{
"network": "morph",
"address": "0xToken...",
"symbol": "USDT"
}
]
}
Success response
data includes preview_id (pvp_*), intent_hash (display only — do not
stamp), expires_at, gas_tokens[]. Pick gas_tokens[].contract and a
max_gas_fee (typically suggested_max_gas_fee) for create.
preview_id expires and is consumed on a successful create. An expired or
already-used id fails create, not authorize. After create, watch
sign_deadline instead.
Create Payout
POST /payouts
Creates the payout order and returns param_json to stamp. Requires header
X-Idempotency-Key (≤128 characters). Do not send sign_mode on this step.
If source_wallet.address is not yours, the call returns 90112 and does not
create an order.
Request fields
| Field | Type | Required | Description |
|---|---|---|---|
preview_id | string | Yes | From preview. |
tx_intent | object | Yes | Must match the preview snapshot. |
selected_gas_token.contract | string | Yes | From gas_tokens[]. |
selected_gas_token.max_gas_fee | string | Yes | Fee cap you accept. |
scene | string | No | Must match preview. |
reference_id | string | No | Your business reference. |
note | string | Yes on EVM | Display note, not on-chain, max 30 Unicode characters. Empty string on EVM (for example morph) returns 90000. SOL/TRX allow empty; send a note on every chain if you integrate more than EVM. |
Success response
| Field | Description |
|---|---|
payout_order_id | pyo_* |
payout_txn_id / reference_id / status / sign_deadline / gas | Order and gas quote |
phase | Always awaiting_authorization (Console uses awaiting_signature) |
param_json | String to stamp. EVM/SOL: one items[] entry; TRX: 2–3. Each TRX param is only {chain, data} (data = raw_data_hex) |
param_hash | Convenience SHA-256 hex of param_json. Not the bytes you stamp |
raw_txs | TRX only: unsigned transaction array (includes raw_data). Echo on authorize. Omit on EVM/SOL |
security | Four risk-control headers |
eip7702_call_struct and raw_tx are not returned. Stamp the whole
param_json string once — Merchant stamp.
Do not put raw_data inside param; keep it in raw_txs.
{
"error_code": 0,
"msg": "success",
"data": {
"payout_order_id": "pyo_...",
"reference_id": "merchant-ref-...",
"payout_txn_id": "pytx_...",
"status": "pending",
"phase": "awaiting_authorization",
"sign_deadline": 1782777900000,
"gas": {
"token_symbol": "USDT",
"token_contract": "0xToken...",
"amount": "0.006214",
"max_amount": "0.007457"
},
"param_json": "{\"items\":[{\"operation\":\"signTransaction\",\"param\":\"...\"}]}",
"param_hash": "a1b2c3...",
"raw_txs": [
{
"txID": "...",
"raw_data_hex": "0a02...",
"raw_data": {},
"visible": false
}
]
},
"trace": "..."
}
Authorize Payout
POST /payouts/authorize
Request fields
| Field | Type | Required | Description |
|---|---|---|---|
payout_order_id | string | Yes | From create. |
param_json | string | Yes | Byte-for-byte echo of create. Rewrite → 90604. |
merchant_sign | string | Yes | Ed25519 over the raw 32-byte SHA-256 of param_json, a vertical bar, and waas_timestamp. Base64. Empty → 90606. See Merchant stamp. |
waas_timestamp | string | Yes | 13-digit Unix milliseconds folded into merchant_sign. Morph sends this value as X-Waas-Timestamp. Missing or not 13 digits → 90000. Outside ±60 seconds → 90604. |
source_address | string | Yes | Same as create tx_intent.source_wallet.address (required on TRX; param has no from). |
sign_mode | string | Yes | Only waas. |
raw_txs | object[] | TRX only | Byte-for-byte echo of create raw_txs. Missing or mismatched raw_data_hex → 90604. Do not send on EVM/SOL. |
{
"payout_order_id": "pyo_...",
"param_json": "{\"items\":[{\"operation\":\"signTransaction\",\"param\":\"...\"}]}",
"merchant_sign": "base64...",
"waas_timestamp": "1700000000000",
"source_address": "0xabc1...def0",
"sign_mode": "waas",
"raw_txs": [
{
"txID": "...",
"raw_data_hex": "0a02...",
"raw_data": {},
"visible": false
}
]
}
raw_txs is required for TRX create/authorize. The example includes it so a
TRX integration can copy the field; omit it on EVM and SOL.
Success response
data includes payout_order_id, status, phase, and accepted. Poll
Get Payout or List Payouts until status is
success or failed. Payout completion is not delivered on the payin
order webhook.
List Payouts
GET /payouts
Returns payout history in reverse-chronological order by default, with
cursor-based pagination. Omit scene to return both payout and withdraw
records (Console-created payout_schedule rows may also appear).
Query parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
scene | string | No | payout, withdraw, or payout_schedule; omit to return all. |
status | string | No | One of pending, success, failed. |
chain | string | No | Filter by chain identifier. Max 32 characters. |
reference_id | string | No | Filter by your business reference id. Max 64 characters. |
start | string | No | Range start, Unix milliseconds or RFC3339. Max 32 characters. |
end | string | No | Range end, Unix milliseconds or RFC3339. Max 32 characters. |
sort_by | string | No | Sort field. Currently only created_at is supported. |
order | string | No | Sort direction. One of asc, desc (default). |
cursor | string | No | Pagination cursor; omit for the first page. Max 512 characters. |
limit | integer | No | Page size. 1–100, default 20 when omitted. |
Any value outside the ranges/enums above is rejected with error_code: 90000
before the request reaches order-service — you will not get a partial or
best-effort match.
Request example
GET /pro/merchant/payout/v1/payouts?scene=withdraw&limit=20
Success response
{
"error_code": 0,
"msg": "success",
"data": {
"list": [
{
"payout_order_id": "pyo_...",
"scene": "withdraw",
"reference_id": "merchant-ref-...",
"status": "success",
"chain": "morph",
"created_at": 1782777600000
}
],
"cursor": "eyJ..."
},
"trace": "..."
}
To fetch the next page, pass the returned cursor on the next request. An empty
or missing cursor means there are no more results.
The fields shown above are the stable query-side fields. Each list[] entry may
carry additional payout detail fields (amount, recipient, token, etc.); treat
the payout record itself as the source of truth for those.
Get Payout
GET /payouts/detail
Returns the full detail of a single payout, including the on-chain
transaction and the per-recipient breakdown — fields not present on the
List Payouts list[] entry.
Query parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
payout_order_id | string | Yes | The payout id (pyo_*) returned by List Payouts or Create Payout. |
Omitting payout_order_id is rejected with error_code: 90000.
Request example
GET /pro/merchant/payout/v1/payouts/detail?payout_order_id=pyo_...
Success response
{
"error_code": 0,
"msg": "success",
"data": {
"payout_order_id": "pyo_...",
"scene": "payout",
"status": "success",
"phase": "completed",
"chain": "morph",
"source_address": "0xabc1...def0",
"reference_id": "merchant-ref-...",
"recipient_count": 2,
"gas_token_symbol": "USDT",
"gas_token_address": "0xToken...",
"gas_token_decimals": 6,
"quoted_gas_fee": "0.006214",
"max_gas_fee": "0.007457",
"final_gas_fee": "0.005750",
"transfer_summary": [
{ "symbol": "USDT", "address": "0xToken...", "decimals": 6, "amount": "20.00", "recipient_count": 2 }
],
"transaction": {
"payout_txn_id": "pytx_...",
"chain": "morph",
"status": "success",
"phase": "completed",
"from_address": "0xabc1...def0",
"tx_hash": "0x...",
"confirmed_at": 1782777610000
},
"items": [
{ "index": 0, "recipient_address": "0xdef2...7890", "token_symbol": "USDT", "token_address": "0xToken...", "token_decimals": 6, "amount": "10.00" },
{ "index": 1, "recipient_address": "0x3333...4444", "token_symbol": "USDT", "token_address": "0xToken...", "token_decimals": 6, "amount": "10.00" }
],
"created_at": 1782777600000,
"updated_at": 1782777610000,
"completed_at": 1782777610000
},
"trace": "..."
}
The fields shown above reflect a completed payout example. A failed
payout instead carries failure_code / failure_message; an in-flight one may
omit transaction / completed_at until it settles. Treat the payout record
itself as the source of truth — do not assume every field is always present.
Get Balance
GET /balance
No query parameters. Returns the real-time on-chain balance (with fiat valuation) of your receiving wallet, per active chain and token. Results are cached for about 10 seconds.
Success response
{
"error_code": 0,
"msg": "success",
"data": {
"address": "0xdef2...7890",
"list": [
{
"chain_code": "morph",
"chain_name": "Morph",
"chain_id": 2818,
"token_code": "usdt",
"token_name": "USDT",
"token_contract": "0xToken...",
"token_decimals": 6,
"balance": "1234.56",
"price": "1.00",
"change_24h": 0.02
}
]
},
"trace": "..."
}
Response fields
| Field | Type | Description |
|---|---|---|
address | string | The receiving wallet address the balance belongs to. |
list[].chain_code / chain_name / chain_id | string / string / int64 | Chain information. |
list[].token_code / token_name / token_contract / token_decimals | string / string / string / int32 | Token information. |
list[].balance | string | On-chain balance of this token in the wallet. |
list[].price | string | Real-time fiat unit price of the token. |
list[].change_24h | number | 24-hour price change. |
This endpoint only inspects your TEE-managed receiving wallets. Wallet selection follows these rules:
- Among your TEE wallets, if one is marked as default, its balance is returned.
- If none is marked as default, the earliest created TEE wallet is used (the one with the smallest internal ID, not necessarily the first shown in any list).
- Funds held on other (non-selected) receiving wallets are not included.
If you have no TEE-managed receiving wallets (for example, you only use
self-custody signing wallets), no wallet is matched and the response returns an
empty result (address: "", list: []) rather than an error.
Errors
| error_code | Meaning | Description |
|---|---|---|
0 | Success | — |
90000 | Invalid parameters | Missing field, empty EVM note, sign_mode not waas, missing or overlong X-Idempotency-Key, or a query enum/range error. |
90001 | Unauthorized | Authentication failed or merchant identity is missing. |
90112 | Wallet not found | source_wallet.address / source_address is not a hosted wallet on your account. Create does not persist an order. |
90309 | Security check failed | Risk-control headers on create, or transaction / stamp verification on authorize. |
90604 | Invalid merchant stamp | Stamp failed, echoed param_json does not match create, or TRX raw_txs is missing / does not match param.data. |
90605 | Origin key missing | Bind an origin key in Console first. |
90606 | Missing merchant stamp | merchant_sign empty. |
903xx | Pay-service business error | For example insufficient gas or balance. Passed through. |
80000 – 80099 | System error | Retry later; include trace if it persists. |
See the Error Codes reference for details.