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

# Stored Disbursement Methods

A stored disbursement method (SDM) is a saved ACH bank account or debit card that a Firm can use for later disbursements. This guide explains how to collect the payment details with a Confido-hosted iframe, then create the SDM from your server with the Firm API token.

The embed supports **manual ACH** (`ACH_DIRECT`) and **push-to-card** (`PUSH_TO_CARD`, debit cards only).

<Accordion title="Note for partners using the form">
  If you are integrating as a partner, use a **Firm API token** (`f_…`) for the Firm whose stored disbursement method you are creating. A Partner API token cannot mint an SDM token or create an SDM.

  You can obtain a Firm API token by:

  * Saving the `apiToken` returned by `createFirm`
  * Exchanging a Connect code for a Firm API token

  Keep the Firm API token on your server. Only the short-lived SDM token from `storedDisbursementMethodTokenCreate` may be sent to the browser.
</Accordion>

## Prerequisites

* The Firm is `ACTIVE` and disbursements are enabled for that Firm
* You have the Firm API token for the Firm on your server
* The Firm is configured to allow the disbursement method you intend to collect
* Register your application's parent-page origin as a [trusted domain](/docs/hosted-fields/trusted-domains). Required in sandbox and production.

In sandbox, `createFirm(input: { mockOnboarding: true })` activates the Firm, enables disbursements, and returns a Firm API token you can use immediately. `mockOnboarding` is available in sandbox only.

## High level flow

<Steps>
  <Step title="Mint a stored disbursement method token from your server" />

  <Step title="Embed the stored disbursement method form in an iframe" />

  <Step title="Submit the form from the parent and wait until the details are staged" />

  <Step title="Create the stored disbursement method from your server" />
</Steps>

## 1. Mint a token

Call `storedDisbursementMethodTokenCreate` with the Firm API token:

```graphql theme={"system"}
mutation StoredDisbursementMethodTokenCreate {
  storedDisbursementMethodTokenCreate {
    token
    expiresAt
  }
}
```

Send the returned `token` to your frontend. Mint a fresh token when the form opens. SDM tokens expire after **30 minutes**. If staging fails because the token expired, mint another and reload the iframe `src`.

## 2. Embed the form

| Environment | Base URL                               |
| ----------- | -------------------------------------- |
| Sandbox     | `https://pay.sandbox.confidolegal.com` |
| Production  | `https://pay.confidolegal.com`         |

| Path              | Form                  |
| ----------------- | --------------------- |
| `/sdm/embed`      | Tabs for card and ACH |
| `/sdm/embed/card` | Push-to-card only     |
| `/sdm/embed/ach`  | Manual ACH only       |

**Query parameters**

* **`token`** (required) — value from `storedDisbursementMethodTokenCreate`
* **`opts`** (optional) — `encodeURIComponent(JSON.stringify(branding))` where branding is `{ backgroundColor, centerColor, footerText, headerImg, headerName, partnerImg }` (`footerText` / image fields may be `null`)

The tabbed embed loads recipient ACH and instant fees from the Firm's billing settings. You do not pass fees in the URL.

```html theme={"system"}
<iframe
  id="sdm-embed"
  title="Add disbursement method"
  src="https://pay.sandbox.confidolegal.com/sdm/embed?token=TOKEN"
  style="border: none; width: 100%; min-height: 480px;"
></iframe>
```

A missing or invalid token, or a parent origin that is not a trusted domain, returns HTTP 403 and posts `confido:sdm_load_error`.

## 3. Submit the form from the parent

The iframe has no Save button. Listen for its events, enable your application's Save button when the form is valid, and post `confido:sdm_submit` to the payment-page origin when the user clicks Save.

Check `event.origin` against the payment-page origin. Post `confido:sdm_submit` **to that origin**, not `*`.

```javascript theme={"system"}
const PAY_ORIGIN = 'https://pay.sandbox.confidolegal.com'; // production: https://pay.confidolegal.com
const iframe = document.getElementById('sdm-embed');
const save = document.getElementById('save');
let isReady = false;
let isValid = false;

function _syncSaveButton() {
  save.disabled = !(isReady && isValid);
}

window.addEventListener('message', (event) => {
  if (event.origin !== PAY_ORIGIN) return;
  const { type } = event.data || {};

  if (type === 'confido:sdm_fields_loaded') {
    isReady = true;
    _syncSaveButton();
  }

  if (type === 'confido:sdm_height_change') {
    iframe.style.height = `${Math.max(480, event.data.height)}px`;
  }

  if (type === 'confido:sdm_validity_change') {
    isValid = event.data.isValid;
    _syncSaveButton();
  }

  if (type === 'confido:sdm_staged') {
    fetch('/your-backend/sdm/create', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        token, // the SDM token, not the Firm API token
        vendorId, // or clientId — exactly one
        payeeEmail,
        nickname,
      }),
    });
  }

  if (type === 'confido:sdm_stage_error' || type === 'confido:sdm_load_error') {
    // Show event.data.message. If the token expired, mint a new one and reload src.
  }
});

save.addEventListener('click', () => {
  iframe.contentWindow.postMessage({ type: 'confido:sdm_submit' }, PAY_ORIGIN);
});
```

