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
- An initialized
Portalclient with a wallet and API access. - Noah enabled for your Portal environment and configured in the dashboard.
- For payins and payouts, the end user must complete Noah KYC with approved status before those flows succeed.
Architecture
All request and response types are exported from
package:portal_flutter/portal_flutter.dart.
Supported networks
Everynetwork 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.
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 withjsonEncode(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’sNOAH_*_ERRORcode. An empty or whitespace-only string is treated as absent. On responses, read the decoded getter instead of the raw string:The getters returnnull(or an empty map forcountries) when the value is absent or malformed, rather than throwing. -
Enum-like fields are strings.
customerType,capability,stepType, andcomparisonOperatorareStringfields. Use the Dart enums to produce and parse them: pass.valuewhen building a request, and usefromValueto read a response. An unknowncustomerTypeorcapabilitystring is rejected before any network call with the method’sNOAH_*_ERRORcode.comparisonOperatoris not checked by the SDK and is validated by the backend. -
Lists have nullable elements. List fields are typed
List<T?>, so iterate withwhereType<T>().
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 opendata.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).
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.NoahInitiateKycResponse: { data: { hostedUrl: String }, metadataJson: String? }.
This call only starts onboarding; KYC outcome and status changes arrive asynchronously via Noah
Customer webhooks. See Noah webhooks.initiatePayin
Creates a fiat-to-stablecoin payin and returns bank instructions and apayinId. Use a supported network and the user’s wallet address as destinationAddress.
Returns —
NoahInitiatePayinResponse: { data: { payinId: String, bankDetails: NoahBankDetails }, metadataJson: String? }.
NoahBankDetails includes:
paymentMethodId— payment method identifierpaymentMethodType— payment rail type, for exampleBankSepaorIdentifierPixaccountNumber— bank account numbercryptoCurrency— crypto currency for this payinnetwork— network identifierfee— fee breakdown (NoahFeeDetails:fiatCurrencyCode,totalFeePct,totalFeeBase,totalFeeMin)accountHolderName— optional account holder namebankCode— optional bank routing or sort codebankName— optional bank namebankAddress— optionalNoahBankAddresswithstreet,street2,city,postCode,state,countryreference— optional payment referencerelatedPaymentMethods— optionalList<NoahBankToAddressRelatedPaymentMethod?>
Payin lifecycle updates are asynchronous; track them with Noah
FiatDeposit and Transaction webhooks, not by polling this SDK response. See Noah 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 thebankDetails.paymentMethodId returned by initiatePayin.
Returns —
NoahSimulatePayinResponse: { data: { fiatDepositId: String, reference: String? }, metadataJson: String? }.
getPayoutCountries
Lists countries available for fiat payouts, keyed by country code with the supported fiat currency codes for each.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. OnlycryptoCurrency is required; country and fiatCurrency narrow the results, and fiatAmount refines channel availability.
Returns —
NoahGetPayoutChannelsResponse: { data: { items: List<NoahChannel?>, pageToken: String? }, metadataJson: String? }.
Each NoahChannel includes:
id— channel identifierpaymentMethodCategory— broad grouping such asBank,Card, orIdentifierpaymentMethodType— payment rail type, for exampleBankSepaorIdentifierPixfiatCurrency— fiat currency codecountry— ISO country codelimits—NoahChannelLimitswithminLimitand optionalmaxLimitrate— exchange rate as a stringprocessingSeconds— estimated settlement timecalculated— optionalNoahChannelCalculatedwithtotalFeepaymentMethods— optionalList<NoahChannelPaymentMethodDisplay?>processingTier— optional settlement speed tier such asStandardorPriorityformSchemaJson— optional inline JSON Schema for the channel’s payout form; read it decoded through theformSchemagetterformMetadata— optionalNoahFormMetadatawithcontentHashissuer— optional issuer identifier
getPayoutChannelForm
Loads the dynamic form schema for a channel so you can collect recipient fields before requesting a quote.
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. IncludeformJson when the channel requires recipient data.
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.
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.
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 omittrigger, 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.getPaymentMethods
Returns payment methods available to the customer, including a pagination token when more results are available.request to use server-side defaults:
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 aPortalException 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.