Skip to main content
The Flutter SDK exposes Noah virtual accounts and global payouts through portal.ramps.noah. Each method forwards to the native iOS or Android SDK, which calls Portal’s Noah integration on the Client API using your client API key. You do not call Noah’s servers directly from the app.
For dashboard setup, signing keys, and supported CAIP-2 networks, see Noah integration overview. For HTTP shapes and webhooks, see the Noah workflow guides and Noah Business API / EMM documentation.

Prerequisites

Architecture

All request and response types are exported from package:portal_flutter/portal_flutter.dart.

Supported networks

Every network parameter takes a CAIP-2 chain identifier. Use the constants on NoahNetwork rather than string literals to avoid typos. The backend also accepts solana:mainnet and solana:devnet as aliases for the two Solana networks. It rejects any other value, including non-CAIP-2 strings such as "ethereum", with a 400.
Testnet networks pair with sandbox-only test assets such as USDC_TEST.

Types and responses

Successful responses use an envelope { data, metadataJson }. For example, NoahInitiateKycResponse is { data: NoahInitiateKycData(hostedUrl), metadataJson }. Every response also has a metadata getter that decodes metadataJson into a Map<String, dynamic>?. Types that cross the platform channel follow a few Flutter-specific conventions:
  • Free-form JSON is passed as a string. Fields with no fixed shape end in Json. On requests, encode the value with jsonEncode (formJson, metadataJson). The value must be a JSON object: invalid JSON, or an array or other non-object value, is rejected before any network call with the method’s NOAH_*_ERROR code. An empty or whitespace-only string is treated as absent. On responses, read the decoded getter instead of the raw string: The getters return null (or an empty map for countries) when the value is absent or malformed, rather than throwing.
  • Enum-like fields are strings. customerType, capability, stepType, and comparisonOperator are String fields. Use the Dart enums to produce and parse them: pass .value when building a request, and use fromValue to read a response. An unknown customerType or capability string is rejected before any network call with the method’s NOAH_*_ERROR code. comparisonOperator is not checked by the SDK and is validated by the backend.
  • Lists have nullable elements. List fields are typed List<T?>, so iterate with whereType<T>().
All nine methods return a Future and throw PortalException on failure. See Error handling.

initiateKyc