### Events from the iframe

Every message is `{ type, ...payload }`.

| `type`                            | Payload                     | When                                                                                                                                                               |
| --------------------------------- | --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `confido:sdm_page_ready`          | —                           | Iframe loaded                                                                                                                                                      |
| `confido:sdm_fields_loaded`       | —                           | Forms are ready                                                                                                                                                    |
| `confido:sdm_height_change`       | `{ height }`                | Content height changed (pixels)                                                                                                                                    |
| `confido:sdm_validity_change`     | `{ isValid, method }`       | Enable or disable your Save button. `method` is `ACH_DIRECT` or `PUSH_TO_CARD`                                                                                     |
| `confido:sdm_method_change`       | `{ method }`                | User switched tabs                                                                                                                                                 |
| `confido:sdm_bin_change`          | `{ brand?, cardType? }`     | Card BIN lookup result                                                                                                                                             |
| `confido:sdm_field_error`         | `{ field, message }`        | Field-level validation error                                                                                                                                       |
| `confido:sdm_field_error_cleared` | `{ field }`                 | That error cleared                                                                                                                                                 |
| `confido:sdm_staging`             | —                           | Submit started                                                                                                                                                     |
| `confido:sdm_staged`              | `{ method, maskedDetails }` | Safe to call `storedDisbursementMethodCreate`. `maskedDetails` includes `lastFour` and `method`; card may include `brand` / `cardType`, ACH may include `bankName` |
| `confido:sdm_stage_error`         | `{ message }`               | Staging failed (expired token, validation, etc.)                                                                                                                   |
| `confido:sdm_load_error`          | `{ message? }`              | Iframe could not load                                                                                                                                              |

### Command into the iframe

| `type`               | Payload | When                          |
| -------------------- | ------- | ----------------------------- |
| `confido:sdm_submit` | —       | User clicked your Save button |

## 4. Create the stored disbursement method

After receiving `confido:sdm_staged`, send the SDM token and your non-sensitive metadata to your backend. Call `storedDisbursementMethodCreate` there with the Firm API token.

```graphql theme={"system"}
mutation StoredDisbursementMethodCreate(
  $input: StoredDisbursementMethodCreateInput!
) {
  storedDisbursementMethodCreate(input: $input) {
    storedDisbursementMethod {
      id
      method
      nickname
      displayName
      details {
        lastFour
        brand
        bankName
        routingNumber
        accountHolderName
      }
      client {
        id
      }
      vendor {
        id
      }
    }
  }
}
```

**Input**

* **`token`** (required) — the same SDM token used in the iframe
* **`payeeEmail`** (required) — valid email for the payee
* **`clientId`** or **`vendorId`** (required) — exactly one
* **`nickname`** (optional)

The SDM token is scoped to the Firm, and the Firm API token used for create must belong to that same Firm. The token cannot create an SDM by itself.

On success the cache entry is deleted. Creating again with the same token fails. A successful create emits [`stored_disbursement_method.created`](/docs/webhooks/webhook-types).

## Using a stored method

List methods for a client or vendor with the Firm API token:

```graphql theme={"system"}
query StoredDisbursementMethodsList($vendorId: String, $clientId: String) {
  storedDisbursementMethodsList(vendorId: $vendorId, clientId: $clientId) {
    total
    storedDisbursementMethods {
      id
      method
      nickname
      displayName
      archived
      details {
        lastFour
      }
    }
  }
}
```

To send funds to a saved method, create a disbursement as usual, then call `disbursementInitiateTxnToSdm` with `{ disbursementId, sdmId }` using the Firm API token.

## Limits and errors

* **Token TTL** is 30 minutes. Remint and reload the iframe if staging reports an expiry.
* **Firm must be active.** Token create fails with `This firm is not active.`
* **Method must be allowed** on the Firm. Create fails with `{METHOD} is not allowed for this firm`.
* **Parent has no Save control inside the iframe.** If you never post `confido:sdm_submit`, details are never staged.
