> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.mercoa.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.mercoa.com/_mcp/server.

If you have built out your own payment rails, you can use Mercoa to orchestrate and trigger your custom payment rails.
This lets you take advantage of Mercoa's invoice management, payment tracking, and webhook functionality while using payment rails provided by you, your banking partner, or your payment processor.

At this time, custom payment rails cannot be used with Mercoa's built-in payment rails in the same invoice.

\


Entities that only use custom payment rails do not need to go through Mercoa's KYB/KYC checks. When creating entities
that will only use custom payment rails, you only need to provide the required parameters and can skip adding EINs,
representatives, etc.

## How it works

In order to create a custom payment rail, you must first define a schema for your payment rail.
This schema will be used to validate the payment rail data that you send to Mercoa.
You can then use this schema to create a custom `paymentMethod` in Mercoa.
Once the `paymentMethod` is created, you can use it to create an invoice.

Custom payment rails are designed to be used along with our [webhooks](/common-concepts/webhooks) functionality.
When an invoice created with a custom rail is set to the `pending` status, Mercoa will send a webhook notification to your system.
Your system can then use the webhook notification to trigger the payment using your custom payment rail.
Once the payment is complete, your system needs to update the invoice as `paid`.

## Creating a custom payment rail schema

[API Reference](/api-reference/custom-payment-method-schema/create)

A custom payment rail schema is a set of key/value pairs that define the data that is required to trigger a payment using your custom payment rail.

For example, if you are using a custom payment rail to trigger a wire transfer, you might require the following data:

* `bankName`
* `accountNumber`
* `routingNumber`
* `recipientName`

Because a wire transfer is used to send money from one bank account to another, you would define the schema as a `destination` payment method. Schemas can be defined as a `source` or `destination` or both.

You can define this schema using the [POST /paymentMethod/schema](/api-reference/custom-payment-method-schema/create) endpoint:

### Request