Starts hosted Noah onboarding. The SDK returns the URL but does not open it, so open data.hostedUrl in the system browser yourself. The example below uses the url_launcher package. Validate HTTPS and the hostname against the checkout domains Noah documents for your environment (extend the example allowlist accordingly).
Signature
The returnUrl must be an HTTPS URL. Custom app schemes (for example myapp://callback) are not supported. For mobile applications, the recommended pattern is to use an HTTPS bridge page that redirects to a deep link after KYC completion.
Returns — NoahInitiateKycResponse: { data: { hostedUrl: String }, metadataJson: String? }.
While the customer’s KYC is pending, each call starts a new onboarding session and returns a new hostedUrl. Once KYC is approved or declined, it returns the stored hostedUrl without starting a new session.
This call only starts onboarding; KYC outcome and status changes arrive asynchronously via Noah Customer webhooks. See Noah webhooks.
See also: Noah KYC guide, Noah hosted flows.

initiatePayin

Creates a fiat-to-stablecoin payin and returns bank instructions and a payinId. Use a supported network and the user’s wallet address as destinationAddress.
Signature
Returns — NoahInitiatePayinResponse: { data: { payinId: String, bankDetails: NoahBankDetails }, metadataJson: String? }. NoahBankDetails includes:
  • paymentMethodId — payment method identifier
  • paymentMethodType — payment rail type, for example BankSepa or IdentifierPix
  • accountNumber — bank account number
  • cryptoCurrency — crypto currency for this payin
  • network — network identifier
  • fee — fee breakdown (NoahFeeDetails: fiatCurrencyCode, totalFeePct, totalFeeBase, totalFeeMin)
  • accountHolderName — optional account holder name
  • bankCode — optional bank routing or sort code
  • bankName — optional bank name
  • bankAddress — optional NoahBankAddress with street, street2, city, postCode, state, country
  • reference — optional payment reference
  • relatedPaymentMethods — optional List<NoahBankToAddressRelatedPaymentMethod?>
NoahBankDetails carries sensitive financial data — accountNumber, accountHolderName, bankCode, and bankAddress. Render it to the user as deposit instructions and keep it in memory; do not write it to logs, crash reports, or analytics.
Payin lifecycle updates are asynchronous; track them with Noah FiatDeposit and Transaction webhooks, not by polling this SDK response. See Noah webhooks.
See also: Payins, FiatDeposit webhooks.

simulatePayin

Simulates a fiat deposit against a payment method so you can exercise the payin flow end to end without moving real money. Use the bankDetails.paymentMethodId returned by initiatePayin.
Signature
Returns — NoahSimulatePayinResponse: { data: { fiatDepositId: String, reference: String? }, metadataJson: String? }.
This endpoint is sandbox-only. Calling it against a production environment fails with a 400 client error — Simulate fiat deposit is only available in sandbox environments. Guard the call behind your app’s own sandbox/production configuration — for example, the setting that selects your sandbox API key — rather than shipping it in a production code path. Don’t rely on kDebugMode alone: a debug build can point at production, and a release build at sandbox.

getPayoutCountries

Lists countries available for fiat payouts, keyed by country code with the supported fiat currency codes for each.
Signature
Returns — NoahGetPayoutCountriesResponse: { data: { countriesJson: String }, metadataJson: String? }. Read the map through the data.countries getter, typed Map<String, List<String>>.

getPayoutChannels

Returns payout rails for a given crypto asset. Only cryptoCurrency is required; country and fiatCurrency narrow the results, and fiatAmount refines channel availability.
Signature
Returns — NoahGetPayoutChannelsResponse: { data: { items: List<NoahChannel?>, pageToken: String? }, metadataJson: String? }. Each NoahChannel includes:
  • id — channel identifier
  • paymentMethodCategory — broad grouping such as Bank, Card, or Identifier
  • paymentMethodType — payment rail type, for example BankSepa or IdentifierPix
  • fiatCurrency — fiat currency code
  • country — ISO country code
  • limits — NoahChannelLimits with minLimit and optional maxLimit
  • rate — exchange rate as a string
  • processingSeconds — estimated settlement time
  • calculated — optional NoahChannelCalculated with totalFee
  • paymentMethods — optional List<NoahChannelPaymentMethodDisplay?>
  • processingTier — optional settlement speed tier such as Standard or Priority
  • formSchemaJson — optional inline JSON Schema for the channel’s payout form; read it decoded through the formSchema getter
  • formMetadata — optional NoahFormMetadata with contentHash
  • issuer — optional issuer identifier
When a channel already carries a formSchema, use it directly instead of calling getPayoutChannelForm — it saves a round-trip.
See also: Payouts.

getPayoutChannelForm

Loads the dynamic form schema for a channel so you can collect recipient fields before requesting a quote.
Signature
Returns — NoahGetPayoutChannelFormResponse: { data: { formSchemaJson: String?, formMetadata: NoahFormMetadata? }, metadataJson: String? }. Read the schema decoded through the data.formSchema getter.
The form schema depends on the region the customer completed KYC in. Build the formJson you send to getPayoutQuote from the schema returned for that customer, not from a fixed payload.

getPayoutQuote

Requests fees and crypto amount estimates for a payout. Include formJson when the channel requires recipient data.
Signature

Choosing the amount denomination

fiatAmount and cryptoAmount are mutually exclusive. Set exactly one: the SDK checks this in Dart before calling the native SDK, and throws a PortalException with code INVALID_PAYOUT_QUOTE_AMOUNT if you pass both or neither.
Returns — NoahGetPayoutQuoteResponse: data includes payoutId, totalFee, cryptoAmountEstimate, cryptoAuthorizedAmount, formSessionId, and optional cryptoCurrency, fiatCurrency, fiatAmount, rate, breakdown, quote ({ signedQuote, expiry }), and nextStep. Each breakdown entry is a NoahTransactionBreakdownItem with a type of ChannelFee, BusinessFee, or Remaining, plus an amount. When the form has more screens to collect, nextStep is a NoahFormNextStep with a stepId, a stepType string (parse it with NoahFormNextStepType.fromValue), and the next screen’s schema through the schema getter. Send the next screen with the returned formSessionId.
Set quoted: true if you intend to submit the payout with a quoted trigger — that variant requires the signedQuote returned here.

initiatePayout

Executes a payout after quoting. Returns the destination address and the on-chain deposit conditions your transfer must satisfy.
Signature
Returns — NoahInitiatePayoutResponse: { data: { destinationAddress: String?, conditions: List<NoahDepositSourceTriggerCondition?>?, ruleId: String? }, metadataJson: String? }. destinationAddress is optional because it is derived from conditions[0].destinationAddress and is null when that shape is missing. ruleId is returned for permanent and quoted triggers.

Deposit source triggers

The payout must be authorized by a trigger. If you omit trigger, Portal builds a single trigger from the quote (an EQ amount condition on its cryptoAmountEstimate) using the network, sourceAddress, expiry, and nonce you passed. Pass trigger to use a permanent or quoted trigger, or your own amount conditions. NoahOnchainDepositSourceTrigger has one nullable field per variant. Set exactly one of them — the SDK throws a PortalException with code INVALID_PAYOUT_TRIGGER before calling the native SDK if you set none or more than one. Use the same sourceAddress, expiry, and nonce on the trigger as on the request:
The SDK does not send the funding transaction. After this call returns destinationAddress and conditions, submit the on-chain transfer to satisfy them. You can do this with any wallet — including Portal’s own send method (portal.sendAsset(...)) on the same Portal instance, which builds, signs, and broadcasts in one call.
This call initiates the payout flow; completion and failures are reported asynchronously via Noah Transaction webhooks. See Noah webhooks.
See also: Transaction events, automated payout recipes.

getPaymentMethods

Returns payment methods available to the customer, including a pagination token when more results are available.
Omit request to use server-side defaults:
Signature
Returns — NoahGetPaymentMethodsResponse: { data: { paymentMethods: List<NoahPaymentMethod?>, pageToken: String? }, metadataJson: String? }. Each NoahPaymentMethod includes id, paymentMethodCategory, country, and displayDetails, plus optional customerId, capabilities, accountHolderDetails, and issuerDetails.

Error handling

All Noah methods throw a PortalException on failure. Catch it with on PortalException catch (e) and branch on e.code. Log or surface errors without printing full API responses in production if they might contain sensitive identifiers. Retry only idempotent reads unless your product team confirms otherwise. The NOAH_*_ERROR codes are the same on iOS and Android. They also cover server errors, responses missing a required field, and input rejected before the network call — an unknown customerType or capability string, or a formJson / metadataJson that is not a JSON object. If Portal rejects the API key or Client Session Token, the call throws CREDENTIAL_REJECTED instead.
e.message includes the backend’s error on both platforms, but its format differs between iOS and Android. Branch on e.code, not on e.message.
Noah methods do not accept a trace ID, because the native Noah APIs do not support one yet.