> ## Documentation Index
> Fetch the complete documentation index at: https://developers.dwolla.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Adding a Debit Card

> Learn how to add a debit card as a funding source for Push to Card payouts.

## Overview

Adding a debit card as a funding source enables your Customers to receive instant payouts via Push to Card. This guide walks through the complete integration process, combining a client-side card capture experience with server-side API calls to Dwolla.

Unlike bank account funding sources, debit card integration requires a client-side component to securely capture card details. The card capture component provides a pre-built, PCI-compliant UI that handles card tokenization, removing the need for your application to directly handle sensitive card data.

> [!WARNING]
>
> Push to Card is a premium feature available for Dwolla customers. Enabling Push to Card requires additional Dwolla approvals before getting started. Please [contact Sales](https://www.dwolla.com/contact?b=apidocs) or your Relationship Manager for more information.

### Integration Flow

The complete flow for adding a debit card involves four key steps:

**1. Create and Retrieve an Exchange Session**

Create an exchange-session resource via the Dwolla API, then retrieve the exchange-session. The response includes payment session details in `externalProviderSessionData` that you use to initialize the Flow component. Optionally opt into an Account Name Inquiry here to have the cardholder name checked against the card issuer's records.

**2. Mount the Card Capture Component**

Load the Flow UI on your client-side application using the payment session data from the exchange-session response. When the customer submits their card, Flow's `onPaymentCompleted` callback returns a payment ID (`pay_xxx`).

**3. Create an Exchange in Dwolla**

Create an Exchange resource in Dwolla using the payment ID (pay\_xxx) from Flow as the `token`. The Exchange links the card to your Customer.

**4. Create a Funding Source in Dwolla**

Create a funding source for your Customer by linking to the Exchange created in the previous step, plus a name and cardDetails (cardholder name and billing address). You can also supply optional cardholder identity fields here to reduce sanctions screening alerts.

### Sequence Diagram

The following diagram illustrates the complete sequence of interactions between your client, your server, and the Dwolla API:

```mermaid theme={"dark"}
sequenceDiagram
    autonumber
    participant Client as Client Browser
    participant Server as Your Server
    participant Dwolla as Dwolla API

    Note over Client,Dwolla: Step 1: Create and Retrieve Exchange Session
    Client->>Server: Request to add card
    Server->>Dwolla: POST /customers/{id}/exchange-sessions
    Dwolla-->>Server: 201 Created (exchange-session location)
    Server->>Dwolla: GET /exchange-sessions/{id}
    Dwolla-->>Server: Exchange session (externalProviderSessionData)
    Server-->>Client: Return payment session data

    Note over Client,Dwolla: Step 2: Mount Card Capture Component
    Client->>Client: Mount Flow and customer enters card
    Client->>Client: Flow returns payment ID to client

    Note over Client,Dwolla: Step 3: Create Exchange in Dwolla
    Client->>Server: Send payment ID
    Server->>Dwolla: POST /customers/{id}/exchanges (token = pay_xxx)
    Dwolla-->>Server: 201 Created (exchange location)

    Note over Client,Dwolla: Step 4: Create Funding Source in Dwolla
    Server->>Dwolla: POST /customers/{id}/funding-sources (_links.exchange)
    Dwolla-->>Server: 201 Created (funding source URL)
    Server-->>Client: Success response
```

## Before You Begin

Before integrating debit card functionality, ensure you have:

* A Dwolla [Sandbox](https://accounts-sandbox.dwolla.com/sign-up) or [Production](https://accounts.dwolla.com/sign-up) account with Push to Card enabled
* A [Checkout.com](https://www.checkout.com/) sandbox account with a **public key** and push to card enabled
* An existing [Customer](https://developers.dwolla.com/docs/customer-types.md) in Dwolla who will receive the card funding source

> [!TIP]
>
> Check out our [Push to Card sample application](https://github.com/Dwolla/integration-examples/tree/main/packages/push-to-card/checkout) for a complete working example of this integration flow.

### Supported Card Types

Push to Card supports debit cards issued by the following card networks:

| Card Network | Supported |
| - | - |
| Visa | ✓ |
| Mastercard | ✓ |

> [!NOTE]
>
> Only **debit cards** are supported for Push to Card payouts. Credit cards, prepaid cards, and cards issued outside the United States are not eligible. If an unsupported card is tokenized, the funding source creation will fail with a validation error.

### Card Capture API Keys

For this integration, a **public key** is required. You use it in the browser to initialize the card capture component.

Create a public key in the [Checkout.com Dashboard](https://www.checkout.com/) with the following scopes:

* `payment-sessions:pay`
* `vault-tokenization`

### Sandbox Testing

When testing in the sandbox environment, use the following test card numbers to simulate successful card tokenization:

| Card Network | Test Card Number | CVV | Expiry |
| - | - | - | - |
| Visa (Debit) | `4024764449971519` | Any 3 digits | Any future date |
| Mastercard (Debit) | `5318773012490080` | Any 3 digits | Any future date |

> [!TIP]
>
> For a complete list of test cards and response codes, see the [test cards documentation](https://www.checkout.com/docs/developer-resources/testing/test-cards#Card_payout_test_cards).

## Step 1: Create and Retrieve an Exchange Session

Server-side

When your Customer is ready to add a card, create an **exchange-session** resource via the Dwolla API, then retrieve that exchange-session. The response includes **payment session details** in `externalProviderSessionData` that you use to initialize the card capture component on the client.

> [!NOTE]
>
> Although Flow uses a payment session, you are not processing a payment. No funds move and your customer is never charged. The payment session is used to securely tokenize your customer's card. As part of that process, Dwolla's card processing partner performs a \$0 authorization through the card networks to confirm the card is valid and able to receive funds. This is a standard check, not a separate step you need to trigger.

### Step 1A: Create an Exchange Session

Create an exchange-session for the Customer. This session will specify the card network **exchange partner**. The Exchange Partner ID can be found by calling the [List Exchange Partners](https://developers.dwolla.com/docs/api-reference/exchanges/list-exchange-partners.md) API endpoint.

| Parameter | Required | Type | Description |
| - | - | - | - |
| \_links.exchange-partner.href | yes | string | URL of the exchange-partner resource (e.g., the card network exchange partner for debit card). |
| cardDetails | no | object | Cardholder details used to request an Account Name Inquiry (ANI) name-match check. See [Optional: Check the Cardholder Name with Account Name Inquiry](#optional-check-the-cardholder-name-with-account-name-inquiry). |
| cardDetails.firstName | conditional | string | The cardholder first name you expect on the card. Required when `cardDetails` is present. |
| cardDetails.lastName | conditional | string | The cardholder last name you expect on the card. Required when `cardDetails` is present. |
| cardDetails.accountNameInquiry | conditional | boolean | Set to `true` to request a name-match check on the card. Required when `cardDetails` is present. |

```bash Create Exchange Session theme={"dark"}
POST https://api-sandbox.dwolla.com/customers/247f401e-0363-4370-baa5-ab7e97d98079/exchange-sessions
Content-Type: application/vnd.dwolla.v1.hal+json
Accept: application/vnd.dwolla.v1.hal+json
Authorization: Bearer your_dwolla_access_token

{
  "_links": {
    "exchange-partner": {
      "href": "https://api-sandbox.dwolla.com/exchange-partners/d652517d-9c02-4ea4-87af-2977e6cf3850"
    }
  }
}

HTTP/1.1 201 Created
Location: https://api-sandbox.dwolla.com/exchange-sessions/8b46c060-03a8-4a6e-8094-7cf5a4b4fc01
```

```javascript createExchangeSession.js theme={"dark"}
const customerId = "247f401e-0363-4370-baa5-ab7e97d98079";
const exchangePartnerId = "d652517d-9c02-4ea4-87af-2977e6cf3850"; // Provided by Dwolla

const response = await dwolla.post(
  `https://api-sandbox.dwolla.com/customers/${customerId}/exchange-sessions`,
  {
    _links: {
      "exchange-partner": {
        href: `https://api-sandbox.dwolla.com/exchange-partners/${exchangePartnerId}`,
      },
    },
  }
);

const exchangeSessionLocation = response.headers.get("Location");
// => "https://api-sandbox.dwolla.com/exchange-sessions/8b46c060-03a8-4a6e-8094-7cf5a4b4fc01"
```

```ruby create_exchange_session.rb theme={"dark"}
customer_id = "247f401e-0363-4370-baa5-ab7e97d98079"
exchange_partner_id = "d652517d-9c02-4ea4-87af-2977e6cf3850"

request_body = {
  _links: {
    "exchange-partner" => {
      href: "https://api-sandbox.dwolla.com/exchange-partners/#{exchange_partner_id}"
    }
  }
}

# Using DwollaV2 - https://github.com/Dwolla/dwolla-v2-ruby
response = app_token.post(
  "https://api-sandbox.dwolla.com/customers/#{customer_id}/exchange-sessions",
  request_body
)
exchange_session_location = response.response_headers[:location]
```

#### Optional: Check the Cardholder Name with Account Name Inquiry

Account Name Inquiry (ANI) compares the cardholder name you expect against the name the card issuer has on file. Requesting it gives you an early signal of fraudulent card usage before you create a funding source.

To opt in, include a `cardDetails` object with `accountNameInquiry` set to `true` and the name you expect on the card. Dwolla passes that name to the card network during capture and returns the match result on the resulting exchange, which you retrieve in [Step 3](#step-3-create-an-exchange-in-dwolla) as `cardDetails.accountNameInquiry`.

ANI is optional. If you omit `cardDetails`, the card capture flow behaves exactly as shown above.

```bash Create Exchange Session with ANI theme={"dark"}
POST https://api-sandbox.dwolla.com/customers/247f401e-0363-4370-baa5-ab7e97d98079/exchange-sessions
Content-Type: application/vnd.dwolla.v1.hal+json
Accept: application/vnd.dwolla.v1.hal+json
Authorization: Bearer your_dwolla_access_token

{
  "_links": {
    "exchange-partner": {
      "href": "https://api-sandbox.dwolla.com/exchange-partners/d652517d-9c02-4ea4-87af-2977e6cf3850"
    }
  },
  "cardDetails": {
    "firstName": "Jane",
    "lastName": "Doe",
    "accountNameInquiry": true
  }
}

HTTP/1.1 201 Created
Location: https://api-sandbox.dwolla.com/exchange-sessions/8b46c060-03a8-4a6e-8094-7cf5a4b4fc01
```

```javascript createExchangeSessionWithAni.js theme={"dark"}
const customerId = "247f401e-0363-4370-baa5-ab7e97d98079";
const exchangePartnerId = "d652517d-9c02-4ea4-87af-2977e6cf3850"; // Provided by Dwolla

const response = await dwolla.post(
  `https://api-sandbox.dwolla.com/customers/${customerId}/exchange-sessions`,
  {
    _links: {
      "exchange-partner": {
        href: `https://api-sandbox.dwolla.com/exchange-partners/${exchangePartnerId}`,
      },
    },
    cardDetails: {
      firstName: "Jane",
      lastName: "Doe",
      accountNameInquiry: true,
    },
  }
);

const exchangeSessionLocation = response.headers.get("Location");
// => "https://api-sandbox.dwolla.com/exchange-sessions/8b46c060-03a8-4a6e-8094-7cf5a4b4fc01"
```

```ruby create_exchange_session_with_ani.rb theme={"dark"}
customer_id = "247f401e-0363-4370-baa5-ab7e97d98079"
exchange_partner_id = "d652517d-9c02-4ea4-87af-2977e6cf3850"

request_body = {
  _links: {
    "exchange-partner" => {
      href: "https://api-sandbox.dwolla.com/exchange-partners/#{exchange_partner_id}"
    }
  },
  cardDetails: {
    firstName: "Jane",
    lastName: "Doe",
    accountNameInquiry: true
  }
}

# Using DwollaV2 - https://github.com/Dwolla/dwolla-v2-ruby
response = app_token.post(
  "https://api-sandbox.dwolla.com/customers/#{customer_id}/exchange-sessions",
  request_body
)
exchange_session_location = response.response_headers[:location]
```

### Step 1B: Retrieve the Exchange Session

Send a GET request to the exchange-session URL to retrieve the session. The response includes `externalProviderSessionData` with the payment session fields needed to mount the card capture component.

```bash Retrieve Exchange Session theme={"dark"}
GET https://api-sandbox.dwolla.com/exchange-sessions/8b46c060-03a8-4a6e-8094-7cf5a4b4fc01
Accept: application/vnd.dwolla.v1.hal+json
Authorization: Bearer your_dwolla_access_token
```

```javascript retrieveExchangeSession.js theme={"dark"}
const exchangeSessionUrl = response.headers.get("Location");

const sessionResponse = await dwolla.get(exchangeSessionUrl);
const exchangeSession = await sessionResponse.body;
const externalProviderSessionData = exchangeSession.externalProviderSessionData; // Payment session data
```

```json Response theme={"dark"}
{
  "created": "2026-02-20T10:12:45.138Z",
  "_links": {
    "self": {
      "href": "https://api-sandbox.dwolla.com/exchange-sessions/8b46c060-03a8-4a6e-8094-7cf5a4b4fc01",
      "type": "application/vnd.dwolla.v1.hal+json",
      "resource-type": "exchange-sessions"
    },
    "exchange-partner": {
      "href": "https://api-sandbox.dwolla.com/exchange-partners/d652517d-9c02-4ea4-87af-2977e6cf3850",
      "type": "application/vnd.dwolla.v1.hal+json",
      "resource-type": "exchange-partner"
    }
  },
  "externalProviderSessionData": {
    "id": "ps_39vhHjFxZuJRKOw5Hexbnv39fC7",
    "payment_session_secret": "pss_2400dd88-171d-4a9c-bebf-e06b56d59bb6",
    "payment_session_token": "YmFzZTY0:eyJpZCI6InBzXzM5dmhIakZ4WnVKUktPdzVIZXhibnYzOWZDNyIs..."
  }
}
```

Use the `externalProviderSessionData` object as the payment session when initializing Flow: pass `id`, `payment_session_secret`, and `payment_session_token` to your client so it can mount the card capture component (Step 2).

> [!WARNING]
>
> Storing card details requires explicit customer consent. Your application must collect and record this consent before initiating the card capture flow. The consent collection must happen outside of the card capture component.

## Step 2: Mount the Card Capture Component

Client-side

With the payment session data from the exchange-session response (Step 1), you can initialize and mount the card capture component on your frontend. It provides a secure, pre-built UI for capturing card details without handling sensitive data directly.

When your customer submits their card, the component tokenizes it and a \$0 authorization is performed through the card networks to validate the card. Both happen automatically, so there is nothing for you to trigger.

### Install the Library

Install the card capture library via npm:

```bash theme={"dark"}
npm install @checkout.com/checkout-web-components --save
```

Alternatively, load the package directly via a script tag:

```html theme={"dark"}
<script src="https://checkout-web-components.checkout.com/index.js"></script>
```

> [!WARNING]
>
> To remain PCI compliant, only load the script directly from `https://checkout-web-components.checkout.com`. Do not download, host, or bundle the script yourself.

### Initialize and Mount Flow

Create a container element in your HTML and initialize the Flow component with your payment session:

```html theme={"dark"}
<div id="card-capture-container"></div>

<script src="https://checkout-web-components.checkout.com/index.js"></script>
<script>
  (async () => {
    // Your Checkout.com public key
    const PUBLIC_KEY = "pk_sbox_your_public_key";
    
    // Payment session: pass through externalProviderSessionData from the exchange-session (Step 1)
    const paymentSession = {
      id: externalProviderSessionData.id,
      payment_session_secret: externalProviderSessionData.payment_session_secret,
      payment_session_token: externalProviderSessionData.payment_session_token
    };

    const checkout = await CheckoutWebComponents({
      publicKey: PUBLIC_KEY,
      environment: "sandbox", // Use "production" for live
      paymentSession,
      componentOptions: {
        card: {
          displayCardholderName: "hidden", // No need to collect name in Flow; collect it when creating the funding source (Step 4) via cardDetails.firstName and cardDetails.lastName
        },
      },
      onReady: () => {
        console.log("Flow component ready");
      },
      onPaymentCompleted: async (_component, paymentResponse) => {
        // paymentResponse.id contains the payment ID (pay_xxx)
        console.log("Card captured successfully:", paymentResponse.id);
        
        // Send the payment ID to your server for Step 3
        await sendPaymentIdToServer(paymentResponse.id);
      },
      onChange: (component) => {
        console.log(`Form valid: ${component.isValid()}`);
      },
      onError: (_component, error) => {
        console.error("Flow error:", error);
      }
    });

    // Create and mount the Flow component
    const flowComponent = checkout.create("flow");
    flowComponent.mount(document.getElementById("card-capture-container"));
  })();

  async function sendPaymentIdToServer(paymentId) {
    // Send to your backend to complete Steps 3, 4, and 5
    const response = await fetch("/api/complete-card-addition", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ paymentId })
    });
    
    if (response.ok) {
      window.location.href = "/card/success";
    }
  }
</script>
```

### Configuration Options

| Option | Description |
| - | - |
| publicKey | Your Checkout.com public API key (prefixed with `pk_`) |
| environment | `sandbox` for testing, `production` for live transactions |
| locale | Customer's locale (e.g., `en-US`). Defaults to browser locale |
| paymentSession | Object with `id`, `payment_session_secret`, and `payment_session_token` from the exchange-session's `externalProviderSessionData` (Step 1) |
| componentOptions | Options for Flow subcomponents. For this integration, set `card.displayCardholderName: "hidden"` so the cardholder name is not collected in Flow; you collect it when creating the funding source (Step 4) via `cardDetails.firstName` and `cardDetails.lastName`. |
| onReady | Callback fired when the component is ready |
| onPaymentCompleted | Callback fired when card capture succeeds. Contains the payment ID |
| onChange | Callback fired when form validity changes |
| onError | Callback fired when an error occurs |

### Customizing Flow Appearance

The Flow component can be customized to match your application's branding. You can configure colors, fonts, and border styles by passing an `appearance` object when initializing the component:

```javascript theme={"dark"}
const checkout = await CheckoutWebComponents({
  publicKey: PUBLIC_KEY,
  environment: "sandbox",
  paymentSession,
  appearance: {
    colorAction: '#5E48FC',
    colorBackground: '#FFFFFF',
    colorPrimary: '#1A1A1A',
    colorSecondary: '#6B7280',
    borderRadius: ['8px', '8px']
  },
  // ... callbacks/componentOptions
});
```

For complete customization options, see the [Flow Customization documentation](https://www.checkout.com/docs/payments/accept-payments/accept-a-payment-on-your-website/customize-your-flow-integration).

## Step 3: Create an Exchange in Dwolla

Server-side

When the customer submits their card, Flow's `onPaymentCompleted` callback returns the payment ID (`pay_xxx`). Send this payment ID to your server and use it as the `token` when creating the Exchange.

### Request Parameters

| Parameter | Required | Type | Description |
| - | - | - | - |
| \_links.exchange-partner.href | yes | string | URL of the exchange-partner resource. |
| token | yes | string | The payment ID (`pay_xxx`) from Flow's `onPaymentCompleted` callback. |

### Request and Response

```bash Create Exchange theme={"dark"}
POST https://api-sandbox.dwolla.com/customers/247f401e-0363-4370-baa5-ab7e97d98079/exchanges
Content-Type: application/vnd.dwolla.v1.hal+json
Accept: application/vnd.dwolla.v1.hal+json
Authorization: Bearer your_dwolla_access_token

{
  "_links": {
    "exchange-partner": {
      "href": "https://api-sandbox.dwolla.com/exchange-partners/d652517d-9c02-4ea4-87af-2977e6cf3850"
    }
  },
  "token": "pay_abc123xyz789"
}

HTTP/1.1 201 Created
Location: https://api-sandbox.dwolla.com/exchanges/d46b028c-244b-4054-b19b-72f09cd1dc04
```

```javascript createExchange.js theme={"dark"}
const customerId = "247f401e-0363-4370-baa5-ab7e97d98079";
const exchangePartnerId = "d652517d-9c02-4ea4-87af-2977e6cf3850"; // Provided by Dwolla
const paymentId = "pay_abc123xyz789"; // From Flow's onPaymentCompleted callback

const response = await dwolla.post(
  `https://api-sandbox.dwolla.com/customers/${customerId}/exchanges`,
  {
    _links: {
      "exchange-partner": {
        href: `https://api-sandbox.dwolla.com/exchange-partners/${exchangePartnerId}`,
      },
    },
    token: paymentId,
  }
);

const exchangeLocation = response.headers.get("Location");
// => "https://api-sandbox.dwolla.com/exchanges/d46b028c-244b-4054-b19b-72f09cd1dc04"
```

```ruby create_exchange.rb theme={"dark"}
customer_id = "247f401e-0363-4370-baa5-ab7e97d98079"
exchange_partner_id = "d652517d-9c02-4ea4-87af-2977e6cf3850"
payment_id = "pay_abc123xyz789"

request_body = {
  _links: {
    "exchange-partner" => {
      href: "https://api-sandbox.dwolla.com/exchange-partners/#{exchange_partner_id}"
    }
  },
  token: payment_id
}

# Using DwollaV2 - https://github.com/Dwolla/dwolla-v2-ruby
response = app_token.post(
  "https://api-sandbox.dwolla.com/customers/#{customer_id}/exchanges",
  request_body
)
exchange_location = response.response_headers[:location]
```

Save the Exchange URL from the `Location` header. You'll pass this as `_links.exchange.href` when creating the funding source in Step 4.

### Retrieve the ANI Result

If you requested an Account Name Inquiry in [Step 1A](#optional-check-the-cardholder-name-with-account-name-inquiry), send a GET request to the Exchange URL to read the name-match result from `cardDetails.accountNameInquiry`. Check this value before creating the funding source so you can stop a card that the issuer does not associate with your Customer.

```bash Retrieve Exchange theme={"dark"}
GET https://api-sandbox.dwolla.com/exchanges/d46b028c-244b-4054-b19b-72f09cd1dc04
Accept: application/vnd.dwolla.v1.hal+json
Authorization: Bearer your_dwolla_access_token
```

```json Response theme={"dark"}
{
  "_links": {
    "self": {
      "href": "https://api-sandbox.dwolla.com/exchanges/d46b028c-244b-4054-b19b-72f09cd1dc04",
      "type": "application/vnd.dwolla.v1.hal+json",
      "resource-type": "exchange"
    },
    "exchange-partner": {
      "href": "https://api-sandbox.dwolla.com/exchange-partners/d652517d-9c02-4ea4-87af-2977e6cf3850",
      "type": "application/vnd.dwolla.v1.hal+json",
      "resource-type": "exchange-partner"
    }
  },
  "id": "d46b028c-244b-4054-b19b-72f09cd1dc04",
  "status": "active",
  "created": "2026-02-20T10:14:02.118Z",
  "cardDetails": {
    "accountNameInquiry": "full-match"
  }
}
```

```javascript retrieveExchangeAni.js theme={"dark"}
const exchangeUrl = response.headers.get("Location");

const exchangeResponse = await dwolla.get(exchangeUrl);
const exchange = await exchangeResponse.body;
const aniResult = exchange.cardDetails?.accountNameInquiry; // => "full-match"
```

```ruby retrieve_exchange_ani.rb theme={"dark"}
exchange_url = response.response_headers[:location]

# Using DwollaV2 - https://github.com/Dwolla/dwolla-v2-ruby
exchange = app_token.get exchange_url
ani_result = exchange.cardDetails&.accountNameInquiry # => "full-match"
```

`accountNameInquiry` returns one of the following values:

| Value | Meaning |
| - | - |
| `full-match` | The name you supplied matched the name the card issuer has on file. |
| `partial-match` | Part of the name matched, for example the last name but not the first. |
| `no-match` | The name you supplied did not match the name the card issuer has on file. |
| `not-performed` | ANI was not requested, or the check did not run. |
| `not-supported` | The card issuer or network does not support ANI. |

> [!NOTE]
>
> Dwolla returns these values in hyphenated form (`full-match`), which differs from the underscored form the card network uses internally. Match on the hyphenated values.

> [!NOTE]
>
> ANI is informational. A result other than `full-match` does not block you from creating the funding source, and `cardDetails` is absent entirely when ANI was not requested. Decide in your own application how to treat each result.

## Step 4: Create a Funding Source in Dwolla

Server-side

With the Exchange URL from Step 3, create a card funding source for your Customer by linking to that Exchange and providing a name and cardDetails (cardholder firstName, lastName, and billing address). For complete API details, see [Create a funding source for a customer](https://developers.dwolla.com/docs/api-reference/funding-sources/create-customer-funding-source.md).

`cardDetails` also accepts three optional identity fields about the cardholder: `dateOfBirth`, `countryOfBirth`, and `identification`. Supplying them reduces the number of sanctions screening alerts raised when the card is registered and processed, because they give the screening engine enough context to tell your cardholder apart from a similarly named person and clear matches automatically. Fewer alerts means less manual compliance review and fewer delayed payouts. See [Cardholder identity fields](#cardholder-identity-fields) below.

### Request Parameters

**\_links** `object` *required*

Exchange resource to link for the card funding source.

**child attributes**

**\_links.exchange** `object` *required*

**child attributes**

**\_links.exchange.href** `string\<uri\>` *required*

Example: `https://api-sandbox.dwolla.com/exchanges/6bc9109a-04fd-49b6-ace6-ca06fd282d65`

**name** `string` *required*

Arbitrary nickname for the funding source (max 50 characters).

**cardDetails** `object` *required*

Cardholder name and billing address.

**child attributes**

**cardDetails.firstName** `string` *required*

Cardholder first name.

**cardDetails.lastName** `string` *required*

Cardholder last name.

**cardDetails.billingAddress** `object` *required*

Billing address associated with the card.

**child attributes**

**cardDetails.billingAddress.address1** `string` *required*

Street address line 1.

**cardDetails.billingAddress.address2** `string`

Street address line 2 (apartment, suite, etc.).

**cardDetails.billingAddress.city** `string` *required*

City.

**cardDetails.billingAddress.stateProvinceRegion** `string` *required*

Two-letter state/province code.

**cardDetails.billingAddress.postalCode** `string` *required*

Postal/ZIP code.

**cardDetails.billingAddress.country** `string` *required*

Two-letter ISO country code (e.g., `US`).

**cardDetails.dateOfBirth** `string`

Cardholder date of birth in `YYYY-MM-DD` format.

**cardDetails.countryOfBirth** `string`

Cardholder country of birth as a two-letter ISO country code (e.g., `US`). Must be exactly two characters.

**cardDetails.identification** `object`

A government identification document for the cardholder. Write-only -- this object is never returned when you retrieve the funding source.

**child attributes**

**cardDetails.identification.type** `string` *required*

The kind of document provided. One of `passport`, `license`, `idCard`, or `taxId`.

**cardDetails.identification.number** `string` *required*

The document number.

**cardDetails.identification.country** `string` *required*

Country that issued the document, as a two-letter ISO country code (e.g., `GB`). Must be exactly two characters.

#### Cardholder Identity Fields

All three identity fields are optional and independent -- send any combination of them, or none at all. Omitting them does not prevent you from creating a card funding source.

If you don't have the values at card capture time, you can add them later by [updating the card funding source](#updating-a-card-funding-source).

When `identification` is present, all three of its child fields (`type`, `number`, and `country`) are required.

### Request and Response

```bash Create Card Funding Source theme={"dark"}
POST https://api-sandbox.dwolla.com/customers/247f401e-0363-4370-baa5-ab7e97d98079/funding-sources
Content-Type: application/vnd.dwolla.v1.hal+json
Accept: application/vnd.dwolla.v1.hal+json
Authorization: Bearer your_dwolla_access_token

{
  "_links": {
    "exchange": {
      "href": "https://api-sandbox.dwolla.com/exchanges/d46b028c-244b-4054-b19b-72f09cd1dc04"
    }
  },
  "name": "My Debit Card",
  "cardDetails": {
    "firstName": "Jane",
    "lastName": "Doe",
    "billingAddress": {
      "address1": "123 Main St",
      "address2": "Apt 4B",
      "city": "Dallas",
      "stateProvinceRegion": "TX",
      "country": "US",
      "postalCode": "76034"
    },
    "dateOfBirth": "1990-01-15",
    "countryOfBirth": "US",
    "identification": {
      "type": "passport",
      "number": "P123456",
      "country": "GB"
    }
  }
}

HTTP/1.1 201 Created
Location: https://api-sandbox.dwolla.com/funding-sources/12fb2f3c-39c7-40cf-99e2-b0311ba39261
```

```ruby create_card_funding_source.rb theme={"dark"}
customer_url = 'https://api-sandbox.dwolla.com/customers/247f401e-0363-4370-baa5-ab7e97d98079'
exchange_url = 'https://api-sandbox.dwolla.com/exchanges/d46b028c-244b-4054-b19b-72f09cd1dc04'

request_body = {
  _links: {
    exchange: {
      href: exchange_url
    }
  },
  name: 'My Debit Card',
  cardDetails: {
    firstName: 'Jane',
    lastName: 'Doe',
    billingAddress: {
      address1: '123 Main St',
      address2: 'Apt 4B',
      city: 'Dallas',
      stateProvinceRegion: 'TX',
      country: 'US',
      postalCode: '76034'
    },
    dateOfBirth: '1990-01-15',
    countryOfBirth: 'US',
    identification: {
      type: 'passport',
      number: 'P123456',
      country: 'GB'
    }
  }
}

# Using DwollaV2 - https://github.com/Dwolla/dwolla-v2-ruby
funding_source = app_token.post "#{customer_url}/funding-sources", request_body
funding_source.response_headers[:location] # => "https://api-sandbox.dwolla.com/funding-sources/12fb2f3c-39c7-40cf-99e2-b0311ba39261"
```

```javascript createCardFundingSource.js theme={"dark"}
const customerUrl =
  "https://api-sandbox.dwolla.com/customers/247f401e-0363-4370-baa5-ab7e97d98079";
const exchangeUrl =
  "https://api-sandbox.dwolla.com/exchanges/d46b028c-244b-4054-b19b-72f09cd1dc04";

const requestBody = {
  _links: {
    exchange: {
      href: exchangeUrl,
    },
  },
  name: "My Debit Card",
  cardDetails: {
    firstName: "Jane",
    lastName: "Doe",
    billingAddress: {
      address1: "123 Main St",
      address2: "Apt 4B",
      city: "Dallas",
      stateProvinceRegion: "TX",
      country: "US",
      postalCode: "76034",
    },
    dateOfBirth: "1990-01-15",
    countryOfBirth: "US",
    identification: {
      type: "passport",
      number: "P123456",
      country: "GB",
    },
  },
};

dwolla
  .post(`${customerUrl}/funding-sources`, requestBody)
  .then((res) => res.headers.get("location"));
// => "https://api-sandbox.dwolla.com/funding-sources/12fb2f3c-39c7-40cf-99e2-b0311ba39261"
```

```python create_card_funding_source.py theme={"dark"}
customer_url = 'https://api-sandbox.dwolla.com/customers/247f401e-0363-4370-baa5-ab7e97d98079'
exchange_url = 'https://api-sandbox.dwolla.com/exchanges/d46b028c-244b-4054-b19b-72f09cd1dc04'

request_body = {
  '_links': {
    'exchange': {
      'href': exchange_url
    }
  },
  'name': 'My Debit Card',
  'cardDetails': {
    'firstName': 'Jane',
    'lastName': 'Doe',
    'billingAddress': {
      'address1': '123 Main St',
      'address2': 'Apt 4B',
      'city': 'Dallas',
      'stateProvinceRegion': 'TX',
      'country': 'US',
      'postalCode': '76034'
    },
    'dateOfBirth': '1990-01-15',
    'countryOfBirth': 'US',
    'identification': {
      'type': 'passport',
      'number': 'P123456',
      'country': 'GB'
    }
  }
}

# Using dwollav2 - https://github.com/Dwolla/dwolla-v2-python
funding_source = app_token.post('%s/funding-sources' % customer_url, request_body)
funding_source.headers['location']
# => 'https://api-sandbox.dwolla.com/funding-sources/12fb2f3c-39c7-40cf-99e2-b0311ba39261'
```

When the funding source is successfully created, you'll receive a `201 Created` response with the Location header containing the URL of the new card funding source.

### Retrieve the Card Funding Source

After creating the card funding source, you can retrieve it to confirm the details and verify its status:

```bash Retrieve Card Funding Source theme={"dark"}
GET https://api-sandbox.dwolla.com/funding-sources/12fb2f3c-39c7-40cf-99e2-b0311ba39261
Accept: application/vnd.dwolla.v1.hal+json
Authorization: Bearer your_dwolla_access_token
```

```json Response [expandable] theme={"dark"}
{
  "_links": {
    "self": {
      "href": "https://api-sandbox.dwolla.com/funding-sources/12fb2f3c-39c7-40cf-99e2-b0311ba39261",
      "type": "application/vnd.dwolla.v1.hal+json",
      "resource-type": "funding-source"
    },
    "transfer-to-balance": {
      "href": "https://api-sandbox.dwolla.com/transfers",
      "type": "application/vnd.dwolla.v1.hal+json",
      "resource-type": "transfer"
    },
    "remove": {
      "href": "https://api-sandbox.dwolla.com/funding-sources/12fb2f3c-39c7-40cf-99e2-b0311ba39261",
      "type": "application/vnd.dwolla.v1.hal+json",
      "resource-type": "funding-source"
    },
    "customer": {
      "href": "https://api-sandbox.dwolla.com/customers/247f401e-0363-4370-baa5-ab7e97d98079",
      "type": "application/vnd.dwolla.v1.hal+json",
      "resource-type": "customer"
    },
    "transfer-receive": {
      "href": "https://api-sandbox.dwolla.com/transfers",
      "type": "application/vnd.dwolla.v1.hal+json",
      "resource-type": "transfer"
    }
  },
  "id": "12fb2f3c-39c7-40cf-99e2-b0311ba39261",
  "status": "verified",
  "type": "card",
  "name": "My Debit Card",
  "created": "2025-12-10T18:02:47.985Z",
  "removed": false,
  "channels": [],
  "fingerprint": "D7E2CF4D9477D83A8E9FA73E109ACA6D171B8AD98E37F54A3B7D13DA1D3801D2",
  "cardDetails": {
    "brand": "VISA",
    "lastFour": "1519",
    "expirationMonth": 10,
    "expirationYear": 2027,
    "nameOnCard": "Jane Doe",
    "bin": "40247644",
    "billingAddress": {
      "address1": "123 Main St",
      "address2": "Apt 4B",
      "city": "Dallas",
      "stateProvinceRegion": "TX",
      "country": "US",
      "postalCode": "76034"
    },
    "dateOfBirth": "1990-01-15",
    "countryOfBirth": "US"
  }
}
```

`dateOfBirth` and `countryOfBirth` appear only if you supplied them. The `identification` object is write-only and is never returned.

> [!NOTE]
>
> Card funding sources are created with a `verified` status and are immediately eligible to receive Push to Card payouts. The card was already validated by the \$0 authorization that ran during card capture in [Step 2](#step-2-mount-the-card-capture-component), so unlike bank funding sources, there is no separate verification step for you to trigger.

## Updating a Card Funding Source

To update a card funding source, send a POST request to the funding source URL with only the fields you want to change. This is how you add the optional cardholder identity fields to a card that already exists, or correct values you sent earlier.

Every field is optional, but you must provide at least one. Fields you omit keep their current values.

| Parameter | Required | Type | Description |
| - | - | - | - |
| name | no | string | Arbitrary nickname for the funding source (max 50 characters). |
| cardDetails | no | object | Cardholder details to add or update. Accepts `firstName`, `lastName`, `billingAddress`, `dateOfBirth`, `countryOfBirth`, and `identification`, with the same formats used when [creating the card](#cardholder-identity-fields). |

```bash Update Card Funding Source theme={"dark"}
POST https://api-sandbox.dwolla.com/funding-sources/12fb2f3c-39c7-40cf-99e2-b0311ba39261
Content-Type: application/vnd.dwolla.v1.hal+json
Accept: application/vnd.dwolla.v1.hal+json
Authorization: Bearer your_dwolla_access_token

{
  "cardDetails": {
    "dateOfBirth": "1990-01-15",
    "countryOfBirth": "US",
    "identification": {
      "type": "passport",
      "number": "P123456",
      "country": "GB"
    }
  }
}

HTTP/1.1 200 OK
```

```ruby update_card_funding_source.rb theme={"dark"}
funding_source_url = 'https://api-sandbox.dwolla.com/funding-sources/12fb2f3c-39c7-40cf-99e2-b0311ba39261'

request_body = {
  cardDetails: {
    dateOfBirth: '1990-01-15',
    countryOfBirth: 'US',
    identification: {
      type: 'passport',
      number: 'P123456',
      country: 'GB'
    }
  }
}

# Using DwollaV2 - https://github.com/Dwolla/dwolla-v2-ruby
funding_source = app_token.post funding_source_url, request_body
funding_source.cardDetails.countryOfBirth # => "US"
```

```javascript updateCardFundingSource.js theme={"dark"}
const fundingSourceUrl =
  "https://api-sandbox.dwolla.com/funding-sources/12fb2f3c-39c7-40cf-99e2-b0311ba39261";

const requestBody = {
  cardDetails: {
    dateOfBirth: "1990-01-15",
    countryOfBirth: "US",
    identification: {
      type: "passport",
      number: "P123456",
      country: "GB",
    },
  },
};

// Using dwolla-v2 - https://github.com/Dwolla/dwolla-v2-node
const response = await dwolla.post(fundingSourceUrl, requestBody);
const cardDetails = (await response.body).cardDetails; // => countryOfBirth: "US"
```

```python update_card_funding_source.py theme={"dark"}
funding_source_url = 'https://api-sandbox.dwolla.com/funding-sources/12fb2f3c-39c7-40cf-99e2-b0311ba39261'

request_body = {
    'cardDetails': {
        'dateOfBirth': '1990-01-15',
        'countryOfBirth': 'US',
        'identification': {
            'type': 'passport',
            'number': 'P123456',
            'country': 'GB'
        }
    }
}

# Using dwollav2 - https://github.com/Dwolla/dwolla-v2-python
funding_source = app_token.post(funding_source_url, request_body)
funding_source.body['cardDetails']['countryOfBirth'] # => 'US'
```

> [!WARNING]
>
> Card funding sources cannot be updated with bank fields. Sending `routingNumber`, `accountNumber`, `bankAccountType`, or `bankAccountHolderType` to a card funding source returns a validation error.

### Update Errors

> [accordion] No Fields Provided
>
> An update request must contain at least one updateable field.
>
> ```json theme={"dark"}
> {
>   "code": "ValidationError",
>   "message": "Validation error(s) present. See embedded errors list for more details.",
>   "_embedded": {
>     "errors": [
>       {
>         "code": "Invalid",
>         "message": "At least one field must be provided for update",
>         "path": "/"
>       }
>     ]
>   }
> }
> ```
>
> **Resolution:** Include `name`, `cardDetails`, or both in the request body.

> [accordion] Bank Fields Sent to a Card
>
> Bank-specific fields are not valid on a card funding source.
>
> ```json theme={"dark"}
> {
>   "code": "ValidationError",
>   "message": "Validation error(s) present. See embedded errors list for more details.",
>   "_embedded": {
>     "errors": [
>       {
>         "code": "NotAllowed",
>         "message": "Card funding sources cannot be updated with bank fields",
>         "path": "/routingNumber"
>       }
>     ]
>   }
> }
> ```
>
> **Resolution:** Remove the bank fields and send only `name` and `cardDetails`.

> [accordion] Invalid Identity Field
>
> `countryOfBirth` and `identification.country` must each be exactly two characters, and `dateOfBirth` must be a valid date in `YYYY-MM-DD` format.
>
> ```json theme={"dark"}
> {
>   "code": "ValidationError",
>   "message": "Validation error(s) present. See embedded errors list for more details.",
>   "_embedded": {
>     "errors": [
>       {
>         "code": "InvalidFormat",
>         "message": "Card details country of birth must be exactly 2 characters.",
>         "path": "/cardDetails/countryOfBirth"
>       }
>     ]
>   }
> }
> ```
>
> **Resolution:** Send a two-letter ISO country code, such as `US`, and format dates as `YYYY-MM-DD`.

## Removing a Card Funding Source

To remove a card funding source, send a POST request to the funding source URL with `removed` set to `true`. Once removed, the card can no longer receive Push to Card payouts.

```bash Remove Card Funding Source theme={"dark"}
POST https://api-sandbox.dwolla.com/funding-sources/12fb2f3c-39c7-40cf-99e2-b0311ba39261
Content-Type: application/vnd.dwolla.v1.hal+json
Accept: application/vnd.dwolla.v1.hal+json
Authorization: Bearer your_dwolla_access_token

{
  "removed": true
}

HTTP/1.1 200 OK
```

```ruby remove_card_funding_source.rb theme={"dark"}
funding_source_url = 'https://api-sandbox.dwolla.com/funding-sources/12fb2f3c-39c7-40cf-99e2-b0311ba39261'

request_body = {
  removed: true
}

# Using DwollaV2 - https://github.com/Dwolla/dwolla-v2-ruby
app_token.post funding_source_url, request_body
```

```javascript removeCardFundingSource.js theme={"dark"}
const fundingSourceUrl =
  "https://api-sandbox.dwolla.com/funding-sources/12fb2f3c-39c7-40cf-99e2-b0311ba39261";

const requestBody = {
  removed: true,
};

// Using dwolla-v2 - https://github.com/Dwolla/dwolla-v2-node
dwolla.post(fundingSourceUrl, requestBody);
```

```python remove_card_funding_source.py theme={"dark"}
funding_source_url = 'https://api-sandbox.dwolla.com/funding-sources/12fb2f3c-39c7-40cf-99e2-b0311ba39261'

request_body = {
    'removed': True
}

# Using dwollav2 - https://github.com/Dwolla/dwolla-v2-python
app_token.post(funding_source_url, request_body)
```

When a card is removed, a `customer_card_funding_source_removed` webhook is triggered. For more information on webhook events, see [Webhook Events](https://developers.dwolla.com/docs/webhook-events.md).

## Handling Errors

When creating an Exchange (Step 3) or a card funding source (Step 4), the Dwolla API may return validation errors. Handle these errors gracefully in your application.

### Common Error Responses

> [accordion] Invalid Token (Exchange)
>
> Returned when creating an Exchange (Step 3) if the `token` provided is invalid or malformed.
>
> ```json theme={"dark"}
> {
>   "code": "ValidationError",
>   "message": "Validation error(s) present. See embedded errors list for more details.",
>   "_embedded": {
>     "errors": [
>       {
>         "code": "Invalid",
>         "message": "The card data is invalid.",
>         "path": "/token",
>         "_links": {}
>       }
>     ]
>   }
> }
> ```

> [accordion] Unsupported Country
>
> Returned when the card's issuing country is not supported for Push to Card. Only US-based cards are supported.
>
> ```json theme={"dark"}
> {
>   "code": "ValidationError",
>   "message": "Validation error(s) present. See embedded errors list for more details.",
>   "_embedded": {
>     "errors": [
>       {
>         "code": "Invalid",
>         "message": "The card's country is not supported.",
>         "path": "/token"
>       }
>     ]
>   }
> }
> ```

> [accordion] Maximum Number of Cards Exceeded
>
> Returned when the Customer has reached the maximum number of allowed card funding sources. The default maximum is 2 cards per Customer, but this limit can be increased if needed.
>
> ```json theme={"dark"}
> {
>   "code": "ValidationError",
>   "message": "Validation error(s) present. See embedded errors list for more details.",
>   "_embedded": {
>     "errors": [
>       {
>         "code": "Invalid",
>         "message": "The maximum number of cards has been exceeded.",
>         "path": "/_links/exchange"
>       }
>     ]
>   }
> }
> ```

> [accordion] Missing Required Fields
>
> Returned when required fields are missing from the Exchange or funding source request.
>
> ```json theme={"dark"}
> {
>   "code": "ValidationError",
>   "message": "Validation error(s) present. See embedded errors list for more details.",
>   "_embedded": {
>     "errors": [
>       {
>         "code": "Required",
>         "message": "Token is required.",
>         "path": "/token"
>       },
>       {
>         "code": "Required",
>         "message": "Name is required.",
>         "path": "/name"
>       }
>     ]
>   }
> }
> ```

## Next Steps

Now that you've added a debit card as a funding source, you're ready to initiate Push to Card payouts.

> [card] [Understanding Push to Card](https://developers.dwolla.com/docs/push-to-card.md)
>
> Learn about the good funds model, settlement accounts, and how Push to Card transfers work

> [card] [Initiating a Push to Card Transfer](https://developers.dwolla.com/docs/push-to-card.md#initiating-a-push-to-card-payout)
>
> Learn how to send instant payouts to debit cards


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.