Skip to main content

Overview

Noah payouts let your client convert stablecoins to fiat and disburse through local payout rails.

1. Prerequisite: approved KYC

Ensure the client has completed Noah onboarding and has APPROVED status. See Noah KYC onboarding.

2. Get supported payout countries

curl --request GET \
  --url https://api.portalhq.io/api/v3/clients/me/integrations/noah/payouts/countries \
  --header 'Authorization: Bearer [token]'
The countries map includes many additional country/currency combinations based on Noah support.

3. Get payout channels

Call GET /payouts/channels with at least cryptoCurrency. country and fiatCurrency are optional filters; pass either or both to narrow results.
curl --request GET \
  --url 'https://api.portalhq.io/api/v3/clients/me/integrations/noah/payouts/channels?country=BR&cryptoCurrency=USDC_TEST&fiatCurrency=BRL' \
  --header 'Authorization: Bearer [token]'
In Noah’s sandbox, cryptocurrency tickers must be suffixed with _TEST (e.g. USDC_TEST). Drop the suffix in production. See Noah’s sandbox naming convention.
Optional query params:
  • fiatAmount — pricing context for the returned calculated.totalFee.
  • paymentMethodId — scope channels to a specific existing payment method.
  • pageSize — page size (1-100).
  • pageToken — pagination token from a previous response.

4. (Optional) Get saved payment methods

Call GET /payouts/payment-methods to list saved payout destinations for the approved customer. If you want to use a saved payout destination, you can pass its id as paymentMethodId in POST /payouts/quote.
curl --request GET \
  --url https://api.portalhq.io/api/v3/clients/me/integrations/noah/payouts/payment-methods \
  --header 'Authorization: Bearer [token]'
Example response (truncated to key fields):
{
  "data": {
    "paymentMethods": [
      {
        "id": "Bank/Ach/USD/021000021/123456789/cmmw6tzqc00003saqzbgwueb5",
        "paymentMethodCategory": "Bank",
        "customerId": "cmmw6tzqc00003saqzbgwueb5",
        "country": "US",
        "capabilities": {
          "payinTo": false,
          "payoutFrom": false,
          "payoutTo": true
        },
        "displayDetails": {
          "type": "FiatPaymentMethodBankDisplay",
          "accountNumber": "123456789",
          "bankCode": "021000021"
        },
        "accountHolderDetails": {
          "name": {
            "firstName": "John",
            "lastName": "Mock-Doe"
          }
        },
        "issuerDetails": {}
      }
    ]
  }
}
pageToken may also be present when pagination is returned.

5. Get dynamic form for selected channel

Use GET /payouts/channels/:channelId/form and render the returned formSchema.
curl --request GET \
  --url https://api.portalhq.io/api/v3/clients/me/integrations/noah/payouts/channels/[channelId]/form \
  --header 'Authorization: Bearer [token]'
Example response (truncated to key fields):
{
  "data": {
    "formMetadata": {
      "contentHash": "eda5d74d98e3e7429644fd7f74d4979638eca0e9aa7422091a600d85c1f3422a"
    },
    "formSchema": {
      "$schema": "http://json-schema.org/draft-07/schema#",
      "type": "object",
      "properties": {
        "AccountHolderName": {
          "type": "object",
          "title": "Account Holder Name"
        },
        "AccountHolderAddress": {
          "type": "object",
          "title": "Account Holder Address"
        },
        "BankDetails": {
          "type": "object",
          "title": "Bank Details"
        },
        "PaymentPurpose": {
          "type": "string",
          "title": "Payment Purpose"
        }
      },
      "required": [
        "AccountHolderName",
        "AccountHolderAddress",
        "BankDetails",
        "PaymentPurpose"
      ]
    }
  }
}
formSchema can include additional nested validation rules (allOf, if/then, minLength, maxLength, pattern) that should be rendered and validated in your form UI.

6. Quote payout

Call POST /payouts/quote to validate form data and get fee + estimate. paymentMethodId is optional and is only needed when quoting against a saved payment method from step 4. Provide exactly one of fiatAmount or cryptoAmount:
  • fiatAmount — amount the recipient receives. The quote derives the equivalent crypto.
  • cryptoAmount — exact crypto the depositor will send. The quote derives the equivalent fiat (returned in the response’s fiatAmount disclosure field).
Pass quoted: true to request a fixed-rate quote. When quoted is true and all form steps are complete, the response includes a quote object with a signedQuote and expiry issued by Noah. Forward quote.signedQuote unchanged into the Quoted trigger on POST /payouts (as trigger.SignedQuote) to lock the rate end-to-end. See the trigger variants in step 7. Optional businessFee applies a per-transaction business fee on top of the customer’s payout (fixed FeeBase and/or FeePct in the given FiatCurrency).
curl --request POST \
  --url https://api.portalhq.io/api/v3/clients/me/integrations/noah/payouts/quote \
  --header 'Authorization: Bearer [token]' \
  --header 'Content-Type: application/json' \
  --data '{
    "channelId": "214eab50-e22b-5e0f-b487-37dc1addee90",
    "cryptoCurrency": "USDC_TEST",
    "fiatAmount": "0.01",
    "fiatCurrency": "USD",
    "form": {
      "AccountHolderName": {
        "AccountHolderType": "Individual",
        "Name": {
          "FirstName": "John",
          "LastName": "Mock-Doe"
        }
      },
      "AccountHolderAddress": {
        "Address": "123 Main St",
        "City": "Miami",
        "PostalCode": "33139",
        "State": "FL"
      },
      "BankDetails": {
        "AccountNumber": "900711447286",
        "BankCode": "101019644",
        "AccountType": "Checking"
      },
      "PaymentPurpose": "Personal transfer",
      "Reference": "Optional reference"
    }
  }'
