Multi-leg payments

The Multi-leg payments feature enables you to process payment transactions by grouping multiple legs into a single request. This approach simplifies payment processing and removes the need to send multiple separate single-leg payment requests. With this feature, you can define each individual transaction—either credit or debit—within each leg of the multi-leg payment.

The Pismo platform ensures data integrity by enforcing strict validation, idempotency, and error-handling mechanisms throughout the payment lifecycle. Each transaction is uniquely identified by a multi-leg ID, which cannot be reused across different services. In the event of errors—such as a failed debit or credit leg—the platform employs a robust rollback process to reverse all successfully executed legs, maintaining transactional consistency and supporting accurate reconciliation.

💡

Notes

  • Each multi-leg payment request must contain a minimum of two legs (either credit or debit) and can include up to a maximum of 20 legs.
  • You cannot use Post multi-leg payment to transfer credit and debit leg payments to different accounts for identical amounts. For such situations, use Post payment instead.

Supported features

In addition to simplifying complex transactions, multi-leg payments offer several capabilities that enhance flexibility, control, and reconciliation.

  • Instant clearing: Use the instant_clearing field to request immediate clearing for specific debit or credit legs. Refer to Post multi-leg payment for details.
  • Earmark support: Debit legs can reference an earmark_id to use earmarked funds.
  • Granular validation rules: Per-leg validation options allow override or force behavior for account status and ledger checks, reducing the likelihood of rollbacks.
  • Metadata support: You can pass metadata and soft descriptor in each leg. Custom metadata provided in the request is returned in responses and included in events, aiding reconciliation and reporting.

Multi-leg and tracking ID

A multileg_id uniquely identifies the entire multi-leg payment request. A tracking ID on the other hand, is used to identify and track an individual payment leg within a multi-leg transaction, whether debit or credit. When using Post multi-leg payment, you must pass a tracking ID in each leg to ensure the transaction is unique.

💡

About tracking ID

Each tracking ID is single-use and cannot be reused across different services. For example, if a tracking ID has already been used for a Post payment, it cannot be used again for a leg in Post multi-leg payment.

Force payment restrictions

In scenarios where force_post is set to true, any reason-based force payment restrictions on the account result in a failed force payment operation. These restrictions apply to both credit and debit operations. Use the List status reasons endpoint to view the corresponding reason IDs. Forced transfers from an earmark balance are not supported.

If an account has a status that prevents force operations, the payment will fail. For more information, refer to Manage account statuses.

Error handling mechanism

During the processing of a multi-leg payment request, the Pismo platform enforces a robust error-handling mechanism known as rollback to safeguard data integrity. If any leg—credit or debit—fails, all previously successful legs are rolled back. If a rollback attempt itself fails, those legs are marked as ROLLBACK_FAILED. The rollback process is resource-intensive and generates compensating transactions to reverse any legs that were already executed.
To minimize the need for rollbacks, the Pismo platform performs thorough validations when each payment request is received and proactively returns errors for invalid inputs before processing begins.

Error response structure

When validation errors occur in a request, the API returns a structured error response with these components.

  • Standard error message: For example, error code WMLP0005 warns about invalid inputs without providing leg-specific details. This typically occurs when reusing a tracking_id.
  • Duplicate tracking ID: If you reuse a tracking ID from a previous payment, the system returns error code WPMT0007 and rejects the request.
  • List of leg-specific errors: For certain failures, the response includes detailed errors for individual legs within the error object of each leg. In some cases, errors apply to the entire request, such as when the maximum number of legs is exceeded, and only a standard code and message are returned.

Error identification

When a leg fails, you can retrieve the payment’s status using Get multi-leg payment status and check the event_datetime field in the affected leg to see when the error occurred. This information is available only if the multi-leg payment was being created and the failure occurred while generating platform authorization. Note that not all legs display the event_datetime field.

💡

Note

If either a credit or debit leg fails in a multi-leg payment, the Pismo platform automatically initiates a rollback of all previously executed legs, unless the rollback itself fails. In cases where a rollback cannot be completed, the affected legs are marked as ROLLBACK_FAILED.

