Skip to main content

Payouts

Payouts​

Send funds from your Morph-hosted wallet in three write steps, then poll the read endpoints:

  1. Preview Payout — quote gas; no order is created.
  2. Create Payout — create the order and receive param_json (TRX also returns raw_txs).
  3. Authorize Payout — stamp param_json (Ed25519). TRX must also echo raw_txs. Morph submits on-chain. There is no public submit API.

Read endpoints:

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": "..."
}
note

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​

FieldTypeRequiredDescription
scenestringNopayout (default) or withdraw (exactly one recipient).
tx_intent.chainstringYesSingle chain, for example morph.
tx_intent.source_wallet.addressstringYesHosted wallet: EVM 0x, SOL base58, or TRX T….
tx_intent.recipients[]object[]Yesindex, address, token.contract, amount. Optional note (max 30 characters).
tx_intent.recipient_countintegerYesMust equal recipients.length.
support_networksstring[]YesFrom Support tokens; every entry must equal tx_intent.chain.
support_tokensobject[]Yes{network, address, symbol} subset of the support-tokens catalog.
dryrunbooleanNoOmit. 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.

caution

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​

FieldTypeRequiredDescription
preview_idstringYesFrom preview.
tx_intentobjectYesMust match the preview snapshot.
selected_gas_token.contractstringYesFrom gas_tokens[].
selected_gas_token.max_gas_feestringYesFee cap you accept.
scenestringNoMust match preview.
reference_idstringNoYour business reference.
notestringYes on EVMDisplay 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​

FieldDescription
payout_order_idpyo_*
payout_txn_id / reference_id / status / sign_deadline / gasOrder and gas quote
phaseAlways awaiting_authorization (Console uses awaiting_signature)
param_jsonString to stamp. EVM/SOL: one items[] entry; TRX: 2–3. Each TRX param is only {chain, data} (data = raw_data_hex)
param_hashConvenience SHA-256 hex of param_json. Not the bytes you stamp
raw_txsTRX only: unsigned transaction array (includes raw_data). Echo on authorize. Omit on EVM/SOL
securityFour 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​

FieldTypeRequiredDescription
payout_order_idstringYesFrom create.
param_jsonstringYesByte-for-byte echo of create. Rewrite → 90604.
merchant_signstringYesEd25519 over the raw 32-byte SHA-256 of param_json, a vertical bar, and waas_timestamp. Base64. Empty → 90606. See Merchant stamp.
waas_timestampstringYes13-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_addressstringYesSame as create tx_intent.source_wallet.address (required on TRX; param has no from).
sign_modestringYesOnly waas.
raw_txsobject[]TRX onlyByte-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​

ParameterTypeRequiredDescription
scenestringNopayout, withdraw, or payout_schedule; omit to return all.
statusstringNoOne of pending, success, failed.
chainstringNoFilter by chain identifier. Max 32 characters.
reference_idstringNoFilter by your business reference id. Max 64 characters.
startstringNoRange start, Unix milliseconds or RFC3339. Max 32 characters.
endstringNoRange end, Unix milliseconds or RFC3339. Max 32 characters.
sort_bystringNoSort field. Currently only created_at is supported.
orderstringNoSort direction. One of asc, desc (default).
cursorstringNoPagination cursor; omit for the first page. Max 512 characters.
limitintegerNoPage size. 1–100, default 20 when omitted.
note

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.

note

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​

ParameterTypeRequiredDescription
payout_order_idstringYesThe 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": "..."
}
note

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​

FieldTypeDescription
addressstringThe receiving wallet address the balance belongs to.
list[].chain_code / chain_name / chain_idstring / string / int64Chain information.
list[].token_code / token_name / token_contract / token_decimalsstring / string / string / int32Token information.
list[].balancestringOn-chain balance of this token in the wallet.
list[].pricestringReal-time fiat unit price of the token.
list[].change_24hnumber24-hour price change.
caution

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_codeMeaningDescription
0Success—
90000Invalid parametersMissing field, empty EVM note, sign_mode not waas, missing or overlong X-Idempotency-Key, or a query enum/range error.
90001UnauthorizedAuthentication failed or merchant identity is missing.
90112Wallet not foundsource_wallet.address / source_address is not a hosted wallet on your account. Create does not persist an order.
90309Security check failedRisk-control headers on create, or transaction / stamp verification on authorize.
90604Invalid merchant stampStamp failed, echoed param_json does not match create, or TRX raw_txs is missing / does not match param.data.
90605Origin key missingBind an origin key in Console first.
90606Missing merchant stampmerchant_sign empty.
903xxPay-service business errorFor example insufficient gas or balance. Passed through.
80000 – 80099System errorRetry later; include trace if it persists.

See the Error Codes reference for details.