Pay
Connect Payout Methods
Enrollment Form

Building Custom Payouts Enrollment Forms

Learn how to create fully-customized payouts enrollment forms with secure input fields


The Payment Method Form SDK provides secure input fields that you can embed into custom-built enrollment forms for collecting payout information. Unlike hosted enrollment links, the embedded form gives you complete control over your form's HTML, styling, and user experience while keeping sensitive banking data secure through isolated secure input fields.

Prerequisites

Before building enrollment forms, it's helpful to learn about the following topics.


When to Use Embedded Enrollment Forms


Use the Embedded Enrollment Form SDK when you need complete control over your enrollment form's design and user experience while maintaining security. The SDK provides secure input fields that handle sensitive bank account data, isolating this data from your application while giving you full control over the surrounding UI, layout, and styling.

Common use cases

  • Custom-Branded Enrollment: Build enrollment experiences that perfectly match your brand guidelines and design system
  • Integrated Onboarding Flows: Embed payout enrollment directly within existing onboarding forms alongside profile information and agreements
  • Multi-Step Enrollment: Create wizard-style enrollment flows with custom progress indicators and navigation

How Enrollment Forms Work


Enrollment Forms use a combination of regular HTML inputs and secure input fields:

  1. Secure Input Fields: Sensitive data like account numbers and routing numbers are rendered in isolated iframes that communicate with Payload's secure environment. These fields appear as <div> elements with special attributes.

  2. Regular Input Fields: Non-sensitive data like account holder name and account type use standard HTML <input> elements with special attributes to identify them to the SDK.

  3. Form Submission: When the form is submitted, the SDK collects data from both secure and regular fields, sends sensitive data directly to Payload's servers, and returns a payment method ID to your application.

Building an Enrollment Form


Creating a payouts enrollment form requires three steps: building the HTML form, creating an intent on your backend, and initializing the form with JavaScript.

import payload
pl = payload.Session('secret_key_3bW9...', api_version='v2')
 
# Create a payment method form intent for payouts enrollment
intent = pl.Intent.create(
    type='payment_method_form',
    payment_method_form={
        'payment_method_template': {
            'transfer_type': 'receive_only',
            'account_id': 'acct_recipient123',
            'account_defaults': {
                'paying': 'payouts'
            }
        }
    }
)
 
# Send the token to your frontend
token = intent.token
<!DOCTYPE html>
<html>
<head>
  <script src="https://payload.com/Payload.js"></script>
</head>
<body>
  <form id="payout-enrollment-form" pl-form="payment_method">
    <h2>Bank Account Information</h2>
 
    <!-- Routing number secure input -->
    <label>Routing Number</label>
    <div pl-input="routing_number" placeholder="000000000"></div>
 
    <!-- Account number secure input -->
    <label>Account Number</label>
    <div pl-input="account_number" placeholder="Account Number"></div>
 
    <!-- Account type selection -->
    <label>Account Type</label>
    <select pl-input="account_type">
      <option value="checking" selected>Checking</option>
      <option value="savings">Savings</option>
    </select>
 
    <!-- Account holder name -->
    <label>Account Holder Name</label>
    <input type="text" pl-input="account_holder" placeholder="Full Name">
 
    <button type="submit">Save Bank Account</button>
  </form>
 
  <div id="error-message" style="display: none; color: red;"></div>
 
  <script src="bank-account.js"></script>
</body>
</html>
// Initialize Payload with token from backend
Payload('your_token_from_backend');
 
// Create the payment method form for payouts enrollment
const form = new Payload.Form(document.getElementById('payout-enrollment-form'));
 
// Listen for successful enrollment
form.on('success', (evt) => {
  console.log('Payment method created:', evt.payment_method_id);
  // Redirect to success page or update UI
  window.location.href = `/enrollment/success?pm=${evt.payment_method_id}`;
});
 
// Handle errors
form.on('error', (evt) => {
  console.error('Enrollment error:', evt.message);
  document.getElementById('error-message').textContent = evt.message;
  document.getElementById('error-message').style.display = 'block';
});
 
// Handle validation events
form.on('invalid', (evt) => {
  console.log('Invalid state:', evt.invalid);
  // Show field-specific error
});
 
form.on('valid', (evt) => {
  console.log('Valid state:', evt.live);
  // Clear field-specific error
});

This example shows the complete flow for building a custom payouts enrollment form:

  1. Backend: Create an intent with type='payment_method_form' and configure transfer_type='receive_only' for payouts
  2. Frontend HTML: Build your form with pl-form="payment_method" attribute and secure input fields using pl-input attributes
  3. Frontend JavaScript: Initialize with Payload(token) and new Payload.Form(formElement), or use React components like <PaymentMethodForm>, <RoutingNumber>, and <AccountNumber>
  4. Event Handling: Listen for success or error events (or use React props like onSuccess, onError)