This rollback mechanism helps maintain transactional consistency. You can monitor the status of a multi-leg payment by using the Get multi-leg payment status endpoint with a multileg_id. The event_datetime field might be displayed for a failed leg only when the multi-leg payment is created and the failure occurs while generating platform authorization. If a rollback fails under these conditions, the same endpoint reflects that status, enabling you to verify the outcome.

How to use Post multi-leg payment

Consider a scenario in which your account has a balance of $1000, and you wish to initiate a payment request with three legs: two debit legs (one for $100, another for $200) and one credit leg for $600. Here are the API request details.

Sample request
curl --request POST \
     --url https://sandbox.pismolabs.io/corporate/v3/payments/multileg \
     --header 'accept: application/json' \
     --header 'content-type: application/json' \
     --data '
{
  "multileg_id": "0a50c88a-ade8-4bcc-a639-9a0b0f11e245",
  "debits": [
    {
      "tracking_id": "cf7f77ae-466c-428d-8e9f-7834341dd81e",
      "amount": 100.00,
      "currency": "USD",
      "external_account_id": "account-a",
      "processing_code": "219258",
      "soft_descriptor": "ACME Inc. - Invoice 2938",
      "force_post": false,
      "validation_rules": {
        "ACCOUNT_STATUS": {
          "override": false
        },
        "LEDGER": {
          "force": false
        }
      },
      "skip_account_date_validation": false,
      "earmark_id": "8a3be3c1-5a2c-4d98-9901-8-account-a",
      "instant_clearing": false
    },
    {
      "tracking_id": "4cd1caeb-d454-4c92-971f-aeb6c14a4445",
      "amount": 200.00,
      "currency": "USD",
      "external_account_id": "account-a",
      "processing_code": "220037",
      "soft_descriptor": "ACME Inc. - Invoice 2938",
      "instant_clearing": false
    }
  ],
  "credits": [
    {
      "tracking_id": "8c0d14ff-5c1d-4046-8e55-6833161f218e",
      "amount": 600.00,
      "currency": "USD",
      "external_account_id": "account-a",
      "processing_code": "220035",
      "soft_descriptor": "ACME Inc. - Invoice 2938",
      "instant_clearing": false,
      "force_post": true
    }
  ],
  "metadata": {
    "custom_info": "abc"
  }
}

Upon successful execution, the API returns the following response payload to confirm the successful processing of your request.

Sample response
202 Accepted
{
  "debits": [
    {
      "tracking_id": "cf7f77ae-466c-428d-8e9f-7834341dd81e",
      "external_account_id": "account-a",
      "processing_code": "219258",
      "soft_descriptor": "ACME Inc. - Invoice 2938",
      "amount": 100,
      "currency": "USD",
      "earmark_id": "8a3be3c1-5a2c-4d98-9901-account-a",
      "force_post": false,
      "validation_rules": {
        "ACCOUNT_STATUS": {
          "force": false,
          "override": false
        },
        "LEDGER": {
          "force": false,
          "override": false
        }
      },
      "instant_clearing": false,
      "skip_account_date_validation": false
    },
    {
      "tracking_id": "4cd1caeb-d454-4c92-971f-aeb6c14a4445",
      "external_account_id": "account-a",
      "processing_code": "220037",
      "soft_descriptor": "ACME Inc. - Invoice 2938",
      "amount": 200,
      "currency": "USD",
      "force_post": false,
      "instant_clearing": false,
      "skip_account_date_validation": false
    }
  ],
  "credits": [
    {
      "tracking_id": "8c0d14ff-5c1d-4046-8e55-6833161f218e",
      "external_account_id": "account-a",
      "processing_code": "220035",
      "soft_descriptor": "ACME Inc. - Invoice 2938",
      "amount": 600,
      "currency": "USD",
      "force_post": true,
      "instant_clearing": false,
      "skip_account_date_validation": false
    }
  ],
  "multileg_id": "0a50c88a-ade8-4bcc-a639-9a0b0f11e245",
  "metadata": {
    "custom_info": "abc"
  }
}

You can optionally use Get transaction banking account information to retrieve and display the complete result of this transaction. As shown in the response, the payment request comprising two debit legs totaling $300 and one credit leg of $600 results in an updated account balance of $1300.