Skip to main content

Payment Splits

Payment splits let you route portions of a payment to multiple recipients in a single transaction. Recipients can be either crypto wallets (for on-chain distribution) or bank accounts (for direct bank deposits). Define each recipient with either a percentage or a fixed amount, and ZendFi handles the atomic distribution after the payment confirms. Splits are configured when creating a payment link using the split_recipients field. All payment link features — onramp, customer collection, usage limits, expiration — are automatically inherited by split payments.
Splits are applied at the payment level. When a payment is created from a link with splits, all split recipients are atomically added to the payment and processed sequentially after confirmation.

When creating a payment link, include the split_recipients array with your recipient definitions:
curl -X POST https://api.zendfi.tech/api/v1/payment-links \
  -H "Authorization: Bearer zfi_test_your_key" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": 100.00,
    "description": "Marketplace order",
    "split_recipients": [
      {
        "recipient_type": "wallet",
        "recipient_wallet": "7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU",
        "recipient_name": "Seller A",
        "percentage": 85.0,
        "split_order": 1
      },
      {
        "recipient_type": "wallet",
        "recipient_wallet": "8yLYug3DX98e08UYKEqCe6kClieuqB84UB0TuKthBtV",
        "recipient_name": "Platform Fee",
        "percentage": 15.0,
        "split_order": 2
      }
    ]
  }'

Split Recipient Fields

recipient_type
enum
required
Type of recipient: wallet or bank_account.

For Wallet Recipients (recipient_type: "wallet")

recipient_wallet
string
Solana wallet address to receive on-chain funds. Required when sub_account_id is not provided.
sub_account_id
string
Sub-account external ID or UUID. When set, ZendFi resolves and uses the sub-account wallet internally.
recipient_sub_account
string
Alias for sub_account_id.
recipient_name
string
Human-readable label for the recipient (e.g., “Seller A”).
percentage
number
Percentage of the payment amount (e.g., 85.0 for 85%). Use either percentage or fixed_amount_usd, not both.
fixed_amount_usd
number
Fixed dollar amount to route. Use either percentage or fixed_amount_usd, not both.
If the split has a single recipient and both percentage and fixed_amount_usd are omitted, ZendFi defaults that recipient to 100%.
split_order
integer
default:"0"
Processing order for this split (lower numbers process first).

For Bank Account Recipients (recipient_type: "bank_account")

recipient_account_name
string
required
Account holder name as registered with their bank.
recipient_bank_account
string
required
Bank account number.
recipient_bank_id
string
required
Bank identifier (PAJ bank id, bank code, or bank name). Example values: GTB, Access Bank, 9PSB7A2A2LJZ3H6Q4G8XJ6A4.
recipient_bank / bank_identifier / bank_code
string
Optional aliases accepted by the API and SDK; normalized internally to recipient_bank_id.
recipient_email
string
Email address for the recipient (optional, used for notifications).
percentage
number
Percentage of the payment amount. Use either percentage or fixed_amount_usd, not both.
fixed_amount_usd
number
Fixed dollar amount to route. Use either percentage or fixed_amount_usd, not both.
split_order
integer
default:"0"
Processing order for this split.

Validation & Processing

When you create a payment link with splits:
  1. Link Creation: Split recipients are validated for:
    • Correct structure and required fields
    • No duplicate wallet addresses (for wallet type)
    • No duplicate bank accounts (for bank type)
    • Valid Solana addresses (for wallet type)
  2. Payment Initiation: When a customer creates a payment from a link with bank account splits:
    • Each bank account is validated with PAJ to confirm it exists
    • If validation fails, payment creation returns a 400 Bad Request error
    • Wallet-type recipients do not require pre-validation
  3. Payment Confirmation: After the customer’s payment is confirmed on-chain:
    • Wallet recipients: Funds transferred directly from merchant escrow to wallet
    • Bank recipients: PAJ offramp order created with USDC sent to PAJ deposit address; PAJ converts and deposits to bank account

GET /api/v1/payment-links/{link_id}
Payment link ID.

Response

The response includes the full split_recipients array:
{
  "id": "link_abc123",
  "amount": 100.00,
  "currency": "USD",
  "token": "USDC",
  "description": "Marketplace order",
  "status": "active",
  "split_recipients": [
    {
      "recipient_type": "wallet",
      "recipient_wallet": "7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU",
      "recipient_name": "Seller A",
      "percentage": 85.0,
      "fixed_amount_usd": null,
      "split_order": 1
    },
    {
      "recipient_type": "wallet",
      "recipient_wallet": "8yLYug3DX98e08UYKEqCe6kClieuqB84UB0TuKthBtV",
      "recipient_name": "Platform Fee",
      "percentage": 15.0,
      "fixed_amount_usd": null,
      "split_order": 2
    }
  ],
  "created_at": "2026-03-01T10:00:00Z",
  "updated_at": "2026-03-01T10:00:00Z"
}

Get Payment Splits

GET /api/v1/payments/{payment_id}/splits
Returns all splits associated with a payment, including settlement status.
payment_id
string
required
Payment ID.

Response