Example response (truncated to key fields):
{
  "data": {
    "payoutId": "cmmyatsdk0000wy2en3lof5jj",
    "formSessionId": "d5f81409-5306-476b-84d6-6be2b5656a5a",
    "cryptoAmountEstimate": "0.010004",
    "cryptoAuthorizedAmount": "0.010004",
    "totalFee": "0.01",
    "cryptoCurrency": "USDC_TEST",
    "fiatCurrency": "USD",
    "fiatAmount": "0.01",
    "rate": "0.999",
    "breakdown": [
      { "type": "ChannelFee", "amount": "0.000004" },
      { "type": "BusinessFee", "amount": "0" },
      { "type": "Remaining", "amount": "0.01" }
    ]
  }
}
When quoted: true was sent in the request and all form steps are complete, the response also includes a quote object:
{
  "data": {
    "quote": {
      "signedQuote": "eyJhbGciOi...",
      "expiry": "2026-03-24T08:00:55Z"
    }
  }
}
To lock the rate, pass quote.signedQuote unchanged on POST /payouts inside the Quoted trigger as trigger.SignedQuote. The deposit must arrive and finalize before quote.expiry; otherwise the workflow proceeds at the market rate at execution time.

7. Initiate payout

Call POST /payouts using payoutId returned from quote. nonce must be unique per transaction attempt (max 36 characters). Reuse the same nonce only when retrying the same payout request for idempotency.
curl --request POST \
  --url https://api.portalhq.io/api/v3/clients/me/integrations/noah/payouts \
  --header 'Authorization: Bearer [token]' \
  --header 'Content-Type: application/json' \
  --data '{
    "payoutId":"cmmyatsdk0000wy2en3lof5jj",
    "sourceAddress":"DEeUjZtzWWiM6sJPBFsR5iL9AeSEtvtuh4ycHM4LHyTK",
    "expiry":"2026-03-24T23:59:59Z",
    "nonce":"nonce-20260320-02388933",
    "network":"solana:devnet"
  }'
Example response:
{
	"data": {
		"destinationAddress": "4dguFohaa8F9MvRtZecGsHCvnGqzYPyGyusP6BWu4zLK",
		"conditions": [
			{
				"amountConditions": [
					{
						"comparisonOperator": "EQ",
						"value": "0.010004"
					}
				],
				"cryptoCurrency": "USDC_TEST",
				"destinationAddress": "4dguFohaa8F9MvRtZecGsHCvnGqzYPyGyusP6BWu4zLK",
				"network": "SolanaDevnet"
			}
		]
	}
}
After receiving this response, submit the onchain transfer to the returned destinationAddress and ensure it satisfies the returned conditions (network and amount constraints).

Trigger variants

You can pass an explicit trigger to control how the sell rule fires. The Type field selects the variant:
  • SingleOnchainDepositSourceTriggerInput (default) — executes once. Conditions[*] must carry AmountConditions. Portal forwards the persisted fiatAmount from the quote when set.
  • PermanentOnchainDepositSourceTriggerInput — fires on every matching deposit; the entire deposited crypto amount is always sold. Conditions[*] carries Network only — omit AmountConditions. Optional NetworkAgnostic: true matches deposits on any network.
  • QuotedOnchainDepositSourceTriggerInput — executes once and locks the conversion rate from a prior SellQuote. Requires SignedQuote on the trigger (from quote.signedQuote returned by POST /payouts/quote). Conditions[*] carries Network only — omit AmountConditions. Expiry is optional and defaults to the bound quote’s expiry.
Permanent trigger (no AmountConditions):
{
  "trigger": {
    "Type": "PermanentOnchainDepositSourceTriggerInput",
    "Conditions": [{ "Network": "SolanaDevnet" }],
    "SourceAddress": "DEeUjZtzWWiM6sJPBFsR5iL9AeSEtvtuh4ycHM4LHyTK",
    "Expiry": "2026-03-24T23:59:59Z",
    "Nonce": "nonce-20260320-02388933",
    "NetworkAgnostic": false
  }
}
Quoted trigger (carry the signedQuote returned by POST /payouts/quote as trigger.SignedQuote; no AmountConditions; Expiry defaults to the bound quote’s expiry):
{
  "trigger": {
    "Type": "QuotedOnchainDepositSourceTriggerInput",
    "SignedQuote": "eyJhbGciOi...",
    "Conditions": [{ "Network": "SolanaDevnet" }],
    "SourceAddress": "DEeUjZtzWWiM6sJPBFsR5iL9AeSEtvtuh4ycHM4LHyTK",
    "Nonce": "nonce-20260320-02388933"
  }
}
ComparisonOperator values for the Single trigger: EQ, LTEQ, GTEQ. You can also pass an optional businessFee at the top level of the payout request — same shape as on POST /payouts/quote.

8. Configure Noah webhooks for payout updates

Set up Noah webhooks in Noah Dashboard to receive payout lifecycle and transaction updates directly from Noah.
  • Subscribe to Transaction events for payout execution and completion updates.
  • Noah Webhooks

9. Monitor payout progress

Use Transaction webhook events and Noah Dashboard delivery logs to track payout execution and reconciliation.
POST /payouts only works for payout intents in PREPARED status (created by POST /payouts/quote).