Transfer Funds API
Overview
The Transfer Funds API moves money in a single-call, eliminating the multi-step quote flow. Real-Time Payment (RTP) and Instant Network Transfer (INT) rails are supported.
- For Instant Network Transfers, cards must be tokenized (Step 1) as a reusable payment method (Step 2).
- For Real-Time Payments, bank accounts can be verified and tokenized, but don't have to be; they can also be provided directly via account/routing numbers.
PCI scope
You have three options when tokenizing data, each with different PCI burdens:
- Direct API call: When you tokenize directly with tokenizeCardPaymentMethod, raw card data passes through your servers and puts you in full PCI DSS scope.
- Highnote SDK: When you use a Highnote SDK, card data never touches your servers and passes from the customer's browser to Highnote. You stay out of PCI scope, or at a much lower level like SAQ A.
- PCI-compliant vendor: Using a vendor is the middle ground. You can call the API directly but the PCI compliant vendor handles the PCI-sensitive part, keeping you out of full PCI scope.
Payment strategy
Specify the rail in preferredPaymentStrategy:
| Strategy | Rail | Description |
|---|---|---|
[RTP] | Real-Time Payments | Push funds from a Highnote FinancialAccount to an external bank account via destination.usBankAccount with bankTransferDetails. |
[INSTANT_NETWORK_TRANSFER] | Instant Network Transfer | Push funds to or pull funds from a tokenized external card via Visa Direct or Mastercard Send. |
[] or not provided | — | Fails with USER_ERROR listing supported strategies. |
Transfer options
Each row below shows the calls required to complete one transfer; an empty cell means the flow ends at the previous step. Rows 1–3 complete the transfer in a single transferFunds call.
Row 4 shows the UnifiedFundsTransfer flow, which separates fetching a fee quote (createUnifiedFundsTransferQuote) from initiating the transfer (initiateUnifiedFundsTransfer) so you can show the customer fees before committing.
| Rail | Step 1 | Step 2 | Step 3 |
|---|---|---|---|
| RTP | transferFunds | ||
| RTP | Tokenize account | transferFunds | |
| INT | Tokenize card (as a reusable token) | transferFunds | |
| INT | Tokenize card (as a reusable token) | createUnifiedFundsTransferQuote | initiateUnifiedFundsTransfer |
Consent Requirements
- RTP Transfers: Consent is required via
bankTransferDetails.consent. See Consent below for the required fields. - Instant Network Transfers: Consent is not required; card authorization serves as implicit consent.
Source and destination
- The
sourcecan be a Highnote FinancialAccount ID or a PaymentMethodToken ID. - The
destinationcan be a Highnote FinancialAccount ID, a PaymentMethodToken ID, or raw US bank account details provided inline. - The amount can be defined under
source.amountordestination.amount.
Network fees
Fees Highnote charges you for using the rail.
- RTP: Fees are invoiced monthly.
- INT: Fees are charged to the subscriber per transaction.
Customer fees
Use customerFees on transferFunds to collect a subscriber-defined fee atomically with the transfer — for example, a convenience fee, service fee, or processing fee. Each entry in the array describes one fee.
Each TransferFundsCustomerFeeInput accepts:
| Field | Type | Description |
|---|---|---|
customerFeeCode | String! | Code identifying the fee category — e.g., "CONVENIENCE_FEE", "SERVICE_FEE", "PROCESSING_FEE". You define this value. |
description | String | Optional memo for the fee. |
fixedAmount | AmountInput | Fixed fee amount. Combined with basisPoints if both are provided. |
basisPoints | Int | Fee as basis points of the transfer amount; 100 = 1%. Cannot exceed 10000 (100%). |
minAmount | AmountInput | Floor: if the calculated fee falls below this, the fee is raised to minAmount. |
maxAmount | AmountInput | Cap: if the calculated fee exceeds this, the fee is capped to maxAmount. |
chargeFrom | TransferFundsChargeFromType | SOURCE (added to the source debit) or DESTINATION (subtracted from the destination credit). |
destination | TransferFundsCustomerFeeDestinationInput | The Highnote account that receives the fee, as { id: <FINANCIAL_ACCOUNT_ID> }. |
Provide at least one of fixedAmount or basisPoints. The calculated fee is fixedAmount + (transferAmount × basisPoints / 10000), then bounded by minAmount and maxAmount if either is provided. Currency on every amount must match the transfer currency.
Charge from source
When chargeFrom: SOURCE, the fee is added to the source debit so the destination receives the full transfer amount.
{
"customerFees": [
{
"customerFeeCode": "CONVENIENCE_FEE",
"basisPoints": 250,
"minAmount": { "currencyCode": "USD", "value": 100 },
"maxAmount": { "currencyCode": "USD", "value": 1000 },
"chargeFrom": "SOURCE",
"destination": { "id": "<FINANCIAL_ACCOUNT_ID>" },
"description": "2.5% convenience fee"
}
]
}
For a $500.00 transfer with this fee, the source is debited $510.00, the destination receives $500.00, and the fee account receives $10.00.
Charge from destination
When chargeFrom: DESTINATION, the fee is subtracted from the destination credit so the source pays the full transfer amount.
{
"customerFees": [
{
"customerFeeCode": "SERVICE_FEE",
"fixedAmount": { "currencyCode": "USD", "value": 100 },
"minAmount": { "currencyCode": "USD", "value": 250 },
"chargeFrom": "DESTINATION",
"destination": { "id": "<FINANCIAL_ACCOUNT_ID>" },
"description": "Service fee (min $2.50)"
}
]
}
For a $50.00 transfer with this fee, the source is debited $50.00, the destination receives $47.50 (the $1.00 fixed fee was raised to the $2.50 floor), and the fee account receives $2.50.
Real-Time Payments
Real-Time Payments (RTP) is a push payment; funds are pushed from a Highnote FinancialAccount to an external bank account.
There are three ways to pass account information on the RTP rail:
- Inline raw details via
destination.usBankAccount - Verified ExternalFinancialBankAccount ID via
destination.id(with Plaid or Finicity) - NonVerifiedExternalUSFinancialBankAccount ID via
destination.id(stored but unverified)
While you can use a verified bank account and create an ExternalFinancialBankAccount, you can also provide bank account details inline via destination.usBankAccount.
This bypasses third-party bank verification (Plaid/Finicity) entirely.
The bank account details (account number, routing number) are provided directly in the request (no Plaid verification required).
RTP transfers require bankTransferDetails on the source, which captures payment-related information and the customer's consent. See Consent and Idempotency below.
Consent
RTP requires you to record the customer's consent inside bankTransferDetails on the source. TransferFundsBankTransferDetailsInput has two required fields:
| Field | Type | Description |
|---|---|---|
paymentRelatedInformation | String! | Payment details that may surface to the recipient (e.g., "Invoice 0123456789 Payment 999.99 USD"). Limited to 140 characters and a restricted character set. See Payment information rules. Availability and presentation depend on the receiving institution. |
consent | TransferAgreementConsentInput! | The customer's consent record. |
The TransferAgreementConsentInput object captures who authorized the transfer, when, and which consent template they accepted:
| Field | Type | Description |
|---|---|---|
authorizedPersonId | ID! | ID of the individual authorizing the transfer. Use the accountHolderId for Person account holders, or the primaryAuthorizedPersonId for Business account holders. |
consentTimestamp | String! | ISO 8601 timestamp marking when consent was given (for example, 2026-04-25T14:30:00Z). |
template.consentTemplateId | String! | Identifier of the consent template your customer accepted. You define this value. |
template.consentTemplateVersion | String! | Version of the consent template your customer accepted. You define this value. |
{
"bankTransferDetails": {
"paymentRelatedInformation": "Invoice 0123456789 Payment 999.99 USD",
"consent": {
"authorizedPersonId": "<AUTHORIZED_PERSON_ID>",
"consentTimestamp": "2026-04-25T14:30:00Z",
"template": {
"consentTemplateId": "<YOUR_CONSENT_TEMPLATE_ID>",
"consentTemplateVersion": "<YOUR_CONSENT_TEMPLATE_VERSION>"
}
}
}
}
Payment information rules
When you submit a transfer, Highnote validates and normalizes your paymentRelatedInformation.
Highnote stores these normalized values and forwards them to the receiving bank, so the recipient may see a cleaned-up version of what you sent.
If and how paymentRelatedInformation is displayed to recipients is up to the receiving banks. Treat it as a best-effort memo, not a guaranteed delivery channel.
Character rules
On an RTP transfer, paymentRelatedInformation can have a maximum of 140 characters. Length is counted on the value you submit, before the whitespace normalization described below — collapsing spaces or trimming line breaks does not bring an over-length value under the limit.
- Supported characters: Letters (
A–Z,a–z), digits (0–9), spaces, and the punctuation. , : / ( ) ' + ? & @ = _ ! # $ % * ; { } [ ] ^ | ~ \and-. - Unsupported characters:
",<,>, and`, plus emojis, accented or non-Latin letters, and any other character outside the supported set above.
The supported set includes ', &, =, and \, which are enough to break out of an HTML attribute or a JavaScript string. paymentRelatedInformation is also shared across rails, and the other bank-transfer rails accept ", <, and >. Escape the value for its output context before you render it, the same as any other user-supplied text.
RTP has stricter character rules than the other bank-transfer rails (although ACH has stricter length rules). As paymentRelatedInformation is shared across rails, the field reference lists the broadest accepted character set. For RTP, the stricter rules described here apply.
A transfer rejected for an unsupported character or length returns INVALID_INPUT or MAX_LENGTH_REACHED as a synchronous UserError, or — if reported after the transfer was created — failureReason: PAYMENT_RELATED_INFORMATION_INVALID. See RTP Transfer Error Codes for the full sync/async breakdown.
Whitespace rules
Only three whitespace characters are normalized rather than rejected: carriage return (\r), line feed (\n), and tab (\t).
- Carriage returns, line feeds, and tabs are each replaced with a single space.
- Runs of spaces are collapsed to one.
- Leading and trailing spaces are trimmed.
Any other whitespace — vertical tab, form feed, non-breaking space, or other Unicode whitespace — is not in the supported character set above and is rejected rather than normalized.
Blank or whitespace-only values are treated as no payment information. For example, a pasted multi-line memo is flattened to a single line:
"2,161.92\n\nFyntek invoice" → "2,161.92 Fyntek invoice"
Idempotency
Provide an idempotencyKey on every transferFunds request to prevent duplicate transfers when retrying after network errors or timeouts.
- Generate a fresh UUID v4 per logical transfer.
- Reuse the same key when retrying so Highnote recognizes the request as a retry rather than creating a duplicate transfer.
See Idempotency for the full specification.
Push to verified bank account
RTP transfers are asynchronous. The initial response status is typically PROCESSING. The final COMPLETED or FAILED status arrives via notification events.
transferFunds — RTP (inline bank details)
Query
mutation TransferFunds($input: TransferFundsInput!) {
transferFunds(input: $input) {
__typename
... on UnifiedFundsTransfer {
id
status
failureReason
createdAt
updatedAt
source {
node {
... on Node {
id
}
... on FinancialAccount {
id
}
}
amount {
currencyCode
value
decimalPlaces
}
}
destination {
node {
... on USBankAccount {
last4
routingNumber
accountType
name {
givenName
middleName
familyName
}
}
}
amount {
currencyCode
value
decimalPlaces
}
}
externalIdentifier
idempotencyKey
memo
steps {
__typename
... on UnifiedFundsTransferInitiateRequestStep {
id
status
failureReason
createdAt
}
... on UnifiedFundsTransferRtpStep {
id
status
failureReason
createdAt
transfer {
id
status
}
}
}
}
}
}
Variables
{ "input": { "source": { "id": "ac_merchant_settlement_account", "amount": { "currencyCode": "USD", "value": "50000" } }, "destination": { "usBankAccount": { "accountNumber": "123456789", "routingNumber": "021000021", "accountType": "CHECKING", "firstName": "John", "middleName": "Michael", "lastName": "Doe" }, "bankTransferDetails": { "paymentRelatedInformation": "Invoice #12345 payment", "consent": { "consentTimestamp": "2026-01-20T14:30:00Z", "authorizedPersonId": "per_john_doe_123", "template": { "consentTemplateId": "rtp_authorization_v1", "consentTemplateVersion": "2.0" } } } }, "idempotencyKey": "550e8400-e29b-41d4-a716-446655440001", "memo": "Payout via RTP", "externalIdentifier": "INVOICE-12345", "strategy": { "preferredPaymentStrategy": [ "RTP" ] } } }
Result
{
"data": {
"transferFunds": {
"__typename": "UnifiedFundsTransfer",
"id": "wouft_1",
"status": "PROCESSING",
"failureReason": null,
"createdAt": "2026-01-20T14:30:01Z",
"updatedAt": "2026-01-20T14:30:01Z",
"source": {
"node": {
"id": "ac_merchant_settlement_account"
},
"amount": {
"currencyCode": "USD",
"value": 50000,
"decimalPlaces": 2
}
},
"destination": {
"node": {
"last4": "6789",
"routingNumber": "021000021",
"accountType": "CHECKING",
"name": {
"givenName": "John",
"middleName": "Michael",
"familyName": "Doe"
}
},
"amount": {
"currencyCode": "USD",
"value": 49500,
"decimalPlaces": 2
}
},
"externalIdentifier": "INVOICE-12345",
"idempotencyKey": "550e8400-e29b-41d4-a716-446655440001",
"memo": "Payout via RTP",
"steps": [
{
"__typename": "UnifiedFundsTransferInitiateRequestStep",
"id": "woustep_1",
"status": "COMPLETED",
"failureReason": null,
"createdAt": "2026-01-20T14:30:01Z"
},
{
"__typename": "UnifiedFundsTransferRtpStep",
"id": "woustep_2",
"status": "PROCESSING",
"failureReason": null,
"createdAt": "2026-01-20T14:30:01Z",
"transfer": {
"id": "rtp_1",
"status": "PROCESSING"
}
}
]
}
}
}
Push to non-verified bank account
If the bank account was previously created via addNonVerifiedExternalUSFinancialBankAccount, you can pass its ID directly instead of providing inline bank details. Consent via bankTransferDetails is still required.
transferFunds — RTP (non-verified account)
Query
mutation TransferFunds($input: TransferFundsInput!) {
transferFunds(input: $input) {
__typename
... on UnifiedFundsTransfer {
id
status
failureReason
createdAt
updatedAt
source {
node {
... on Node {
id
}
... on FinancialAccount {
id
}
}
amount {
currencyCode
value
decimalPlaces
}
}
destination {
node {
... on USBankAccount {
last4
routingNumber
accountType
name {
givenName
familyName
}
}
}
amount {
currencyCode
value
decimalPlaces
}
}
externalIdentifier
idempotencyKey
memo
steps {
__typename
... on UnifiedFundsTransferInitiateRequestStep {
id
status
failureReason
createdAt
}
... on UnifiedFundsTransferRtpStep {
id
status
failureReason
createdAt
transfer {
id
status
}
}
}
}
... on UserError {
errors {
code
description
errorPath
}
}
... on AccessDeniedError {
message
}
}
}
Variables
{ "input": { "source": { "id": "ac_3", "amount": { "currencyCode": "USD", "value": 3501 }, "bankTransferDetails": { "paymentRelatedInformation": "Payment description here", "consent": { "consentTimestamp": "2026-02-20T06:06:51.890Z", "authorizedPersonId": "ps_3", "template": { "consentTemplateId": "consent", "consentTemplateVersion": "0" } } } }, "destination": { "usBankAccount": { "accountNumber": "0123456789", "routingNumber": "074000010", "accountType": "CHECKING", "firstName": "Clemens", "lastName": "McKenzie" } }, "idempotencyKey": "UUID_v4", "strategy": { "preferredPaymentStrategy": [ "RTP" ] }, "externalIdentifier": "UUID_v4", "memo": "Payout via RTP" } }
Result
{
"data": {
"transferFunds": {
"__typename": "UnifiedFundsTransfer",
"id": "wouft_3",
"status": "PROCESSING",
"failureReason": null,
"createdAt": "2026-02-20T06:06:52Z",
"updatedAt": "2026-02-20T06:06:52Z",
"source": {
"node": {
"id": "ac_3"
},
"amount": {
"currencyCode": "USD",
"value": 3501,
"decimalPlaces": 2
}
},
"destination": {
"node": {
"last4": "6789",
"routingNumber": "074000010",
"accountType": "CHECKING",
"name": {
"givenName": "Clemens",
"familyName": "McKenzie"
}
},
"amount": {
"currencyCode": "USD",
"value": 3501,
"decimalPlaces": 2
}
},
"externalIdentifier": "UUID_V4",
"idempotencyKey": "UUID_v4",
"memo": "Payout via RTP",
"steps": [
{
"__typename": "UnifiedFundsTransferInitiateRequestStep",
"id": "woustep_5",
"status": "COMPLETED",
"failureReason": null,
"createdAt": "2026-02-20T06:06:52Z"
},
{
"__typename": "UnifiedFundsTransferRtpStep",
"id": "woustep_6",
"status": "PROCESSING",
"failureReason": null,
"createdAt": "2026-02-20T06:06:52Z",
"transfer": {
"id": "rtp_2",
"status": "PROCESSING"
}
}
]
}
}
}
RTP eligibility of receiving bank
Not all US banks participate in RTP. transferFunds checks participation before submitting in the standard case; see the tip below. This section also documents the async fallback that still applies when a destination becomes ineligible after that check passes, and either way, the transfer does not fall back to same-day ACH automatically.
Build that fallback on the whole group of rail-unavailable failure reasons, not on NETWORK_NOT_SUPPORTED alone. DESTINATION_BANK_NOT_ACTIVE and PROVIDER_NETWORK_DISABLED describe different bank states and call for the same routing decision. Highnote can add a value to that group, and a fallback keyed to one value stops firing when that happens. See RTP cannot deliver this transfer.
To check whether a receiving bank supports RTP before initiating a transfer, use The Clearing House (TCH) references:
- RTP-Participating Financial Institutions — searchable list of participating banks
- Routing Number Lookup — verify whether a given routing number is RTP-eligible
Highnote validates destination-bank eligibility for RTP transfers submitted through transferFunds before creating anything. In the standard case, an ineligible destination returns NETWORK_NOT_SUPPORTED or DESTINATION_BANK_NOT_ACTIVE as a synchronous UserError, and no UnifiedFundsTransfer is created. See RTP Transfer Error Codes for the synchronous codes.
The example below shows the same condition reported asynchronously. That path still exists as a fallback, for instance when the destination becomes ineligible after the pre-flight check passes, so keep this handling in place alongside the synchronous check.
When this condition is instead reported asynchronously, the response surfaces failureReason: "NETWORK_NOT_SUPPORTED" on both the top-level UnifiedFundsTransfer and the UnifiedFundsTransferRtpStep:
Example failure response: NETWORK_NOT_SUPPORTED
{
"data": {
"node": {
"__typename": "UnifiedFundsTransfer",
"id": "wouft_210d83ba021e4b599a38b1672291334e",
"source": {
"node": {
"__typename": "FinancialAccount",
"id": "ac_c02204z8mb5n6h3rd1jmu0of9rkat65rpqwq"
},
"amount": {
"value": 775
}
},
"destination": {
"node": {
"__typename": "USBankAccount",
"last4": "6789",
"routingNumber": "111111118"
},
"amount": {
"value": 775
}
},
"status": "FAILED",
"failureReason": "NETWORK_NOT_SUPPORTED",
"steps": [
{
"__typename": "UnifiedFundsTransferRtpStep",
"status": "FAILED",
"failureReason": "NETWORK_NOT_SUPPORTED",
"createdAt": "2026-04-23T20:34:02.340Z",
"transfer": {
"__typename": "OriginatedRtpTransfer",
"id": "efrtp_4876b2519e55494d85db84883a5f275d",
"createdAt": "2026-04-23T20:34:06.040Z",
"updatedAt": "2026-04-23T20:34:24.897Z",
"status": "FAILED",
"failureReason": "NETWORK_NOT_SUPPORTED",
"source": {
"node": {
"__typename": "FinancialAccount",
"id": "ac_c02204z8mb5n6h3rd1jmu0of9rkat65rpqwq"
},
"amount": {
"value": 775
}
},
"destination": {
"node": {
"__typename": "USBankAccount",
"last4": "6789",
"routingNumber": "111111118",
"accountType": "CHECKING",
"institutionName": null
},
"amount": {
"value": 775
}
},
"events": null,
"bankTransferDetails": {
"paymentRelatedInformation": "Dev-live test"
}
}
},
{
"__typename": "UnifiedFundsTransferInitiateRequestStep",
"status": "COMPLETED",
"failureReason": null,
"createdAt": "2026-04-23T20:34:02.340Z"
}
]
}
}
}
NETWORK_NOT_SUPPORTED is one of several reasons an RTP transfer can fail. For every reason you can receive, grouped by the action to take, see Transfer Failure Reasons.
Receiving institution name
USBankAccount.institutionName returns the name of the receiving bank when Highnote resolves one. Use it to confirm that a payee routing number points at the institution you expect, and to give your support team a readable name instead of a routing number.
Highnote populates the field on the destination of an RTP transfer your organization originated, and only when you supplied that destination as inline bank details. A destination passed by financial account ID resolves to a FinancialAccount, which does not carry the field.
Highnote resolves the institution name after it accepts the transfer, so transferFunds returns before a name exists. Read the field by querying the UnifiedFundsTransfer again by ID, or after an RTP transfer notification event tells you the transfer moved on.
Read the field through the RTP step transfer:
Example query: read institutionName from a completed RTP transfer
query GetTransferInstitutionName($id: ID!) {
node(id: $id) {
... on UnifiedFundsTransfer {
__typename
id
status
steps {
__typename
... on UnifiedFundsTransferRtpStep {
status
transfer {
... on OriginatedRtpTransfer {
id
status
destination {
node {
... on USBankAccount {
last4
routingNumber
accountType
institutionName
}
}
}
}
}
}
}
}
}
}
{
"data": {
"node": {
"__typename": "UnifiedFundsTransfer",
"id": "wouft_210d83ba021e4b599a38b1672291334e",
"status": "COMPLETED",
"steps": [
{
"__typename": "UnifiedFundsTransferInitiateRequestStep"
},
{
"__typename": "UnifiedFundsTransferRtpStep",
"status": "COMPLETED",
"transfer": {
"id": "efrtp_4876b2519e55494d85db84883a5f275d",
"status": "COMPLETED",
"destination": {
"node": {
"last4": "6789",
"routingNumber": "021000021",
"accountType": "CHECKING",
"institutionName": "JPMORGAN CHASE BANK, NA"
}
}
}
}
]
}
}
}
Expect null everywhere else, including:
source, on any RTP transfer- RTP transfers your organization did not originate
- a
USBankAccounton aUnifiedFundsTransferorUnifiedFundsTransferQuotenode; read the field from the RTP step transfer instead - RTP notification event payloads, which carry
last4,routingNumber, andaccountTypeonly
A null value means Highnote did not resolve a name for that routing number. It does not mean the transfer failed, and it is not a participation check. Use the TCH references above to confirm RTP eligibility before you send.
Instant Network Transfer
Instant Network Transfers move funds between a Highnote FinancialAccount and a tokenized external card (with a PaymentMethodToken) through Visa Direct or Mastercard Send. Both push to card and Pull from card are supported.
The external card must be tokenized before initiating the transfer using the Secure Inputs SDK or Checkout SDK. See Instant Network Transfers for the full flow, which includes creating a reusable token.
Tokenize with Checkout SDK
Follow the guide on setting up the Checkout SDK. When initializing the SDK, configure the submitButton text to be Submit instead of "Pay":
renderCheckout({
clientToken,
additionalFormSections: {
cardHolderName: true,
billingAddress: true,
},
submitButton: {
text: "Submit",
},
});
Tokenize with Secure Inputs SDK
Follow the guide on setting up the Secure Inputs SDK.
When tokenizing, additional cardholder data is required for OFAC and AVS compliance checks — namely, fullName and billingAddress.
Soft descriptor
Set a per-transfer soft descriptor via merchantDescriptor.suffix inside cardTransferDetails on the card side of the transfer.
It appears on the cardholder's statement and applies to both push (OCT) and pull (AFT).
When omitted, Highnote uses the platform-derived descriptor built from your merchant name.
- Push (OCT): The card is the
destination: setdestination.cardTransferDetails.merchantDescriptor.suffix. Highnote separates the prefix and suffix with an asterisk (*). - Pull (AFT): The card is the
source: setsource.cardTransferDetails.merchantDescriptor.suffix. No separator is used.
Only one soft descriptor is used per transfer. The suffix itself is limited to 22 characters — always 22, whichever processor handles the transfer; Highnote rejects an over-length suffix before it reaches the network. Once accepted, the suffix is combined with your configured merchant descriptor prefix. The combined value is capped at 22 characters (25 when the transfer routes through a Visa DMS or Visa SMS processor), and Highnote truncates the suffix if the combined length exceeds that cap.
Passing the 22-character input check therefore does not mean the whole suffix reaches the statement. The characters actually available are the cap minus your prefix, minus one more on push (OCT) for the * separator. With an 8-character prefix under a 22-character cap, 13 characters remain for the suffix on push and 14 on pull; anything longer is truncated.
See Merchant Descriptors for how the prefix and suffix are constructed, the combined-length cap, and the allowed character set.
{
"input": {
"source": {
"id": "ac_source_financial_account",
"amount": { "currencyCode": "USD", "value": "10000" }
},
"destination": {
"id": "pmt_tokenized_card_123",
"cardTransferDetails": {
"merchantDescriptor": { "suffix": "ORDER-4821" }
}
},
"idempotencyKey": "550e8400-e29b-41d4-a716-446655440000",
"memo": "Card payout",
"strategy": { "preferredPaymentStrategy": ["INSTANT_NETWORK_TRANSFER"] }
}
}
Push to card (Visa Direct OCT / Mastercard MoneySend Payment)
An OCT (Original Credit Transaction) pushes funds from a Highnote Financial Account to a tokenized external card.
transferFunds — INT OCT Payment
Query
mutation TransferFunds($input: TransferFundsInput!) {
transferFunds(input: $input) {
__typename
... on UnifiedFundsTransfer {
id
status
failureReason
createdAt
updatedAt
source {
node {
... on Node {
id
}
... on FinancialAccount {
id
}
}
amount {
currencyCode
value
decimalPlaces
}
}
destination {
node {
... on Node {
id
}
... on PaymentMethodToken {
id
}
}
amount {
currencyCode
value
decimalPlaces
}
}
externalIdentifier
idempotencyKey
memo
steps {
__typename
... on UnifiedFundsTransferInitiateRequestStep {
id
status
failureReason
createdAt
}
... on UnifiedFundsTransferInstantNetworkTransferStep {
id
status
failureReason
createdAt
transfer {
... on InstantNetworkTransfer {
id
createdAt
updatedAt
status
failureReason
}
}
}
}
}
... on UserError {
errors {
code
description
errorPath
}
}
... on AccessDeniedError {
message
}
}
}
Variables
{ "input": { "source": { "id": "ac_2", "amount": { "currencyCode": "USD", "value": "10000" } }, "destination": { "id": "tkpmc_2" }, "idempotencyKey": "UUID_v4", "memo": "Card payout", "strategy": { "preferredPaymentStrategy": [ "INSTANT_NETWORK_TRANSFER" ] } } }
Result
{
"data": {
"transferFunds": {
"__typename": "UnifiedFundsTransfer",
"id": "wouft_2",
"status": "PROCESSING",
"failureReason": null,
"createdAt": "2026-01-20T15:00:01Z",
"updatedAt": "2026-01-20T15:00:01Z",
"source": {
"node": {
"id": "ac_2"
},
"amount": {
"currencyCode": "USD",
"value": 10000,
"decimalPlaces": 2
}
},
"destination": {
"node": {
"id": "tkpmc_2"
},
"amount": {
"currencyCode": "USD",
"value": 10000,
"decimalPlaces": 2
}
},
"externalIdentifier": null,
"idempotencyKey": "UUID_v4",
"memo": "Card payout",
"steps": [
{
"__typename": "UnifiedFundsTransferInitiateRequestStep",
"id": "woustep_3",
"status": "COMPLETED",
"failureReason": null,
"createdAt": "2026-01-20T15:00:01Z"
},
{
"__typename": "UnifiedFundsTransferInstantNetworkTransferStep",
"id": "woustep_4",
"status": "PROCESSING",
"failureReason": null,
"createdAt": "2026-01-20T15:00:01Z",
"transfer": {
"id": "int_1",
"status": "PROCESSING"
}
}
]
}
}
}
Pull from card (Visa Direct AFT / Mastercard MoneySend Funding)
An AFT (Account Funding Transaction) pulls funds from a tokenized external card into a Highnote FinancialAccount. The mutation is the same — swap the source and destination:
{
"input": {
"source": {
"id": "pmt_tokenized_card_123",
"amount": {
"currencyCode": "USD",
"value": "10000"
}
},
"destination": {
"id": "ac_destination_financial_account"
},
"idempotencyKey": "550e8400-e29b-41d4-a716-446655440001",
"memo": "Card funding",
"strategy": {
"preferredPaymentStrategy": ["INSTANT_NETWORK_TRANSFER"]
}
}
}