MegTrustDevelopers

Send a payout

Request a withdrawal into your organisation's approval flow, follow it to settlement, and recover it safely after a timeout.

Before you start

  • The destination must already be an approved, verified destination — see Destinations.
  • Take wallet_id, asset and network from the wallet's balances in GET /api/v1/wallets.
  • Obtain the payment instruction and persist one idempotency key for this payment before the first attempt — for example your invoice or payout reference.

Request a withdrawal

POST /api/v1/withdrawals submits a withdrawal into your organisation's approval flow. Every payout passes compliance checks and a human approval before it reaches the network. Neither the API nor the MCP connector exposes an approval endpoint.

202 means “accepted for approval”, not “sent”.

Never show a user "sent" on a 202 — poll the status endpoint or wait for the webhook.

FieldTypeNotes
wallet_idstringRequired. From GET /wallets. Up to 128 characters.
assetstringRequired. Symbol, e.g. ETH. Must be held by that wallet.
amountstringRequired. Positive decimal STRING, e.g. "0.25", up to 100 characters. More decimals than the asset allows returns 400.
destinationstringRequired. Must already be an approved, verified destination. Up to 128 characters.
networkstringUse the exact balance network. Required when the asset is held on several networks; if omitted, it resolves to the one network the asset is held on.
fee_prioritystringOptional. low · medium · high.
idempotency-keyheaderRequired. Retry with the SAME value to avoid a second withdrawal.

Bodies reject unknown fields with 400. Idempotency keys are 1–128 printable ASCII characters without spaces, and are unique within your organisation. A withdrawal is visible only to keys of the workspace that created it.

body
{
  "wallet_id": "acct_7f3a…",
  "asset": "ETH",
  "amount": "0.25",
  "destination": "0x03a6…81b9",
  "network": "ethereum"
}
response
202 Accepted

{
  "data": {
    "id": "5f0c2a9e-7d1b-4c3a-9e2f-6b8d4a1c3e57",
    "status": "pending_approval",
    "wallet_id": "acct_7f3a…",
    "asset": "ETH",
    "amount": "0.25",
    "destination": "0x03a6…81b9",
    "network": "ethereum",
    "tx_hash": null,
    "created_at": "2026-07-30T09:02:11.004Z",
    "updated_at": "2026-07-30T09:02:11.004Z"
  },
  "meta": {}
}

With the supplied Node.js client:

// These values come from your persisted payment record.
const paymentKey = "invoice-2026-0042";
const payment = {
  wallet_id: "WALLET_ID_FROM_API", asset: "USDC", amount: "25.50",
  network: "ethereum", destination: "VERIFIED_DESTINATION_ADDRESS",
  fee_priority: "medium"
};
const result = await api.request("POST", "/api/v1/withdrawals", payment, paymentKey);
if (result.status === 202 || result.status === 200) {
  const status = await api.request("GET", `/api/v1/withdrawals/${result.body.data.id}`);
  console.log(status.body);
}

Follow the lifecycle

GET /api/v1/withdrawals/{id} refreshes progress through settlement. Read data.status even on a successful HTTP response.

received → pending_approval → approved → processing → completed
StatusMeaning
receivedAccepted by us, not yet submitted for approval.
pending_approvalPending approval or processing checks. The only state you can cancel from.
approvedApproved, preparing to send.
processingSubmitted to the network.
completedSettled. tx_hash is populated.
rejected · failed · expiredTerminal unsuccessful outcome. Investigate before requesting another payment.
cancelledTerminal. You cancelled it before approval; nothing was sent.

If a live refresh fails, the response carries the last recorded state with meta.stale: true. Final states come from the record. Polling never approves or retries a payment.

Every status change also arrives as a withdrawal.status_changed webhook if you have one configured. Treat the webhook as a prompt to re-read GET /withdrawals/{id}, which is the source of truth.

Retry safely

If a POST times out or fails with 500/503 or a network error, retry with the same key and the same body. The client signs a fresh attempt. You get one of:

  • 200 with meta.idempotent_replay: true and the original withdrawal — including a recorded failed or still-received status. A replay returns recorded state; it never resumes or reissues the payment.
  • 202 — the earlier attempt never landed, so this one is evaluated afresh.
  • 409 idempotency_conflict — that key was already used for a different body. Recover the original payment before proceeding. A collision with a payment outside your workspace also returns a conflict, without exposing that payment.

A request refused before it is accepted — a 400, a 404, a 422 for the wallet or asset, or a 503 — records nothing. Fix the cause if there is one, then retry with the same key: the retry is evaluated afresh, as if it were the first attempt. Only the same key with a different body is refused, with 409 idempotency_conflict.

A NEW key on a retry is a NEW withdrawal. Never generate a replacement key for an uncertain payment.

Recover a lost submission

GET /api/v1/withdrawals lists recorded payouts, including a submission whose response was lost.

ParameterTypeNotes
limitinteger1–100; default 25.
offsetinteger0–10000; default 0. Follow meta.next_offset until null. Deduplicate by id when new payments arrive during paging.
wallet_idstringOptional wallet filter within this key's workspace.
idempotency_keystringLook up the original payment after a timeout. Preserve its key and body when retrying.
const found = await api.request("GET", `/api/v1/withdrawals?idempotency_key=${encodeURIComponent(paymentKey)}`);

Listing returns recorded state. Use GET /withdrawals/{id} to refresh an individual payout. If a payout stays received or otherwise unresolved, contact your administrator with the withdrawal ID and the X-Request-Id of your request.

Errors you should handle

CodeHTTPMeaning
idempotency_key_required400POST /withdrawals needs an Idempotency-Key header.
idempotency_conflict409That payment key was used for another payload.
asset_not_held422That wallet holds no such asset on that network.
destination_not_whitelisted422The destination is not an approved, verified destination yet.
no_approver_available422The workspace has no second approver configured.
withdrawal_failed422The request could not be processed.

The full list is on Errors.

On this page