> For clean Markdown of any page, append .md to the page URL. > For a complete documentation index, see https://docs.mercoa.com/embedded-ap-ar/home/payment-methods/bring-your-own-payments/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 " \ -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 ", "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 ") 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 ' 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 response = Unirest.post("https://api.mercoa.com/paymentMethod/schema") .header("Authorization", "Bearer ") .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 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 ', '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 "); 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 ", "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 " \ -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 ", "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 ") 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 ' 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 response = Unirest.post("https://api.mercoa.com/entity/ent_21661ac1-a2a8-4465-a6c0-64474ba8181d/paymentMethod") .header("Authorization", "Bearer ") .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 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 ', '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 "); 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 ", "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).