# Direct Automated Payout

> Create a workflow that triggers a fiat payout whenever a crypto deposit is detected, removing the need to pre-fund.

## Overview

The Automated Payout API enables businesses to create workflows that automatically trigger fiat payouts when cryptocurrency deposits are detected in their accounts, eliminating the need for pre-funding, and enabling your customer to settle with Noah directly.

Use the [POST `workflows/onchain-deposit-to-payment-method`](../../../api-reference/onchain-deposit-to-fiat-payout) endpoint to configure these workflows.

For more details on this product, see the [Automated Payout](../../products/automated-payouts.md) Product page.

### Example Scenario

Consider a freelance platform that enables contractors to receive payments by sending USDC to a designated address:

**Setup:**

- **CustomerID:** Contractor's profile with local banking details
- **Input:** USDC deposits to platform's designated address
- **Output:** Local fiat currency via bank transfer
- **Destination:** Contractor's registered bank account

**Key Implementation Features:**

- Deposit source triggers monitoring incoming crypto transactions
- Automated crypto-to-fiat conversion upon deposit detection
- Configurable workflow conditions for execution control
- Instant payout processing through local payment channels

## Recipe

Implement a scenario as described above by following the steps below.

<details>

<summary>1. Set Up the Environment</summary>

#### Register Your Interest

