Version 1.0.0 · Base URL: https://business.madhousewallet.com
Enter your API key to enable the "Try It" panels. Your key is only held in this tab and never stored. Create a key →
All requests require a Bearer token in the Authorization header. Get a key from the Developers page.
Authorization: Bearer mw_live_<keyId>_<secret>
Rate limits apply per API key. Exceeded requests return HTTP 429.
| Endpoint | Limit |
|---|---|
| GET /api/payouts/deposit-options | 60 req/min |
| GET|POST /api/payouts/account-requirements | 500 req/min |
| GET /api/payouts/quote | 20 req/min |
| GET|POST|PATCH|DELETE /api/payouts/recipients | 30 req/min |
| POST /api/payouts/transfer | 20 req/min |
| POST /api/payouts/transfer/v2 | 20 req/min |
| POST /api/payouts/transfer/v2/confirm-transfer | 20 req/min |
| GET /api/payouts/transfer/:id | 30 req/min |
Headers on every response: X-RateLimit-Limit · X-RateLimit-Remaining · X-RateLimit-Reset (Unix s)
GET /api/payouts/deposit-options. Returns supported source tokens and networks (e.g. USDC on Base, USDT on Tron or Ethereum). Pick the token and network the sender will use.GET /api/payouts/account-requirements?currency=EUR. If any field has refreshRequirementsOnChange: true, POST back with current values when that field changes.POST /api/payouts/recipients. Save the returned id.GET /api/payouts/quote. Valid for 5 minutes. Returns quoteId.Initiate transfer
You send USDC to the deposit address. The rest of the pipeline runs automatically.
POST /api/payouts/transfer with quote_id, recipientId, amount, customer_uuid. Returns deposit_address + transfer_id.wallet_address to deposit_address on source_network.GET /api/payouts/transfer/:transfer_id to monitor progress./api/payouts/account-requirements500 req/minGet required fields for a recipient currency
Call this before creating a recipient. Returns the account types (e.g. iban, sort_code, aba) available for the target currency and the fields required for each. Use the POST variant to refresh the field list when a refreshRequirementsOnChange field changes (e.g. switching legalType from PRIVATE to BUSINESS).
Dynamic requirements loop:
currency → render initial form fieldsrefreshRequirementsOnChange: truetype + details → re-render updated fieldsPOST /api/payouts/recipientsExample Success Response — GET (200)
{
"data": [
{
"type": "iban",
"title": "IBAN",
"fields": [
{
"name": "Legal type",
"group": [
{
"key": "legalType",
"name": "Legal type",
"type": "select",
"required": true,
"refreshRequirementsOnChange": true,
"valuesAllowed": [
{
"key": "PRIVATE",
"name": "Person"
},
{
"key": "BUSINESS",
"name": "Business"
}
]
}
]
},
{
"name": "IBAN",
"group": [
{
"key": "iban",
"name": "IBAN",
"type": "text",
"required": true,
"refreshRequirementsOnChange": false,
"example": "DE89370400440532013000",
"minLength": 14,
"maxLength": 42,
"validationRegexp": "^[A-Z]{2}[0-9]{2}[A-Z0-9]{1,30}$"
}
]
}
]
}
]
}cURL — GET initial requirements
curl "https://business.madhousewallet.com/api/payouts/account-requirements?currency=EUR" \ -H "Authorization: Bearer mw_live_<keyId>_<secret>"
cURL — POST refresh after legalType change
curl -X POST "https://business.madhousewallet.com/api/payouts/account-requirements?currency=EUR" \
-H "Authorization: Bearer mw_live_<keyId>_<secret>" \
-H "Content-Type: application/json" \
-d '{"type":"iban","details":{"legalType":"BUSINESS"}}'Response Codes
/api/payouts/recipients30 req/minList recipients
Returns all active payout recipients belonging to your account. The id from each recipient is used as the recipientId in POST /api/payouts/transfer.
Example Success Response (200)
{
"data": [
{
"id": 12345678,
"accountHolderName": "Jane Doe",
"currency": "EUR",
"country": "DE",
"type": "iban",
"active": true,
"details": {
"iban": "DE89370400440532013000",
"legalType": "PRIVATE"
}
}
]
}cURL Example
curl "https://business.madhousewallet.com/api/payouts/recipients" \ -H "Authorization: Bearer mw_live_<keyId>_<secret>"
Response Codes
/api/payouts/recipients30 req/minCreate a recipient
Creates a new payout recipient linked to your account. The required type and details fields vary by currency — use the account requirements API or the dashboard to determine the correct structure.
Wallet verification: wallet is conditionally required — only needed if your account requires KYC verification. When required, it must be a KYC-verified wallet (EVM or SVM); unverified wallets are rejected with 403 (verify at kyc.madhousewallet.com). If KYC is not enabled for your account, you may omit it.
Email transfer recipients: Pass type: "wise_email_recipient" (with currency: "EUR") to create an email-based recipient. No account requirements lookup needed — provide only details: { "email": "recipient@example.com" }. Email recipients carry no transfer fee.
Country format: When details.address is present, details.address.country is required and must be a valid ISO 3166-1 alpha-2 code (two letters, e.g. US, GB, DE). Country names ("United States") and ISO-3 codes ("USA") are rejected with 400.
Compliance screening: The recipient is screened against a sanctions database before creation using three signals: accountHolderName (case-folded + accent-stripped exact match), details.address.country (ISO 2-letter code), and details.address (street + city + postal-code token overlap). A request is rejected with 403 only if all available signals match the same sanctioned entity. When either the sanctioned record or the recipient lacks a country or address, that check falls through to name-only matching so sparse sanctions data still catches matches.
Request Body Fields
| Field | Required | Type | Description |
|---|---|---|---|
| currency | required | string | ISO 4217 currency code for this recipient (e.g. EUR, GBP, USD). Must be EUR for wise_email_recipient. |
| type | required | string | Account type for the currency (e.g. iban, sort_code, aba). Use wise_email_recipient for email-based EUR transfers. |
| accountHolderName | required | string | Full legal name of the account holder |
| details | required | object | Currency/type-specific bank account details. For wise_email_recipient: { "email": "recipient@example.com" }. For all other types: fields from the account requirements API. |
| wallet | conditional | string | Conditionally required — only if your account requires KYC verification. When required, must be a KYC-verified wallet (EVM 0x + 40 hex, or Solana base58 32–44 chars); unverified wallets are rejected with 403. May be omitted if KYC is not enabled for your account. |
Details By Currency / Type
EUR (IBAN)
{
"currency": "EUR",
"type": "iban",
"accountHolderName": "Jane Doe",
"details": {
"legalType": "PRIVATE",
"iban": "DE89370400440532013000"
},
"wallet": "0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B"
}EUR (Email Transfer)
{
"currency": "EUR",
"type": "wise_email_recipient",
"accountHolderName": "Jane Doe",
"details": {
"email": "jane@example.com"
},
"wallet": "0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B"
}GBP (Sort code)
{
"currency": "GBP",
"type": "sort_code",
"accountHolderName": "Jane Doe",
"details": {
"legalType": "PRIVATE",
"sortCode": "231470",
"accountNumber": "28821822"
},
"wallet": "0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B"
}USD (ACH)
{
"currency": "USD",
"type": "aba",
"accountHolderName": "Jane Doe",
"details": {
"legalType": "PRIVATE",
"abartn": "110000000",
"accountNumber": "000123456789",
"accountType": "CHECKING",
"address": {
"country": "US",
"city": "New York",
"firstLine": "123 Main St",
"postCode": "10001",
"state": "NY"
}
},
"wallet": "0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B"
}PHP
{
"currency": "PHP",
"type": "philippines",
"accountHolderName": "Juan Dela Cruz",
"details": {
"legalType": "PRIVATE",
"bankCode": "BPIPH",
"accountNumber": "1234567890"
},
"wallet": "0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B"
}Example Success Response (201)
{
"id": 12345678,
"accountHolderName": "Jane Doe",
"currency": "EUR",
"country": "DE",
"type": "iban",
"active": true,
"details": {
"iban": "DE89370400440532013000",
"legalType": "PRIVATE"
}
}cURL Example (IBAN)
curl -X POST "https://business.madhousewallet.com/api/payouts/recipients" \
-H "Authorization: Bearer mw_live_<keyId>_<secret>" \
-H "Content-Type: application/json" \
-d '{"currency":"EUR","type":"iban","accountHolderName":"Jane Doe","details":{"legalType":"PRIVATE","iban":"DE89370400440532013000"},"wallet":"0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B"}'cURL Example (Email Transfer)
curl -X POST "https://business.madhousewallet.com/api/payouts/recipients" \
-H "Authorization: Bearer mw_live_<keyId>_<secret>" \
-H "Content-Type: application/json" \
-d '{"currency":"EUR","type":"wise_email_recipient","accountHolderName":"Jane Doe","details":{"email":"jane@example.com"},"wallet":"0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B"}'Response Codes
/api/payouts/recipients/:id30 req/minGet a recipient
Returns a single recipient by ID. Returns 403 if the recipient does not belong to your account.
Path Parameters
| Parameter | Required | Type | Description |
|---|---|---|---|
| id | required | integer | Recipient account ID |
Example Success Response (200)
{
"id": 12345678,
"accountHolderName": "Jane Doe",
"currency": "EUR",
"country": "DE",
"type": "iban",
"active": true,
"details": {
"iban": "DE89370400440532013000",
"legalType": "PRIVATE"
}
}cURL Example
curl "https://business.madhousewallet.com/api/payouts/recipients/12345678" \ -H "Authorization: Bearer mw_live_<keyId>_<secret>"
Response Codes
/api/payouts/recipients/:id30 req/minUpdate a recipient
Updates the accountHolderName and/or details of an existing recipient. At least one of accountHolderName / details must be provided. wallet is conditionally required (see below).
Wallet verification: wallet is conditionally required — only needed if your account requires KYC verification. When required, it must be a KYC-verified wallet (EVM or SVM); unverified wallets are rejected with 403 (verify at kyc.madhousewallet.com). If KYC is not enabled for your account, you may omit it.
id after an update. Always use the id from the response for future calls.Country format: If details.address is included in the update, details.address.country must be a valid ISO 3166-1 alpha-2 code (e.g. US, GB, DE). Updates that don't touch the address leave the existing stored country untouched.
Compliance screening: When accountHolderName is included in the request, the recipient is re-screened using the same name + country + address algorithm as POST /api/payouts/recipients. Country and address come from the new details.address when provided, otherwise from the existing recipient. Matches are rejected with 403 and the recipient is left unchanged.
Request Body Fields
| Field | Required | Type | Description |
|---|---|---|---|
| wallet | conditional | string | Conditionally required — only if your account requires KYC verification. When required, must be a KYC-verified wallet (EVM 0x + 40 hex, or Solana base58 32–44 chars); unverified wallets are rejected with 403. May be omitted if KYC is not enabled for your account. |
| accountHolderName | optional | string | Updated legal name of the account holder |
| details | optional | object | Updated account details — include all required fields for the account type, not just changed ones |
At least one of accountHolderName / details must be provided. wallet is required only if your account requires KYC verification.
Example Success Response (200)
{
"id": 12345679,
"accountHolderName": "Jane Smith",
"currency": "EUR",
"country": "DE",
"type": "iban",
"active": true,
"details": {
"iban": "DE89370400440532013000",
"legalType": "PRIVATE"
}
}cURL Example
curl -X PATCH "https://business.madhousewallet.com/api/payouts/recipients/12345678" \
-H "Authorization: Bearer mw_live_<keyId>_<secret>" \
-H "Content-Type: application/json" \
-d '{"accountHolderName":"Jane Smith","wallet":"0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B"}'Response Codes
/api/payouts/recipients/:id30 req/minDelete a recipient
Permanently deactivates a recipient. This cannot be undone. Any future payouts to this recipient will fail — create a new recipient if needed.
If any transfers for this recipient are still in progress, they are immediately marked as failed before deletion proceeds. The transfers_failed field in the response indicates how many were cancelled. Associated deposit wallets are cleaned up automatically.
Wallet verification: wallet is a conditionally required query-string parameter (DELETE carries no body) — only needed if your account requires KYC verification. When required, it must be a KYC-verified wallet; unverified wallets are rejected with 403 (verify at kyc.madhousewallet.com). If KYC is not enabled for your account, you may omit it.
Parameters
| Parameter | In | Required | Type | Description |
|---|---|---|---|---|
| id | path | required | integer | Recipient account ID to delete |
| wallet | query | conditional | string | Conditionally required — only if your account requires KYC verification. When required, must be a KYC-verified wallet (EVM 0x + 40 hex, or Solana base58 32–44 chars). May be omitted if KYC is not enabled for your account. |
Example Success Response (200)
{
"deleted": true,
"id": "12345678",
"transfers_failed": 0
}cURL Example
curl -X DELETE "https://business.madhousewallet.com/api/payouts/recipients/12345678?wallet=0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B" \ -H "Authorization: Bearer mw_live_<keyId>_<secret>"
Response Codes
/api/payouts/deposit-options60 req/minGet supported source tokens and networks
Returns the list of source tokens and networks supported for deposits. Use the token and network values as source_token and source_network in POST /api/payouts/transfer. No parameters required.
Response Fields (per option)
| Field | Type | Description |
|---|---|---|
| token | string | Source token identifier — pass as source_token (e.g. usdc, usdt) |
| network | string | Source network identifier — pass as source_network (e.g. base, ethereum, polygon, solana, tron) |
| label | string | Human-readable label (e.g. USDC on Base) |
| tokenLabel | string | Short token display label (e.g. USDC) |
| networkLabel | string | Short network display label (e.g. Base) |
| status | string | "available" or "unavailable". Unavailable options are temporarily disabled — do not use their token/network values in POST /api/payouts/transfer. |
Example Success Response (200)
{
"options": [
{
"token": "usdc",
"network": "arbitrum",
"label": "USDC on Arbitrum",
"tokenLabel": "USDC",
"networkLabel": "Arbitrum",
"status": "available"
},
{
"token": "usdc",
"network": "avalanche",
"label": "USDC on Avalanche",
"tokenLabel": "USDC",
"networkLabel": "Avalanche",
"status": "available"
},
{
"token": "usdc",
"network": "base",
"label": "USDC on Base",
"tokenLabel": "USDC",
"networkLabel": "Base",
"status": "available"
},
{
"token": "usdc",
"network": "ethereum",
"label": "USDC on Ethereum",
"tokenLabel": "USDC",
"networkLabel": "Ethereum",
"status": "available"
},
{
"token": "usdc",
"network": "polygon",
"label": "USDC on Polygon",
"tokenLabel": "USDC",
"networkLabel": "Polygon",
"status": "available"
},
{
"token": "usdc",
"network": "solana",
"label": "USDC on Solana",
"tokenLabel": "USDC",
"networkLabel": "Solana",
"status": "unavailable"
},
{
"token": "usdt",
"network": "ethereum",
"label": "USDT on Ethereum",
"tokenLabel": "USDT",
"networkLabel": "Ethereum",
"status": "available"
},
{
"token": "usdt",
"network": "tron",
"label": "USDT on Tron",
"tokenLabel": "USDT",
"networkLabel": "Tron",
"status": "unavailable"
}
]
}cURL Example
curl "https://business.madhousewallet.com/api/payouts/deposit-options" \ -H "Authorization: Bearer mw_live_<keyId>_<secret>"
Response Codes
/api/payouts/amount-limits60 req/minGet transfer amount limits
Returns the minimum and maximum USD amounts your account is permitted to submit to GET /api/payouts/quote and POST /api/payouts/transfer. A null value means no limit is configured for that bound. Both endpoints return a 400 error if the submitted amount falls outside these limits.
Response Fields
| Field | Type | Description |
|---|---|---|
| min_amount | number | null | Minimum USD amount allowed per request. null = no minimum configured. |
| max_amount | number | null | Maximum USD amount allowed per request. null = no maximum configured. |
Example Success Response (200)
{
"min_amount": 10,
"max_amount": 50000
}cURL Example
curl "https://business.madhousewallet.com/api/payouts/amount-limits" \ -H "Authorization: Bearer mw_live_<keyId>_<secret>"
Response Codes
/api/payouts/quote20 req/minGet exchange rate quote
Returns a live USD → foreign currency quote including fees and estimated delivery time. The quoted amount is cached server-side for 5 minutes. The response includes a quoteId — pass it as quote_id to POST /api/payouts/transfer or POST /api/payouts/transfer/v2 within the 5-minute window.
v2-offloader accounts: Pass v2_offloader=true when quoting for the v2 transfer pipeline. serviceFeePercent will reflect the combined platform fee (your configured fee + 20 bips for the offloader network). serviceFeeFixed will be 0. providerCharge is absent. Fee fields are display-only — the platform deducts the correct amounts at settlement.
Query Parameters
| Parameter | Required | Type | Description |
|---|---|---|---|
| targetCurrency | required | string | ISO 4217 target currency. 81 supported currencies: AED, ALL, ARS, AUD, BAM, BDT, BHD, BMD, BOB, BRL, BWP, CAD, CHF, CLP, CNY, COP, CRC, CVE, CZK, DKK, DOP, EGP, EUR, GBP, GEL, GHS, GMD, GNF, GTQ, HKD, HNL, HUF, IDR, ILS, INR, ISK, JPY, KES, KGS, KHR, KRW, KWD, LAK, LKR, MAD, MNT, MOP, MUR, MXN, MYR, NAD, NGN, NIO, NOK, NPR, NZD, OMR, PEN, PHP, PKR, PLN, PYG, QAR, RON, RSD, RWF, SAR, SCR, SEK, SGD, SRD, THB, TND, TRY, TZS, UAH, UGX, USD, UYU, VND, ZAR. |
| sourceAmount | required | number | USD amount to convert (max 2 decimal places). |
| v2_offloader | optional | string | Pass true to receive fee fields calculated for the v2 transfer pipeline. When set, serviceFeePercent = (your fee bips + 20) / 100, serviceFeeFixed = 0, and providerCharge is absent. Use this when quoting before calling POST /api/payouts/transfer/v2. |
Example Success Response — v1 account (200)
{
"quoteId": "878c1cc4-025a-4ce2-a19d-920119ea6722",
"sourceAmount": 1000,
"providerCharge": 2,
"serviceFeePercent": 1.5,
"targetCurrency": "EUR",
"usdToTargetRate": 0.9183,
"quote": {
"targetAmount": 915.28,
"transferFee": 0.58,
"feeFxPercent": 0,
"feeFxAmount": 0,
"feePayoutAmount": 0.58,
"estimatedDelivery": "2026-03-26T15:52:37Z"
}
}Example Success Response — v2-offloader account, v2_offloader=true (200)
{
"quoteId": "4d0c2a5d-940b-4759-861f-4d660e5ae492",
"sourceAmount": 100,
"serviceFeePercent": 0.6,
"serviceFeeFixed": 0,
"targetCurrency": "KES",
"usdToTargetRate": 129.6,
"quote": {
"targetAmount": 12274,
"transferFee": 3.98,
"feeFxPercent": 1.15,
"feeFxAmount": 1.83,
"feePayoutAmount": 2.15,
"estimatedDelivery": "2026-05-26T03:24:37Z"
}
}cURL Example
curl "https://business.madhousewallet.com/api/payouts/quote?targetCurrency=EUR&sourceAmount=1000" \ -H "Authorization: Bearer mw_live_<keyId>_<secret>"
Response Codes
/api/payouts/transfer20 req/minExternal walletInitiate a stablecoin-to-fiat payout
Creates a payout and returns a unique deposit_address on the network you specify via source_network. Send exactly amount of source_token from wallet_address to that address — the platform detects the deposit and automatically routes the fiat payout to your recipient.
Account provisioning: your account must be explicitly enabled for this transfer method. If it is not provisioned, requests are rejected with 403 ("This transfer method is not enabled for your account.") — contact support to enable it.
Wallet verification: wallet_address must be a KYC-verified wallet. Unverified wallets are rejected with 403 — verify your wallet at kyc.madhousewallet.com.
Compliance screening: Every wallet_address is screened against a third-party risk database before the transfer is created. Wallets that fail return 403. The screener supports EVM addresses only, so wallet_address is currently restricted to the EVM format — Solana and Tron addresses are temporarily not accepted on this endpoint.
POST /api/payouts/send (internal)
Browser-only. MW signs and broadcasts from your Safe wallet via passkey confirmation.
POST /api/payouts/transfer ← you are here
API key endpoint. You send USDC to the deposit address. The rest of the pipeline runs identically.
Workflow
GET /api/payouts/deposit-options to list supported tokens and networksGET /api/payouts/quote → save the returned quoteIdPOST /api/payouts/transfer with source_token + source_network → receive deposit_address and transfer_idamount of source_token from wallet_address to deposit_address on source_networkrecipientId automatically — email confirmation sentRequest Body Fields
| Field | Required | Type | Description |
|---|---|---|---|
| quote_id | required | string | UUID returned by GET /api/payouts/quote (valid for 5 min, single-use) |
| amount | required | number | USD amount — must match the quote ±$0.01 (max 2 decimal places) |
| recipientId | required | integer | Recipient account ID from GET /api/payouts/recipients |
| customer_uuid | required | string (UUID) | Your own UUID for this transfer — must be unique per transfer; duplicate UUIDs are rejected with 409 |
| customer_email | required | string | Email of the end user for transfer status notifications. |
| source_token | required | string | Token to send. Supported values: usdc, usdt. See GET /api/payouts/deposit-options for supported token/network combinations. |
| source_network | required | string | Network to send from: arbitrum, avalanche, base, ethereum, polygon, solana, tron. The deposit address returned will be on this network. |
| wallet_address | required | string | The address on source_network that will send USDC/USDT to the deposit address. Currently restricted to EVM format (0x + 40 hex chars) because every transfer is pre-screened for compliance and the screener supports Ethereum-family addresses only. Solana and Tron addresses are temporarily rejected on this endpoint. |
| keep_recipient | optional | boolean | When true, the recipient account is not automatically deleted after the transfer reaches a terminal state (completed or failed). Use for saved/reusable recipients you manage yourself. Defaults to false — recipients are deleted automatically. |
Example Success Response (200)
{
"transfer": {
"id": "507f1f77bcf86cd799439011",
"type": "payout",
"amount": 1000,
"currency": "EUR",
"status": "ready_to_process",
"status_label": "Ready to Process",
"recipientId": 12345678,
"recipient": {
"id": 12345678,
"accountHolderName": "Jane Doe",
"currency": "EUR",
"type": "iban",
"country": "DE",
"details": {
"legalType": "PRIVATE",
"iban": "DE89370400440532013000"
}
},
"customerUuid": "550e8400-e29b-41d4-a716-446655440000",
"customerEmail": "user@example.com",
"sourceToken": "usdc",
"sourceNetwork": "base",
"quote": {
"sourceAmount": 1000,
"providerCharge": 2,
"serviceFeePercent": 1.5,
"targetCurrency": "EUR",
"usdToTargetRate": 0.9183,
"targetAmount": 915.28,
"transferFee": 0.58,
"estimatedDelivery": "2026-03-30T12:00:00Z"
},
"wallet_address": "0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B",
"deposit_address": "0xDe123456789aBcDEf1234567890aBcDeF1234567",
"error": null,
"refund_tx_hash": null,
"reference": "507f1f77bcf86cd799439011",
"timestamp": "2026-03-28T10:00:00.000Z"
}
}cURL Example
curl -X POST "https://business.madhousewallet.com/api/payouts/transfer" \
-H "Authorization: Bearer mw_live_<keyId>_<secret>" \
-H "Content-Type: application/json" \
-d '{"quote_id":"281901c4-19c6-462f-9c81-37fc6bbde340","amount":1000,"recipientId":12345678,"customer_uuid":"550e8400-e29b-41d4-a716-446655440000","customer_email":"user@example.com","source_token":"usdc","source_network":"base","wallet_address":"0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B","keep_recipient":false}'Response Codes
/api/payouts/transfer/v220 req/minv2 — USDC onlyInitiate a v2 transfer — no expiry
Creates a v2 transfer and returns a hardcoded deposit address for USDC on the network you specify. Unlike v1, there is no expiry window — you have unlimited time to send funds. After sending USDC on-chain, call POST /api/payouts/transfer/v2/confirm-transfer with the transaction hash to trigger the payout pipeline.
USDC only. For USDT transfers, use POST /api/payouts/transfer.
Wallet compliance screening: when screening is enabled for your account (the default), wallet_address is required, must be an EVM address (0x + 40 hex chars), and is screened before any other processing — a wallet that fails screening is rejected with 403. When screening is disabled for your account, wallet_address stays KYC-conditional and Solana addresses are accepted.
KYC verification: if your account requires KYC verification, wallet_address is required and must be a KYC-verified wallet or the request is rejected with 403 (verify at kyc.madhousewallet.com). If neither compliance screening nor KYC is enabled for your account, you may omit it.
Wallet compliance screening
When compliance screening is enabled for your account (the default), the wallet_address you provide is screened before the transfer is created — it must be an EVM address and a wallet that fails screening is rejected with 403. If screening is temporarily unavailable, the request fails closed with 503 — retry after a short delay. If screening is disabled for your account, vet sending wallets yourself: only submit wallets that you own or have independently verified.
POST /api/payouts/transfer (v1)
POST /api/payouts/transfer/v2 ← you are here
Workflow
GET /api/payouts/quote → save the returned quoteIdPOST /api/payouts/transfer/v2 with source_token + source_network → receive deposit_address and transfer_idamount USDC from wallet_address to deposit_address on source_networkPOST /api/payouts/transfer/v2/confirm-transfer with transfer_id and the on-chain tx_hashRequest Body Fields
| Field | Required | Type | Description |
|---|---|---|---|
| quote_id | required | string (UUID) | UUID returned by GET /api/payouts/quote (valid for 5 min, single-use) |
| amount | required | number | USD amount — must match the quote ±$0.01 (max 2 decimal places) |
| recipientId | required | integer | Recipient account ID from GET /api/payouts/recipients |
| customer_uuid | required | string (UUID) | Your own UUID for this transfer — must be unique per transfer; duplicate UUIDs are rejected with 409 |
| source_token | required | string | Must be usdc. USDT is not supported on v2. |
| source_network | required | string | Network to send from: base, solana, polygon, arbitrum, avalanche, ethereum |
| wallet_address | conditional | string | The address that will send USDC to the deposit address. Conditionally required — required when compliance screening or KYC verification is enabled for your account; may be omitted otherwise (stored and returned as an empty string). When compliance screening is enabled (the default), it must be an EVM address (0x + 40 hex chars) and is screened before processing; Solana addresses are rejected. When screening is disabled, both EVM and Solana (base58, 32–44 chars) addresses are accepted; if KYC is enabled it must be a KYC-verified wallet. |
| keep_recipient | optional | boolean | When true, the recipient is not automatically deleted after the transfer reaches a terminal state. Defaults to false. |
| customer_email | optional | string (email) | Email address to receive payout status notifications (sent and failed). If omitted, notifications go to the authenticated account's email. |
Example Success Response (200)
{
"transfer_id": "507f1f77bcf86cd799439011",
"deposit_address": "0xBC91bCF38e3c0DE1E3fD0cF50Be7c0e52D55D00e",
"source_token": "usdc",
"source_network": "base",
"amount": 500,
"currency": "EUR",
"wallet_address": "0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B",
"instructions": {
"send_amount": 500,
"send_token": "USDC",
"send_network": "base",
"deposit_address": "0xBC91bCF38e3c0DE1E3fD0cF50Be7c0e52D55D00e",
"note": "Send exactly 500 USDC on base to deposit_address, then call POST /api/payouts/transfer/v2/confirm-transfer with transfer_id and tx_hash to trigger the payout pipeline."
}
}cURL Example
curl -X POST "https://business.madhousewallet.com/api/payouts/transfer/v2" \
-H "Authorization: Bearer mw_live_<keyId>_<secret>" \
-H "Content-Type: application/json" \
-d '{"quote_id":"281901c4-19c6-462f-9c81-37fc6bbde340","amount":500,"recipientId":12345678,"customer_uuid":"550e8400-e29b-41d4-a716-446655440000","source_token":"usdc","source_network":"base","wallet_address":"0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B","customer_email":"customer@example.com"}'Response Codes
/api/payouts/transfer/v2/confirm-transfer20 req/minv2 onlyLink a transaction hash to a v2 transfer
After sending USDC on-chain to the deposit_address returned by POST /api/payouts/transfer/v2, call this endpoint with the transfer_id and the on-chain tx_hash. The platform uses the hash to identify and match your deposit to this transfer, then automatically initiates the fiat payout.
Replay attack prevention
Each tx_hash may only be used once across the entire platform — even across different accounts. A hash already associated with any transfer is rejected with 409. Never reuse a transaction hash.
Request Body Fields
| Field | Required | Type | Description |
|---|---|---|---|
| transfer_id | required | string | The transfer_id returned by POST /api/payouts/transfer/v2 |
| tx_hash | required | string | On-chain transaction hash. EVM: 0x + 64 hex chars (32 bytes). Solana: base58 signature (85–88 chars). |
Idempotency & Status Handling
| Scenario | HTTP | Behaviour |
|---|---|---|
| Same transfer_id + same tx_hash (re-call) | 200 | Idempotent — returns current status without changes |
| Same transfer_id + different tx_hash | 409 | Rejected — overwriting is not allowed |
| tx_hash already used on another transfer | 409 | Rejected — each hash is single-use globally |
| Deposit already detected (status beyond pending_match) | 200 | Returns current status — no changes needed |
| Transfer already in terminal state | 200 | Returns terminal status with informational message |
Example Success Response (200)
{
"transfer_id": "507f1f77bcf86cd799439011",
"status": "pending_match",
"message": "tx_hash recorded — the system will match your deposit and initiate the payout automatically"
}cURL Example
curl -X POST "https://business.madhousewallet.com/api/payouts/transfer/v2/confirm-transfer" \
-H "Authorization: Bearer mw_live_<keyId>_<secret>" \
-H "Content-Type: application/json" \
-d '{"transfer_id":"507f1f77bcf86cd799439011","tx_hash":"0xabc123def456abc123def456abc123def456abc123def456abc123def456abc1"}'Response Codes
/api/payouts/transfer/:transfer_id30 req/minGet transfer status
Returns the current status and details of a payout transfer using the transfer_id returned by POST /api/payouts/transfer. Lookup is global by transfer_id — any valid API key can retrieve any transfer (including dashboard-created transfers or those created with a different key) as long as you know its unguessable 24-character ID.
Status Values
ready_to_processWaiting for your USDC deposit to arriveprocessingDeposit received — funds being converted and routedtransfer_createdConversion complete — outgoing transfer to recipient createdcompletedOutgoing payment sent to recipientfailedTransfer failed — check the error fieldrefundedTransfer could not be completed — USDC returned to your wallet (see refund_tx_hash)Example Success Response (200)
{
"transfer": {
"id": "507f1f77bcf86cd799439011",
"type": "payout",
"amount": 1000,
"currency": "EUR",
"status": "processing",
"status_label": "Deposit received — processing",
"recipientId": 12345678,
"recipient": {
"id": 12345678,
"accountHolderName": "Jane Doe",
"currency": "EUR",
"type": "iban",
"country": "DE",
"details": {
"legalType": "PRIVATE",
"iban": "DE89370400440532013000"
}
},
"customerUuid": "550e8400-e29b-41d4-a716-446655440000",
"customerEmail": "user@example.com",
"sourceToken": "usdc",
"sourceNetwork": "base",
"quote": {
"sourceAmount": 1000,
"providerCharge": 2,
"serviceFeePercent": 1.5,
"targetCurrency": "EUR",
"usdToTargetRate": 0.9183,
"targetAmount": 915.28,
"transferFee": 0.58,
"estimatedDelivery": "2026-03-26T15:52:37Z"
},
"wallet_address": "0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B",
"error": null,
"refund_tx_hash": null,
"reference": "507f1f77bcf86cd799439011",
"timestamp": "2026-03-22T14:30:00.000Z",
"updated_at": "2026-03-22T14:32:00.000Z"
}
}cURL Example
curl "https://business.madhousewallet.com/api/payouts/transfer/507f1f77bcf86cd799439011" \ -H "Authorization: Bearer mw_live_<keyId>_<secret>"
Response Codes
The full OpenAPI 3.0 specification is available for code generators and API clients.
Download openapi.json