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! | Free-form payment details that may surface to the recipient (e.g., "Invoice 0123456789 Payment 999.99 USD"). 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>"
}
}
}
}
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 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 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. The Transfer Funds API does not check participation before submitting; if the receiving bank does not support RTP, the transfer fails and does not fall back to same-day ACH.
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
When the receiving bank does not support RTP, 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"
},
"amount": {
"value": 775
}
},
"events": null,
"bankTransferDetails": {
"paymentRelatedInformation": "Dev-live test"
}
}
},
{
"__typename": "UnifiedFundsTransferInitiateRequestStep",
"status": "COMPLETED",
"failureReason": null,
"createdAt": "2026-04-23T20:34:02.340Z"
}
]
}
}
}
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.
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 FinancialAccount {
id
}
}
amount {
currencyCode
value
decimalPlaces
}
}
destination {
node {
... 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"]
}
}
}