1. Register your interest or book a demo by contacting us on business@noah.com.
2. [Signup for a Sandbox account](https://business.sandbox.noah.com/auth/signup) and contact us to upgrade it to a business account.

#### Authentication & Request Signing

For guidance on generating and configuring your API keys, see the [Authentication](../../api-concepts/authentication/configuration.md) page.

1. Generate your Sandbox API key via the Business Dashboard.
2. Include your API key in the `X-Api-Key` header of all requests.
3. Request Signing is optional in Sandbox and required in Production. It is important to setup Request Signing before migrating to Production, to do this see the [Request Signing](../../api-concepts/authentication/signing.md) page.

#### Webhook Configuration

For guidance on webhook subscriptions and configuration, see the [Configuration](../../api-concepts/webhooks/configuration) page.

1. Optionally whitelist Noah's Webhook IP addresses, detailed on the [Whitelisting](../../api-concepts/webhooks/configuration#whitelisting) section.
2. Create a webhook subscription for the following event type, the details required to ingest the webhooks are also available on the respective page.
   - `Transaction` - see [Transaction Event](../../api-concepts/webhooks/transactions).

</details>

<details>

<summary>2. Create a Customer</summary>

Create a customer, if not already created, via the [PUT `customers/:CustomerID`](../../../api-reference/create-update-customer) endpoint.

```typescript
curl -L -X PUT 'https://api.sandbox.noah.com/v1/customers/:CustomerID' \
-H 'Content-Type: application/json' \
-H 'X-Api-Key: <X-Api-Key>' \
--data-raw '{
  "Type": "Individual",
  "FullName": {
    "FirstName": "string",
    "LastName": "string",
    "MiddleName": "string"
  },
  "DateOfBirth": "2024-04-16",
  "Email": "user@example.com",
  "PhoneNumber": "string",
  "Identities": [
    {
      "IssuingCountry": "US",
      "IDNumber": "string",
      "IssuedDate": "2024-04-16",
      "ExpiryDate": "2024-04-16",
      "IDType": "Passport"
    }
  ],
  "PrimaryResidence": {
    "Street": "string",
    "Street2": "string",
    "City": "string",
    "PostCode": "string",
    "State": "string",
    "Country": "US"
  }
}'
```

:::tip

- When integrating under the [Reliance Model](../../getting-started/kyc.md#reliance-model), you must ensure that any customer executing a transaction has a valid and complete KYC Status in your system. As an alternative to the Reliance Model, use the [Standard Model](../../getting-started/kyc.md#standard-model), in support of which a [Hosted Onboarding journey](../../recipes/onboarding/hosted-onboarding.md) is provided.
- Ensure your `CustomerID` is stored against the compliance profile of the same customer, to reference when creating transactions.

:::

</details>

<details>

<summary>3. Define the Payout Channel</summary>

1. Retrieve the list of available countries by calling the [GET `channels/sell/countries`](../../../api-reference/countries) endpoint.

```typescript
curl -L 'https://api.sandbox.noah.com/v1/channels/sell/countries' \
-H 'Accept: application/json' \
-H 'X-Api-Key: <X-Api-Key>'
```

2. Once you've determined that your country of interest is supported, retrieve the list of country-specific compatible payout channels by calling the [GET `channels/sell`](../../../api-reference/channels) endpoint, providing, at least, your settlement `CryptoCurrency`, `FiatCurrency`, and `Country`.

   For example, to retrieve US-oriented payout channels, use values such as the below.

```typescript
curl -L 'https://api.sandbox.noah.com/v1/channels/sell? \
Country=US& \
CryptoCurrency=USDC_TEST& \
FiatCurrency=USD& \
FiatAmount=100' \
-H 'Accept: application/json' \
-H 'X-Api-Key: <X-Api-Key>'
```

:::tip

- Testnet faucets typically only drip small amounts of tokens to prevent abuse, which severely limits testing scenarios involving larger transactions or complex workflows. By deploying the USDC_TEST cryptocurrency, as
  used in the examples above, Noah removes these constraints and enables developers to conduct comprehensive testing with realistic transaction volumes, stress test applications properly, and simulate real-world scenarios without constantly waiting for faucet refills.

- Determine the `FiatCurrency` as an [ISO 4217 currency code](https://en.wikipedia.org/wiki/ISO_4217#List_of_ISO_4217_currency_codes).

:::

3. On success, a payout channel is returned per supported `PaymentMethodTypes`, such as
   `BankAch`, `BankFedwire`, and `TokenizedCard`.

For example, when you retrieve US-oriented payout channels, the response body is such as the below (click below to see the payload). You will need the `ID` value in the next step to be able to generate the dynamic form for the payout channel of interest. For example, in the example below, `a4c3f754-094b-5f73-9da9-e0f1cb367f31` is the `ID` for the `BankFedwire` channel.

<details>
<summary>US-oriented demo response body (click to expand)</summary>

```json
{
  "Items": [
    {
      "Calculated": {
        "TotalFee": "4.5"
      },
      "Country": "US",
      "FiatCurrency": "USD",
      "ID": "ad52bb7c-2cd4-55a0-9547-518bb1b248f8",
      "Limits": {
        "MaxLimit": "1000000",
        "MinLimit": "1.2"
      },
      "PaymentMethodCategory": "Card",
      "PaymentMethodType": "TokenizedCard",
      "ProcessingSeconds": 60,
      "Rate": "1"
    },
    {
      "Calculated": {
        "TotalFee": "0.03"
      },
      "Country": "US",
      "FiatCurrency": "USD",
      "FormSchema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "type": "object",
        "properties": {
          "AccountHolderAddress": {
            "properties": {
              "Address": {
                "maxLength": 70,
                "title": "Address",
                "type": "string"
              },
              "City": {
                "maxLength": 35,
                "title": "Town",
                "type": "string"
              },
              "PostalCode": {
                "maxLength": 9,
                "minLength": 5,
                "title": "Postal Code",
                "type": "string"
              },
              "State": {
                "maxLength": 35,
                "title": "State/Province",
                "type": "string"
              }
            },
            "required": ["State", "City", "Address", "PostalCode"],
            "title": "Account Holder Address",
            "type": "object"
          },
          "BankDetails": {
            "properties": {
              "AccountNumber": {
                "maxLength": 17,
                "minLength": 4,
                "title": "Account Number",
                "type": "string"
              },
              "BankCode": {
                "maxLength": 9,
                "minLength": 9,
                "title": "Routing Number",
                "type": "string"
              }
            },
            "required": ["AccountNumber", "BankCode"],
            "title": "Bank Details",
            "type": "object"
          },
          "PaymentPurpose": {
            "title": "Payment Purpose",
            "type": "string"
          }
        },
        "required": ["AccountHolderAddress", "BankDetails", "PaymentPurpose"]
      },
      "ID": "a4c3f754-094b-5f73-9da9-e0f1cb367f31",
      "Limits": {
        "MaxLimit": "15000",
        "MinLimit": "0"
      },
      "PaymentMethodCategory": "Bank",
      "PaymentMethodType": "BankFedwire",
      "ProcessingSeconds": 86400,
      "Rate": "1"
    },
    {
      "Calculated": {
        "TotalFee": "0.03"
      },
      "Country": "US",
      "FiatCurrency": "USD",
      "FormSchema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "type": "object",
        "properties": {
          "AccountHolderAddress": {
            "properties": {
              "Address": {
                "maxLength": 70,
                "title": "Address",
                "type": "string"
              },
              "City": {
                "maxLength": 35,
                "title": "Town",
                "type": "string"
              },
              "PostalCode": {
                "maxLength": 9,
                "minLength": 5,
                "title": "Postal Code",
                "type": "string"
              },
              "State": {
                "maxLength": 35,
                "title": "State/Province",
                "type": "string"
              }
            },
            "required": ["State", "City", "Address", "PostalCode"],
            "title": "Account Holder Address",
            "type": "object"
          },
          "BankDetails": {
            "properties": {
              "AccountNumber": {
                "maxLength": 12,
                "minLength": 8,
                "title": "Account Number",
                "type": "string"
              },
              "BankCode": {
                "maxLength": 9,
                "minLength": 9,
                "title": "Routing Number",
                "type": "string"
              },
              "AccountType": {
                "enum": ["Checking", "Savings"],
                "title": "Account Type",
                "type": "string"
              }
            },
            "required": ["AccountNumber", "BankCode", "AccountType"],
            "title": "Bank Details",
            "type": "object"
          },
          "PaymentPurpose": {
            "title": "Payment Purpose",
            "type": "string"
          }
        },
        "required": ["AccountHolderAddress", "BankDetails", "PaymentPurpose"]
      },
      "ID": "214eab50-e22b-5e0f-b487-37dc1addee90",
      "Limits": {
        "MaxLimit": "15000",
        "MinLimit": "0"
      },
      "PaymentMethodCategory": "Bank",
      "PaymentMethodType": "BankAch",
      "ProcessingSeconds": 86400,
      "Rate": "1"
    }
  ]
}
```

</details>

</details>

<details>

<summary>4. Render or Populate the Dynamic Form</summary>

Noah's [Dynamic UI API](../../api-concepts/dynamic-ui.md) gives you full control over your frontend while removing the complexity of handling conditional, ever-evolving payment flows.

1. Call the [GET `channels/:ChannelID/form`](../../../api-reference/dynamic-form) endpoint with the value of the `ID` of the payout channel to retrieve the payout channel's `FormSchema`.

   For example, in the example below, `a4c3f754-094b-5f73-9da9-e0f1cb367f31` is the `ID` for the `BankFedwire` channel, retrieved in the previous step.

```typescript
curl -L 'https://api.sandbox.noah.com/v1/channels/a4c3f754-094b-5f73-9da9-e0f1cb367f31/form' \
-H 'Accept: application/json' \
-H 'X-Api-Key: <X-Api-Key>'
```

2. On success, the `FormSchema` for the specified payout channel is generated and defines the payload that is returned. For example, for a `BankFedwire` payout channel, the response body is such as the below (click below to see the payload).

<details>
<summary>Dynamically generated form schema for BankFedwire payout channel (click to expand)</summary>

```json
{
  "FormSchema": {
    "$schema": "http://json-schema.org/draft-07/schema#",
    "type": "object",
    "properties": {
      "AccountHolderAddress": {
        "properties": {
          "Address": {
            "maxLength": 70,
            "title": "Address",
            "type": "string"
          },
          "City": {
            "maxLength": 35,
            "title": "Town",
            "type": "string"
          },
          "PostalCode": {
            "maxLength": 9,
            "minLength": 5,
            "title": "Postal Code",
            "type": "string"
          },
          "State": {
            "maxLength": 35,
            "title": "State/Province",
            "type": "string"
          }
        },
        "required": ["State", "City", "Address", "PostalCode"],
        "title": "Account Holder Address",
        "type": "object"
      },
      "BankDetails": {
        "properties": {
          "AccountNumber": {
            "maxLength": 17,
            "minLength": 4,
            "title": "Account Number",
            "type": "string"
          },
          "BankCode": {
            "maxLength": 9,
            "minLength": 9,
            "title": "Routing Number",
            "type": "string"
          }
        },
        "required": ["AccountNumber", "BankCode"],
        "title": "Bank Details",
        "type": "object"
      },
      "PaymentPurpose": {
        "title": "Payment Purpose",
        "type": "string"
      }
    },
    "required": ["AccountHolderAddress", "BankDetails", "PaymentPurpose"]
  }
}
```

</details>

:::tip

Render the `FormSchema` in your user interface to collect input from your customers, or populate the required fields from pre-existing data.

:::

</details>

<details>

<summary>5. Prepare the Transaction</summary>

The [POST `transactions/sell/prepare`](../../../api-reference/prepare-sell-transaction) endpoint provides the most accurate and up-to-date pricing estimate and pre-validates any form data supplied, working with an existing `PaymentMethodID` if available.

If called without an existing `PaymentMethodID`, all necessary fields will be requested; otherwise, only the missing fields will be requested.

On a successful call to the Prepare endpoint, you will receive a `FormSessionID` which can be used to submit the transaction.

1. Retrieve your `ChannelID`, `PaymentMethodID`, `CryptoCurrency`, `CustomerID`, `FiatAmount` and `Form` fields, from the previous steps.
2. Call the [POST `transactions/sell/prepare`](../../../api-reference/prepare-sell-transaction) endpoint.
3. If you are directly settling with a customer, debit the customer's account in your system - using the `FiatAmount` as a reference for absorbing or passing on the fees to your customer.

You can fetch a customer's existing payment methods using the [GET `payment-methods`](../../../api-reference/payment-methods) endpoint.

:::tip

#### **Prepare Endpoints and PaymentMethodIDs**

The prepare endpoint's primary purposes are:

- To get the most accurate and up-to-date pricing estimate.
- To pre-validate any form data you have supplied, which works in conjunction with an existing `PaymentMethodID`.

If you call the forms endpoint without an existing `PaymentMethodID`, it will request the entire set of fields that Noah needs to collect as new data for instructing a payment to that channel.

If you call the forms endpoint with an existing `PaymentMethodID`, it will request only the fields that Noah doesn't yet have data for.

For example, let's say a Customer has already made an offramp on ChannelA. If they later come back to make another one, and you supply a valid `PaymentMethodID` in the forms response, Noah will not request data that has already been provided.

For a Customer's first transaction, they won't have any saved Payment Methods, and therefore you won't have a `PaymentMethodID` - it's an optional field.

:::

</details>

<details>

<summary>6. Call the Workflow Endpoint</summary>

Call the [POST `workflows/onchain-deposit-to-payment-method`](../../../api-reference/onchain-deposit-to-fiat-payout) endpoint, passing the following data:

```json
curl -L 'https://api.sandbox.noah.com/v1/workflows/onchain-deposit-to-payment-method' \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-H 'X-Api-Key: <X-Api-Key>' \
-d '{
  "Trigger": {
    "Type": "SingleOnchainDepositSourceTriggerInput",
    "Conditions": [
      {
        "AmountConditions": [
          {
            "ComparisonOperator": "EQ",
            "Value": "10.1"
          }
        ],
        "Network": "Ethereum"
      }
    ],
    "SourceAddress": "0x370206496048f4eDbe60e3AcBD4CFEC50B2433bd",
    "Expiry": "2024-04-16T08:00:55Z",
    "Nonce": "dc879b38-494b-4de7-98a9-068703144328"
  },
  "CustomerID": "550e8400-e29b-41d4-a716-446655440000",
  "CryptoCurrency": "BTC",
  "FiatAmount": "10.1",
  "FormSessionID": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
}'
```

Include the data below:

1. `CustomerID`: pertaining to the customer initiating the Onramp.
2. Transaction Details: including the `CryptoCurrency` and withdrawal `Network`
3. Set the `Trigger` object to use the `SingleOnchainDepositSourceTriggerInput` type. This trigger type specifies conditions based on the customer's on-chain deposit.
   - Make sure to include:
     - **SourceAddress**: The customer's self-custody wallet address (where they send crypto from). Noah uses this address to identify which customer made a deposit — when crypto arrives from this address, Noah automatically executes the payout rules configured for the associated `CustomerID`.
     - **AmountConditions**: Specify conditions for the deposit amount using `ComparisonOperator`. This also serves as slippage protection: Noah executes the exchange at the realtime rate when the deposit is detected, but if the rate deviates outside your specified range, the payout will not execute. Available operators are `EQ` (equal), `LTEQ` (less than or equal), and `GTEQ` (greater than or equal). Best practice: Use `LTEQ` with a value slightly above your expected amount to protect against unfavorable rate movements.
     - **CryptoCurrency**: Set the cryptocurrency type (e.g., "USDC_TEST" for sandbox testing).
     - **Network**: Define the network, such as `EthereumTestSepolia` or `PolygonTestMumbai` for test environments.
4. Use the `FormSessionID` from the Prepare step above to reference the payout details.

**Note:** Generate a new `FormSessionID` each time you display the onchain deposit address to the customer. While the original `FormSessionID` may continue working, Noah cannot guarantee how long it remains valid. If a customer reuses an onchain address after its `FormSessionID` has expired, the transfer will fail. However, no funds will be lost.

You will receive an on-chain deposit address to provide to your customer for the deposit.

```json
{
  "Conditions": [
    {
      "AmountConditions": [
        {
          "ComparisonOperator": "EQ",
          "Value": "10.1"
        }
      ],
      "CryptoCurrency": "BTC",
      "Network": "Ethereum",
      "DestinationAddress": "0x71C7656EC7ab88b098defB751B7401B5f6d8976F"
    }
  ],
  "SourceAddress": "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4"
}
```

The response contains two addresses:

- **`SourceAddress`**: The customer's wallet address you provided in the request. Noah monitors for deposits originating from this address.
- **`DestinationAddress`**: The deposit address fixed to your Dashboard account. Give this address to your customer: this is where they should send their crypto. When you create this orchestration rule, you're attributing a Payout Action (defined by the `CustomerID`, payout channel, and `FormSessionID`) that executes automatically when Noah detects a deposit at this address from the registered `SourceAddress`.

</details>

<details>

<summary>7. Receive Webhook Events</summary>

#### Receive the Transaction Webhooks

- You will receive a `Transaction` event for the received crypto. See [Transaction Event](../../api-concepts/webhooks/transactions).
- You will receive a second `Transaction` event for the fiat payout.

</details>

For more details on this product, see the [Automated Payout](../../products/automated-payouts.md) Product page.