[
  {
    "id": "split_abc123",
    "payment_id": "pay_test_xyz789",
    "recipient_wallet": "7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU",
    "recipient_name": "Seller A",
    "percentage": 85.0,
    "fixed_amount_usd": null,
    "split_order": 1,
    "status": "completed",
    "transaction_signature": "5UfDu...kXy",
    "settled_amount_usd": 85.00,
    "settled_amount_crypto": 85.00,
    "settled_currency": "USDC",
    "settled_at": "2026-03-01T12:05:00Z",
    "failure_reason": null,
    "retry_count": 0,
    "created_at": "2026-03-01T12:00:00Z",
    "updated_at": "2026-03-01T12:05:00Z"
  },
  {
    "id": "split_def456",
    "payment_id": "pay_test_xyz789",
    "recipient_wallet": "8yLYug3DX98e08UYKEqCe6kClieuqB84UB0TuKthBtV",
    "recipient_name": "Platform Fee",
    "percentage": 15.0,
    "fixed_amount_usd": null,
    "split_order": 2,
    "status": "completed",
    "transaction_signature": "3RmEp...qWz",
    "settled_amount_usd": 15.00,
    "settled_amount_crypto": 15.00,
    "settled_currency": "USDC",
    "settled_at": "2026-03-01T12:05:02Z",
    "failure_reason": null,
    "retry_count": 0,
    "created_at": "2026-03-01T12:00:00Z",
    "updated_at": "2026-03-01T12:05:02Z"
  }
]

Get a Single Split

GET /api/v1/splits/{split_id}
split_id
string
required
Split ID.

Split Status Lifecycle

StatusDescription
pendingSplit created with payment link, awaiting payment confirmation
processingPayment confirmed, settlement in progress
completedFunds successfully settled (on-chain or bank deposit)
failedSettlement failed, may be retried
refundedSplit was refunded

Recipient Type Behavior

Wallet Recipients

  • Settlement: Direct on-chain transfer from merchant escrow to recipient wallet
  • Speed: Immediate (settlement upon payment confirmation)
  • Token: Inherits payment link token (USDC, USDT, or SOL)
  • Validation: Solana address format verified at link creation
  • Confirmation: On-chain transaction signature returned

Bank Account Recipients

  • Settlement: PAJ offramp (USDC → NGN → bank deposit)
  • Speed: Asynchronous (PAJ converts and deposits within minutes to hours)
  • Token: USDC only (converted to fiat by PAJ)
  • Validation: Bank account verified with PAJ at payment creation
  • Confirmation: PAJ order ID and on-chain deposit signature returned

Validation Rules

Split Configuration Validation

The following rules are enforced at payment link creation:
  • At least one split recipient must be defined
  • No duplicate wallet addresses (for wallet type)
  • No duplicate bank accounts (for bank type)
  • Each recipient must have exactly one of: percentage or fixed_amount_usd
  • Wallet-type recipients must have recipient_wallet or sub_account_id
  • Bank-type recipients must have recipient_account_name, recipient_bank_account, and a bank identifier (recipient_bank_id or alias fields)

Amount Validation

  • Percentage-based: Sum of all percentages across wallet+bank recipients must equal 100%
  • Fixed-amount-based: Total of all fixed amounts must not exceed the payment amount
  • Mixed: When mixing percentages and fixed amounts, percentages are calculated first, then fixed amounts are deducted from the remainder

Bank Account Validation (Payment Time)

When a customer initiates payment with bank account splits:
  • Each bank account is validated with PAJ
  • If validation fails, payment creation returns 400 Bad Request with error details
  • Validation confirms:
    • Account exists
    • Account is active
    • Account holder name matches (or is similar to) the provided name

Use Cases

Marketplace Payments

Route seller commission, platform fee, and service tax in a single checkout. Supports both crypto wallets and bank payouts.

Revenue Sharing

Automatically distribute royalties, affiliate commissions, or revenue splits across multiple partners with a single unified payment.

Multi-Vendor Orders

Accept one payment from a customer and atomically split across multiple supplier bank accounts and crypto wallets.

Tiered Payouts

Create flexible payout tiers: platform retains percentage, send fixed amount to supplier, route remainder to sub-contractor.

NGN Bank Integration

Route USDC payments directly to Nigerian bank accounts via PAJ, eliminating manual reconciliation for bank-account recipients.

Onramp + Splits

Combine onramp (fiat-to-crypto) with splits to accept bank transfers from anywhere and automatically distribute globally.

Error Handling

Split validation errors occur at two stages:
{
  "error": "invalid_split_configuration",
  "message": "Split percentages exceed 100%: 105.00%",
  "status": 400
}
Common link creation errors:
  • Duplicate wallet addresses
  • Invalid Solana address format
  • Missing required bank fields
  • Percentage sum exceeds 100%

Payment Creation Errors

{
  "error": "bank_account_validation_failed",
  "message": "Bank account 1234567890 not found for bank gt_bank",
  "status": 400
}
Common payment creation errors:
  • Bank account does not exist
  • Account name mismatch
  • Invalid bank ID

Retry Logic

Splits use automatic retry for transient failures:
  • Wallet transfers: Up to 3 retry attempts on blockchain failures
  • Bank transfers: Up to 3 retry attempts on PAJ integration failures
  • Retry interval: Exponential backoff starting at 5 minutes
  • Max retry time: Splits are marked failed after 3 attempts
When a split fails permanently, the merchant receives an email notification with failure details.