The secure input fields (pl-input="routing_number", pl-input="account_number") render as iframes that capture sensitive data without exposing it to your application.

Secure Input Fields


The Payment Method Form SDK provides secure input components for capturing sensitive banking data. These fields are rendered as isolated iframes that communicate directly with Payload's secure environment.

Bank Account Input Fields

For payouts, use bank account input fields:

<!DOCTYPE html>
<html>
<head>
  <script src="https://payload.com/Payload.js"></script>
</head>
<body>
  <form id="payout-enrollment-form" pl-form="payment_method">
    <h2>Bank Account Information</h2>
 
    <!-- Routing number secure input -->
    <label>Routing Number</label>
    <div pl-input="routing_number" placeholder="000000000"></div>
 
    <!-- Account number secure input -->
    <label>Account Number</label>
    <div pl-input="account_number" placeholder="Account Number"></div>
 
    <!-- Account type selection -->
    <label>Account Type</label>
    <select pl-input="account_type">
      <option value="checking" selected>Checking</option>
      <option value="savings">Savings</option>
    </select>
 
    <!-- Account holder name -->
    <label>Account Holder Name</label>
    <input type="text" pl-input="account_holder" placeholder="Full Name">
 
    <button type="submit">Save Bank Account</button>
  </form>
 
  <div id="error-message" style="display: none; color: red;"></div>
 
  <script src="bank-account.js"></script>
</body>
</html>
// Initialize Payload with token from backend
Payload('your_token_from_backend');
 
// Create the payment method form for payouts enrollment
const form = new Payload.Form(document.getElementById('payout-enrollment-form'));
 
// Listen for successful enrollment
form.on('success', (evt) => {
  console.log('Payment method created:', evt.payment_method_id);
  // Redirect to success page or update UI
  window.location.href = `/enrollment/success?pm=${evt.payment_method_id}`;
});
 
// Handle errors
form.on('error', (evt) => {
  console.error('Enrollment error:', evt.message);
  document.getElementById('error-message').textContent = evt.message;
  document.getElementById('error-message').style.display = 'block';
});
 
// Handle validation events
form.on('invalid', (evt) => {
  console.log('Invalid state:', evt.invalid);
  // Show field-specific error
});
 
form.on('valid', (evt) => {
  console.log('Valid state:', evt.live);
  // Clear field-specific error
});

This creates input fields for routing number and account number for receiving payouts. The transfer_type='receive_only' configuration ensures the payment method can only be used for receiving payouts, not sending payments.

Plaid Integration


For a streamlined enrollment experience, integrate Plaid to allow recipients to connect their bank accounts by logging into their bank. Plaid reduces form friction by eliminating manual data entry.

<!DOCTYPE html>
<html>
<head>
  <script src="https://payload.com/Payload.js"></script>
</head>
<body>
  <form id="payout-enrollment-form" pl-form="payment_method">
    <h2>Connect Your Bank Account</h2>
    <p>Link your bank account securely using Plaid</p>
 
    <!-- Plaid button -->
    <button id="plaid-button" type="button">
      Connect with Plaid
    </button>
  </form>
 
  <div id="error-message" style="display: none; color: red;"></div>
 
  <script src="with-plaid.js"></script>
</body>
</html>
// Initialize Payload with token from backend
Payload('your_token_from_backend');
 
// Create the payment method form
const form = new Payload.Form(document.getElementById('payout-enrollment-form'));
 
// Enable Plaid integration on a button
form.plaid(document.getElementById('plaid-button'));
 
// Listen for successful enrollment
form.on('success', (evt) => {
  console.log('Payment method created via Plaid:', evt.payment_method_id);
  // Store the payment method ID and redirect
  localStorage.setItem('payout_method_id', evt.payment_method_id);
  window.location.href = '/enrollment/success';
});
 
// Handle errors
form.on('error', (evt) => {
  console.error('Plaid enrollment error:', evt.message);
  document.getElementById('error-message').textContent = evt.message;
  document.getElementById('error-message').style.display = 'block';
});

To enable Plaid, call .plaid() on your form instance and pass a button element. When clicked, Plaid's modal will open for the recipient to securely connect their bank account.

Handling Form Events


The Payment Method Form SDK emits events throughout the enrollment lifecycle. Listen for these events to handle successful enrollments, errors, and validation.

Success Events

form.on('success', (evt) => {
  console.log('Payment method created:', evt.payment_method_id)
  window.location.href = `/enrollment/success?pm=${evt.payment_method_id}`
})