POST [https://api.mercoa.com/paymentMethod/schema](https://api.mercoa.com/paymentMethod/schema)

**`Wire`**

```curl Wire
curl -X POST https://api.mercoa.com/paymentMethod/schema \
     -H "Authorization: Bearer <token>" \
     -H "Content-Type: application/json" \
     -d '{
  "name": "Wire",
  "isSource": false,
  "isDestination": true,
  "fields": [
    {
      "name": "bankName",
      "type": "text",
      "optional": false,
      "displayName": "Bank Name"
    },
    {
      "name": "recipientName",
      "type": "text",
      "optional": false,
      "displayName": "Recipient Name"
    },
    {
      "name": "accountNumber",
      "type": "usBankAccountNumber",
      "optional": false,
      "displayName": "Account Number",
      "useAsAccountNumber": true
    },
    {
      "name": "routingNumber",
      "type": "usBankRoutingNumber",
      "optional": false,
      "displayName": "Routing Number"
    }
  ],
  "supportedCurrencies": [
    "USD",
    "EUR"
  ],
  "estimatedProcessingTime": 0,
  "maxAmount": 100000,
  "minAmount": 1
}'
```

**`Wire`**

```python Wire
import requests

url = "https://api.mercoa.com/paymentMethod/schema"

payload = {
    "name": "Wire",
    "isSource": False,
    "isDestination": True,
    "fields": [
        {
            "name": "bankName",
            "type": "text",
            "optional": False,
            "displayName": "Bank Name"
        },
        {
            "name": "recipientName",
            "type": "text",
            "optional": False,
            "displayName": "Recipient Name"
        },
        {
            "name": "accountNumber",
            "type": "usBankAccountNumber",
            "optional": False,
            "displayName": "Account Number",
            "useAsAccountNumber": True
        },
        {
            "name": "routingNumber",
            "type": "usBankRoutingNumber",
            "optional": False,
            "displayName": "Routing Number"
        }
    ],
    "supportedCurrencies": ["USD", "EUR"],
    "estimatedProcessingTime": 0,
    "maxAmount": 100000,
    "minAmount": 1
}
headers = {
    "Authorization": "Bearer <token>",
    "Content-Type": "application/json"
}

response = requests.post(url, json=payload, headers=headers)

print(response.json())
```

**`Wire`**

```typescript Wire
import { MercoaClient } from "@mercoa/javascript";

const client = new MercoaClient({ token: "YOUR_TOKEN" });
await client.customPaymentMethodSchema.create({
    name: "Wire",
    isSource: false,
    isDestination: true,
    supportedCurrencies: ["USD", "EUR"],
    fields: [{
            name: "bankName",
            displayName: "Bank Name",
            type: "text",
            optional: false
        }, {
            name: "recipientName",
            displayName: "Recipient Name",
            type: "text",
            optional: false
        }, {
            name: "accountNumber",
            displayName: "Account Number",
            type: "usBankAccountNumber",
            optional: false,
            useAsAccountNumber: true
        }, {
            name: "routingNumber",
            displayName: "Routing Number",
            type: "usBankRoutingNumber",
            optional: false
        }],
    estimatedProcessingTime: 0,
    maxAmount: 100000,
    minAmount: 1
});

```

**`Wire`**

```go Wire
package main

import (
	"fmt"
	"strings"
	"net/http"
	"io"
)

func main() {

	url := "https://api.mercoa.com/paymentMethod/schema"

	payload := strings.NewReader("{\n  \"name\": \"Wire\",\n  \"isSource\": false,\n  \"isDestination\": true,\n  \"fields\": [\n    {\n      \"name\": \"bankName\",\n      \"type\": \"text\",\n      \"optional\": false,\n      \"displayName\": \"Bank Name\"\n    },\n    {\n      \"name\": \"recipientName\",\n      \"type\": \"text\",\n      \"optional\": false,\n      \"displayName\": \"Recipient Name\"\n    },\n    {\n      \"name\": \"accountNumber\",\n      \"type\": \"usBankAccountNumber\",\n      \"optional\": false,\n      \"displayName\": \"Account Number\",\n      \"useAsAccountNumber\": true\n    },\n    {\n      \"name\": \"routingNumber\",\n      \"type\": \"usBankRoutingNumber\",\n      \"optional\": false,\n      \"displayName\": \"Routing Number\"\n    }\n  ],\n  \"supportedCurrencies\": [\n    \"USD\",\n    \"EUR\"\n  ],\n  \"estimatedProcessingTime\": 0,\n  \"maxAmount\": 100000,\n  \"minAmount\": 1\n}")

	req, _ := http.NewRequest("POST", url, payload)

	req.Header.Add("Authorization", "Bearer <token>")
	req.Header.Add("Content-Type", "application/json")

	res, _ := http.DefaultClient.Do(req)

	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)

	fmt.Println(res)
	fmt.Println(string(body))

}
```

**`Wire`**

```ruby Wire
require 'uri'
require 'net/http'

url = URI("https://api.mercoa.com/paymentMethod/schema")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true

request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n  \"name\": \"Wire\",\n  \"isSource\": false,\n  \"isDestination\": true,\n  \"fields\": [\n    {\n      \"name\": \"bankName\",\n      \"type\": \"text\",\n      \"optional\": false,\n      \"displayName\": \"Bank Name\"\n    },\n    {\n      \"name\": \"recipientName\",\n      \"type\": \"text\",\n      \"optional\": false,\n      \"displayName\": \"Recipient Name\"\n    },\n    {\n      \"name\": \"accountNumber\",\n      \"type\": \"usBankAccountNumber\",\n      \"optional\": false,\n      \"displayName\": \"Account Number\",\n      \"useAsAccountNumber\": true\n    },\n    {\n      \"name\": \"routingNumber\",\n      \"type\": \"usBankRoutingNumber\",\n      \"optional\": false,\n      \"displayName\": \"Routing Number\"\n    }\n  ],\n  \"supportedCurrencies\": [\n    \"USD\",\n    \"EUR\"\n  ],\n  \"estimatedProcessingTime\": 0,\n  \"maxAmount\": 100000,\n  \"minAmount\": 1\n}"

response = http.request(request)
puts response.read_body
```

**`Wire`**

```java Wire
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;

HttpResponse<String> response = Unirest.post("https://api.mercoa.com/paymentMethod/schema")
  .header("Authorization", "Bearer <token>")
  .header("Content-Type", "application/json")
  .body("{\n  \"name\": \"Wire\",\n  \"isSource\": false,\n  \"isDestination\": true,\n  \"fields\": [\n    {\n      \"name\": \"bankName\",\n      \"type\": \"text\",\n      \"optional\": false,\n      \"displayName\": \"Bank Name\"\n    },\n    {\n      \"name\": \"recipientName\",\n      \"type\": \"text\",\n      \"optional\": false,\n      \"displayName\": \"Recipient Name\"\n    },\n    {\n      \"name\": \"accountNumber\",\n      \"type\": \"usBankAccountNumber\",\n      \"optional\": false,\n      \"displayName\": \"Account Number\",\n      \"useAsAccountNumber\": true\n    },\n    {\n      \"name\": \"routingNumber\",\n      \"type\": \"usBankRoutingNumber\",\n      \"optional\": false,\n      \"displayName\": \"Routing Number\"\n    }\n  ],\n  \"supportedCurrencies\": [\n    \"USD\",\n    \"EUR\"\n  ],\n  \"estimatedProcessingTime\": 0,\n  \"maxAmount\": 100000,\n  \"minAmount\": 1\n}")
  .asString();
```

**`Wire`**

```php Wire
<?php
require_once('vendor/autoload.php');

$client = new \GuzzleHttp\Client();

$response = $client->request('POST', 'https://api.mercoa.com/paymentMethod/schema', [
  'body' => '{
  "name": "Wire",
  "isSource": false,
  "isDestination": true,
  "fields": [
    {
      "name": "bankName",
      "type": "text",
      "optional": false,
      "displayName": "Bank Name"
    },
    {
      "name": "recipientName",
      "type": "text",
      "optional": false,
      "displayName": "Recipient Name"
    },
    {
      "name": "accountNumber",
      "type": "usBankAccountNumber",
      "optional": false,
      "displayName": "Account Number",
      "useAsAccountNumber": true
    },
    {
      "name": "routingNumber",
      "type": "usBankRoutingNumber",
      "optional": false,
      "displayName": "Routing Number"
    }
  ],
  "supportedCurrencies": [
    "USD",
    "EUR"
  ],
  "estimatedProcessingTime": 0,
  "maxAmount": 100000,
  "minAmount": 1
}',
  'headers' => [
    'Authorization' => 'Bearer <token>',
    'Content-Type' => 'application/json',
  ],
]);

echo $response->getBody();
```

**`Wire`**

```csharp Wire
using RestSharp;

var client = new RestClient("https://api.mercoa.com/paymentMethod/schema");
var request = new RestRequest(Method.POST);
request.AddHeader("Authorization", "Bearer <token>");
request.AddHeader("Content-Type", "application/json");
request.AddParameter("application/json", "{\n  \"name\": \"Wire\",\n  \"isSource\": false,\n  \"isDestination\": true,\n  \"fields\": [\n    {\n      \"name\": \"bankName\",\n      \"type\": \"text\",\n      \"optional\": false,\n      \"displayName\": \"Bank Name\"\n    },\n    {\n      \"name\": \"recipientName\",\n      \"type\": \"text\",\n      \"optional\": false,\n      \"displayName\": \"Recipient Name\"\n    },\n    {\n      \"name\": \"accountNumber\",\n      \"type\": \"usBankAccountNumber\",\n      \"optional\": false,\n      \"displayName\": \"Account Number\",\n      \"useAsAccountNumber\": true\n    },\n    {\n      \"name\": \"routingNumber\",\n      \"type\": \"usBankRoutingNumber\",\n      \"optional\": false,\n      \"displayName\": \"Routing Number\"\n    }\n  ],\n  \"supportedCurrencies\": [\n    \"USD\",\n    \"EUR\"\n  ],\n  \"estimatedProcessingTime\": 0,\n  \"maxAmount\": 100000,\n  \"minAmount\": 1\n}", ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
```

**`Wire`**

```swift Wire
import Foundation

let headers = [
  "Authorization": "Bearer <token>",
  "Content-Type": "application/json"
]
let parameters = [
  "name": "Wire",
  "isSource": false,
  "isDestination": true,
  "fields": [
    [
      "name": "bankName",
      "type": "text",
      "optional": false,
      "displayName": "Bank Name"
    ],
    [
      "name": "recipientName",
      "type": "text",
      "optional": false,
      "displayName": "Recipient Name"
    ],
    [
      "name": "accountNumber",
      "type": "usBankAccountNumber",
      "optional": false,
      "displayName": "Account Number",
      "useAsAccountNumber": true
    ],
    [
      "name": "routingNumber",
      "type": "usBankRoutingNumber",
      "optional": false,
      "displayName": "Routing Number"
    ]
  ],
  "supportedCurrencies": ["USD", "EUR"],
  "estimatedProcessingTime": 0,
  "maxAmount": 100000,
  "minAmount": 1
] as [String : Any]

let postData = JSONSerialization.data(withJSONObject: parameters, options: [])

let request = NSMutableURLRequest(url: NSURL(string: "https://api.mercoa.com/paymentMethod/schema")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "POST"
request.allHTTPHeaderFields = headers
request.httpBody = postData as Data

let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
  if (error != nil) {
    print(error as Any)
  } else {
    let httpResponse = response as? HTTPURLResponse
    print(httpResponse)
  }
})

dataTask.resume()
```

Mercoa uses this schema information to automatically create UI elements for the `paymentMethod` that you create using this schema.

## Creating a paymentMethod with a custom payment rail

[API Reference](/api-reference/entity/payment-method/create)

Once you have defined a custom payment rail schema, you can create a `paymentMethod` that uses that schema.
The custom `paymentMethod` will contain the data that is required to trigger a payment using your custom payment rail, and a `foreignId` that can be used to identify the payment method in your system.

The foreignId is a unique identifier for the payment method in your system. It is used to identify the payment method
in your system when you receive a webhook notification.

\


This `paymentMethod` can then be used as the `paymentMethodSource` or `paymentMethodDestination` when creating an invoice.
Mercoa will validate the data that you send against the schema that you defined.

For example, once you have defined a custom payment rail schema for a wire transfer, you can create a `paymentMethod` that uses that schema using the [POST /paymentMethod](/api-reference/entity/payment-method/create) endpoint:

### Request

POST [https://api.mercoa.com/entity/\{entityId}/paymentMethod](https://api.mercoa.com/entity/\{entityId}/paymentMethod)

**`CustomWire`**

```curl CustomWire
curl -X POST https://api.mercoa.com/entity/ent_21661ac1-a2a8-4465-a6c0-64474ba8181d/paymentMethod \
     -H "Authorization: Bearer <token>" \
     -H "Content-Type: application/json" \
     -d '{
  "type": "custom",
  "data": {
    "bankName": "Chase",
    "recipientName": "John Doe",
    "routingNumber": "123456789",
    "accountNumber": "99988767623"
  },
  "schemaId": "cpms_4794d597-70dc-4fec-b6ec-c5988e759769",
  "accountName": "Vendor Wire Account",
  "accountNumber": "123456789",
  "foreignId": "DB_FOREIGN_ID"
}'
```

**`CustomWire`**

```python CustomWire
import requests

url = "https://api.mercoa.com/entity/ent_21661ac1-a2a8-4465-a6c0-64474ba8181d/paymentMethod"

payload = {
    "type": "custom",
    "data": {
        "bankName": "Chase",
        "recipientName": "John Doe",
        "routingNumber": "123456789",
        "accountNumber": "99988767623"
    },
    "schemaId": "cpms_4794d597-70dc-4fec-b6ec-c5988e759769",
    "accountName": "Vendor Wire Account",
    "accountNumber": "123456789",
    "foreignId": "DB_FOREIGN_ID"
}
headers = {
    "Authorization": "Bearer <token>",
    "Content-Type": "application/json"
}

response = requests.post(url, json=payload, headers=headers)

print(response.json())
```

**`CustomWire`**

```typescript CustomWire
import { MercoaClient } from "@mercoa/javascript";

const client = new MercoaClient({ token: "YOUR_TOKEN" });
await client.entity.paymentMethod.create("ent_21661ac1-a2a8-4465-a6c0-64474ba8181d", {
    type: "custom",
    foreignId: "DB_FOREIGN_ID",
    accountName: "Vendor Wire Account",
    accountNumber: "123456789",
    schemaId: "cpms_4794d597-70dc-4fec-b6ec-c5988e759769",
    data: {
        "bankName": "Chase",
        "recipientName": "John Doe",
        "routingNumber": "123456789",
        "accountNumber": "99988767623"
    }
});

```

**`CustomWire`**

```go CustomWire
package main

import (
	"fmt"
	"strings"
	"net/http"
	"io"
)

func main() {

	url := "https://api.mercoa.com/entity/ent_21661ac1-a2a8-4465-a6c0-64474ba8181d/paymentMethod"

	payload := strings.NewReader("{\n  \"type\": \"custom\",\n  \"data\": {\n    \"bankName\": \"Chase\",\n    \"recipientName\": \"John Doe\",\n    \"routingNumber\": \"123456789\",\n    \"accountNumber\": \"99988767623\"\n  },\n  \"schemaId\": \"cpms_4794d597-70dc-4fec-b6ec-c5988e759769\",\n  \"accountName\": \"Vendor Wire Account\",\n  \"accountNumber\": \"123456789\",\n  \"foreignId\": \"DB_FOREIGN_ID\"\n}")

	req, _ := http.NewRequest("POST", url, payload)

	req.Header.Add("Authorization", "Bearer <token>")
	req.Header.Add("Content-Type", "application/json")

	res, _ := http.DefaultClient.Do(req)

	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)

	fmt.Println(res)
	fmt.Println(string(body))

}
```

**`CustomWire`**

```ruby CustomWire
require 'uri'
require 'net/http'

url = URI("https://api.mercoa.com/entity/ent_21661ac1-a2a8-4465-a6c0-64474ba8181d/paymentMethod")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true

request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n  \"type\": \"custom\",\n  \"data\": {\n    \"bankName\": \"Chase\",\n    \"recipientName\": \"John Doe\",\n    \"routingNumber\": \"123456789\",\n    \"accountNumber\": \"99988767623\"\n  },\n  \"schemaId\": \"cpms_4794d597-70dc-4fec-b6ec-c5988e759769\",\n  \"accountName\": \"Vendor Wire Account\",\n  \"accountNumber\": \"123456789\",\n  \"foreignId\": \"DB_FOREIGN_ID\"\n}"

response = http.request(request)
puts response.read_body
```

**`CustomWire`**

```java CustomWire
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;

HttpResponse<String> response = Unirest.post("https://api.mercoa.com/entity/ent_21661ac1-a2a8-4465-a6c0-64474ba8181d/paymentMethod")
  .header("Authorization", "Bearer <token>")
  .header("Content-Type", "application/json")
  .body("{\n  \"type\": \"custom\",\n  \"data\": {\n    \"bankName\": \"Chase\",\n    \"recipientName\": \"John Doe\",\n    \"routingNumber\": \"123456789\",\n    \"accountNumber\": \"99988767623\"\n  },\n  \"schemaId\": \"cpms_4794d597-70dc-4fec-b6ec-c5988e759769\",\n  \"accountName\": \"Vendor Wire Account\",\n  \"accountNumber\": \"123456789\",\n  \"foreignId\": \"DB_FOREIGN_ID\"\n}")
  .asString();
```

**`CustomWire`**

```php CustomWire
<?php
require_once('vendor/autoload.php');

$client = new \GuzzleHttp\Client();

$response = $client->request('POST', 'https://api.mercoa.com/entity/ent_21661ac1-a2a8-4465-a6c0-64474ba8181d/paymentMethod', [
  'body' => '{
  "type": "custom",
  "data": {
    "bankName": "Chase",
    "recipientName": "John Doe",
    "routingNumber": "123456789",
    "accountNumber": "99988767623"
  },
  "schemaId": "cpms_4794d597-70dc-4fec-b6ec-c5988e759769",
  "accountName": "Vendor Wire Account",
  "accountNumber": "123456789",
  "foreignId": "DB_FOREIGN_ID"
}',
  'headers' => [
    'Authorization' => 'Bearer <token>',
    'Content-Type' => 'application/json',
  ],
]);

echo $response->getBody();
```

**`CustomWire`**

```csharp CustomWire
using RestSharp;

var client = new RestClient("https://api.mercoa.com/entity/ent_21661ac1-a2a8-4465-a6c0-64474ba8181d/paymentMethod");
var request = new RestRequest(Method.POST);
request.AddHeader("Authorization", "Bearer <token>");
request.AddHeader("Content-Type", "application/json");
request.AddParameter("application/json", "{\n  \"type\": \"custom\",\n  \"data\": {\n    \"bankName\": \"Chase\",\n    \"recipientName\": \"John Doe\",\n    \"routingNumber\": \"123456789\",\n    \"accountNumber\": \"99988767623\"\n  },\n  \"schemaId\": \"cpms_4794d597-70dc-4fec-b6ec-c5988e759769\",\n  \"accountName\": \"Vendor Wire Account\",\n  \"accountNumber\": \"123456789\",\n  \"foreignId\": \"DB_FOREIGN_ID\"\n}", ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
```

**`CustomWire`**

```swift CustomWire
import Foundation

let headers = [
  "Authorization": "Bearer <token>",
  "Content-Type": "application/json"
]
let parameters = [
  "type": "custom",
  "data": [
    "bankName": "Chase",
    "recipientName": "John Doe",
    "routingNumber": "123456789",
    "accountNumber": "99988767623"
  ],
  "schemaId": "cpms_4794d597-70dc-4fec-b6ec-c5988e759769",
  "accountName": "Vendor Wire Account",
  "accountNumber": "123456789",
  "foreignId": "DB_FOREIGN_ID"
] as [String : Any]

let postData = JSONSerialization.data(withJSONObject: parameters, options: [])

let request = NSMutableURLRequest(url: NSURL(string: "https://api.mercoa.com/entity/ent_21661ac1-a2a8-4465-a6c0-64474ba8181d/paymentMethod")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "POST"
request.allHTTPHeaderFields = headers
request.httpBody = postData as Data

let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
  if (error != nil) {
    print(error as Any)
  } else {
    let httpResponse = response as? HTTPURLResponse
    print(httpResponse)
  }
})

dataTask.resume()
```

In this example, the `foreignId` is `DB_FOREIGN_ID`, and the `schemaId` is `cpms_4794d597-70dc-4fec-b6ec-c5988e759769`.

## Next steps

You can now use this `paymentMethod` just like any other payment method on an invoice! When the invoice is set to the `pending` status, Mercoa will send a webhook notification to your system.

In order to have Mercoa generate the UI elements for both the payer and the vendor, the custom schema must be activated on the [Payments Methods Dashboard](https://mercoa.com/dashboard/paymentmethods).