Skip to main content
The [initiateAccountToAccountBankTransfer](/schema/mutations/initiateaccounttoaccountbanktransfer) mutation enables transfers between bank accounts within the embedded banking platform. This supports moving funds between internal accounts or to/from external bank accounts.
When to use:
  • Transfer funds between a user’s internal accounts
  • Move money from an internal account to an external bank account (ACH transfer)
  • Transfer funds from an external account to an internal account
Scenario: Sarah wants to transfer $500 from her business checking account to her business savings account. She initiates the transfer through your embedded banking interface, and the funds are moved between her accounts.

Transfer types

TypeDescription
INTERNAL_TO_INTERNALTransfer between two internal accounts owned by the same organization
INTERNAL_TO_EXTERNALTransfer from an internal account to an external bank account (ACH push)
EXTERNAL_TO_INTERNALTransfer from an external bank account to an internal account (ACH pull)

Required fields

FieldDescription
fromAccountIdThe UUID of the source bank account
toAccountIdThe UUID of the destination bank account
amountThe amount to transfer (positive decimal value)
typeThe transfer type (see above)

Idempotency key

The idempotencyKey is an optional field that prevents duplicate transfers when the same request is submitted multiple times. This is useful for handling network failures, retries, or ensuring exactly-once processing in distributed systems.
Best practice: Always use an idempotency key for production transfers to prevent accidental duplicate transactions.

How it works

  1. First request: When you submit a transfer with an idempotency key, the system processes the transfer and associates the result with that key.
  2. Subsequent requests: If you submit another transfer request with the same idempotency key:
    • If the parameters match the original request, the system returns the original transfer result without creating a duplicate
    • If the parameters differ, the system returns a TRANSFER_IDEMPOTENCY_KEY_CONFLICT error

Idempotency key requirements

  • Must be a unique string (We recommend using a UUID)
  • Should be generated client-side before the request
  • Can be up to 255 characters

Error handling

Error codeDescription
TRANSFER_IDEMPOTENCY_KEY_CONFLICTA transfer with this idempotency key already exists but with different parameters
Important: An idempotency conflict means the key was already used with different transfer parameters. Generate a new idempotency key if you need to create a different transfer.

Example usage

mutation initiateAccountToAccountBankTransfer($input: InitiateAccountToAccountBankTransferInput!) {
  initiateAccountToAccountBankTransfer(input: $input) {
    bankAccountTransfer {
      id
      amount
      status
      fromAccount {
        id
        nickname
      }
      toAccount {
        id
        nickname
      }
    }
    errors {
      ... on Error {
        message
      }
      ... on EmbeddedError {
        code
      }
    }
  }
}
Variables
{
  "input": {
    "fromAccountId": "550e8400-e29b-41d4-a716-446655440001",
    "toAccountId": "550e8400-e29b-41d4-a716-446655440002",
    "amount": 500.00,
    "type": "INTERNAL_TO_INTERNAL",
    "idempotencyKey": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
  }
}

Retry pattern

When implementing retries for failed requests:
async function initiateTransferWithRetry(transferInput, maxRetries = 3) {
  // Generate idempotency key once, before any retry attempts
  const idempotencyKey = crypto.randomUUID();
  const input = { ...transferInput, idempotencyKey };

  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      const result = await client.mutate({
        mutation: INITIATE_TRANSFER,
        variables: { input }
      });

      // Check for idempotency conflict
      const errors = result.data?.initiateAccountToAccountBankTransfer?.errors;
      if (errors?.some(e => e.code === 'TRANSFER_IDEMPOTENCY_KEY_CONFLICT')) {
        // Transfer already completed with different params - don't retry
        throw new Error('Idempotency conflict: transfer params mismatch');
      }

      return result;
    } catch (error) {
      if (attempt === maxRetries) throw error;
      // Wait before retrying (exponential backoff)
      await new Promise(r => setTimeout(r, Math.pow(2, attempt) * 1000));
    }
  }
}