Error Events

form.on('error', (evt) => {
  console.error('Enrollment error:', evt.message)
  alert(`Error: ${evt.message}`)
})

Validation Events

form.on('invalid', (evt) => {
  console.log('Invalid:', evt.message)
  if (evt.target) evt.target.classList.add('pl-invalid')
})
 
form.on('valid', (evt) => {
  if (evt.target) evt.target.classList.remove('pl-invalid')
})

Styling Secure Input Fields


You can style secure input fields to match your form's design using several approaches. While secure inputs are rendered in isolated iframes for security, the SDK provides multiple ways to apply custom styling.

Extend Payload Classes

Style secure inputs by targeting Payload's default CSS classes:

/* Extend default Payload classes */
.pl-input {
  font-family: "Your Custom Font", sans-serif;
  font-size: 16px;
  padding: 12px;
  border: 1px solid #ddd;
  border-radius: 4px;
}
 
.pl-input-sec {
  background-color: #fff;
}
 
.pl-focus {
  border-color: #007bff;
  box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25);
}
 
.pl-invalid {
  border-color: #dc3545;
}

Payload provides default classes that you can extend with custom CSS rules:

  • pl-input - Base input styling
  • pl-input-sec - Secure field styling
  • pl-focus - Focused input state
  • pl-invalid - Invalid input state

Schema Reference


The following fields are available when configuring the Payment Method Form intent. For complete API reference, see Intent API Reference.

Intent Configuration

Configuration options passed when creating the payment method form intent on your backend:

payment_method_form
object
Configuration for the payment method form intent. Use this type to create a native form for collecting and saving payment method details (cards, bank accounts) without immediately charging. The payment method can then be used for future transactions. Returns a token for rendering with the SDK. Required when type is payment_method_form.
payment_method_template
object
Pre-filled payment method configuration including transfer type, billing address, account holder, currency settings, and account defaults. These values populate the payment method form and determine how the saved payment method can be used. Allows you to set sensible defaults for the payment method being collected.
account_defaults
object
Default transaction types that this payment method will be used for. Controls whether the payment method is the default for paying or funding transactions, and which specific types of transactions it applies to.
funding
enum[string]
Controls which funding transaction types this payment method can be used for. Possible values: "all" (use for all funding transactions), "deposits" (use only for receiving deposits), or "withdraws" (use only for withdrawals). Determines the default usage for receiving funds.
Values: all, deposits, withdraws
paying
enum[string]
Controls which paying transaction type this payment method will be the default for. Possible values: "all" (use for all paying transactions), "payments" (use only for sending payments), or "payouts" (use only for receiving payouts). Determines the default usage for sending funds. If the account default is set for payments, that payment method will be used for any automatic invoice payments.
Values: all, payments, payouts
account_holder
string
Pre-filled name of the account holder for the payment method. For bank accounts, this is the name on the account. For cards, this is the cardholder name. When provided, this value will be used to populate the form.
account_id
string
The ID of the account that will own this payment method. Links the payment method to a specific customer or processing account. Used to associate the payment method with the correct account when it is created.
bank_account
object
Configuration specific to bank account payment methods. Contains bank account-specific settings such as the currency.
currency
enum[string]
The currency for bank account transactions. Must be either "USD" (US Dollars) or "CAD" (Canadian Dollars). Determines the currency for transactions using this bank account.
Values: USD, CAD
billing_address
object
Pre-filled billing address for the payment method. When provided, these values will be used to populate the address fields in the checkout form. Customers may be able to edit these values depending on checkout configuration.
address_line_1
string
Street address of the address
address_line_2
string
Unit number of the address
city
string
City of the company
country_code
string
Country code of the address
postal_code
string
Postal code of the address
state_province
string
State of the address
id
string
The unique identifier of an existing payment method to update. When provided, the form will be used to update the payment method, such as revalidating the CVV, updating the expiration date, or changing the billing address.
transfer_type
enum[string]
The transfer capabilities for this payment method. Possible values: "send_only" (can only send payments/fund payouts), "receive_only" (can only receive payouts/deposits), or "two_way" (can both send and receive). Controls how the payment method can be used for transactions.
Values: send_only, receive_only, two_way

Next Steps

Enhance your custom enrollment form implementation


Send Payouts to Enrolled Recipients

Start sending payouts to enrolled recipients using the payment method IDs returned from completed enrollment forms.

Monitor Enrollment Events

Subscribe to webhook notifications to receive real-time updates when recipients complete enrollment forms and payment methods are created.

Manage Payment Methods

Use the Payment Methods API to retrieve, update, and manage enrolled payout recipient bank accounts after form submission.


Related articles