Overview

Creating a payment sends money from one of your OpenFX accounts to a counterparty. Before you create a payment, you need three things already set up:
  1. A source account (acc_) with sufficient balance in the sending currency.
  2. A counterparty (cpt_) representing the recipient.
  3. A payment method (pm_) on that counterparty with rail-specific delivery details (bank account, IBAN, blockchain address, etc.).
Prerequisites checklist:
  • Customer is active (KYB approved)
  • Source account is active with sufficient funds
  • Counterparty exists with at least one active payment method
  • You have generated an Idempotency-Key for this request

Step-by-Step Payment Creation

1. Choose your rail

Decide which payment rail to use based on the destination currency, country, and speed requirements. Use the Rails Discovery API to programmatically determine available rails for a given corridor.

2. Set the amount

Specify either a sendAmount (how much to debit from your account) or a receiveAmount (how much the counterparty should receive). Set amountInputSide accordingly.
amountInputSideYou specifyThe API calculates
sendsendAmountreceiveAmount (after fees and FX)
receivereceiveAmountsendAmount (including fees and FX)
All monetary amounts are strings, never floating-point numbers. Use exact decimal representations: "1500.00", not 1500 or 1500.0.

3. Choose execution preference

For cross-currency payments, decide how the FX rate should be determined:
PreferenceBehaviorWhen to use
at_marketExecute at the current spot rate at the time of processingSimple payments where exact rate is not critical
use_quoteExecute at a previously locked rate (provide quoteId)When you need a guaranteed rate, e.g., after showing pricing to your customer
For same-currency payments, executionPreference is not required.

4. Send the request

Include the Idempotency-Key header (UUID v4 recommended) on every payment creation request. This protects you from duplicate payments if a request is retried due to network issues.

Creating a Payment via the Unified Endpoint

The unified POST /payments endpoint accepts any rail in the rail field.
curl -X POST https://sandbox.api.openfx.com/v1/payments \
  -H "Authorization: Bearer $API_KEY" \
  -H "X-Signature: $SIGNATURE" \
  -H "X-Timestamp: $TIMESTAMP" \
  -H "Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000" \
  -H "Content-Type: application/json" \
  -d '{
    "sourceAccountId": "acc_01953e1a5f4b7002",
    "counterpartyId": "cpt_01953e1a5f4b7004",
    "paymentMethodId": "pm_01953e1a5f4b7300",
    "rail": "ach",
    "sendAmount": {
      "currency": "USD", "value": "150000"
    },
    "amountInputSide": "send",
    "senderReference": "INV-2026-0042"
  }'
The response is 201 Created with the full Payment object:
{
  "id": "pmt_01953e1a5f4b7005",
  "sourceAccountId": "acc_01953e1a5f4b7002",
  "counterpartyId": "cpt_01953e1a5f4b7004",
  "paymentMethodId": "pm_01953e1a5f4b7300",
  "rail": "ach",
  "status": "created",
  "sendAmount": {
    "currency": "USD", "value": "150000"
  },
  "receiveAmount": {
    "currency": "USD", "value": "150000"
  },
  "amountInputSide": "send",
  "fees": {
    "feeAmount": "0.50",
    "feeCurrency": "USD",
    "feeComponents": [
      { "type": "processing_fee", "value": "50", "currency": "USD" }
    ]
  },
  "totalDebitAmount": {
    "currency": "USD", "value": "150050"
  },
  "senderReference": "INV-2026-0042",
  "createdAt": "2026-02-25T14:30:00Z",
  "updatedAt": "2026-02-25T14:30:00Z"
}

Creating a SWIFT Payment with Purpose Code

International SWIFT payments require a purpose field. This is a standardized payment purpose code used for regulatory reporting.
curl -X POST https://sandbox.api.openfx.com/v1/payments \
  -H "Authorization: Bearer $API_KEY" \
  -H "X-Signature: $SIGNATURE" \
  -H "X-Timestamp: $TIMESTAMP" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "sourceAccountId": "acc_01953e1a5f4b7002",
    "counterpartyId": "cpt_01953e1a5f4b7008",
    "paymentMethodId": "pm_01953e1a5f4b7400",
    "rail": "swift",
    "sendAmount": {
      "currency": "USD", "value": "2500000"
    },
    "amountInputSide": "send",
    "executionPreference": "at_market",
    "purpose": "invoice_payment",
    "senderReference": "PO-2026-1138"
  }'

Unified vs. Rail-Specific Endpoints

AspectUnified (POST /payments)Rail-Specific (POST /payments/{rail})
Rail selectionrail field in request bodyImplicit from URL path
Request schemaSuperset schema with all rail fieldsTight schema with only that rail’s fields
Response schemaFull Payment objectRail-native Payment object with rail-specific fields
Use caseMulti-rail integrations, dynamic rail selectionSingle-rail integrations, rail-native workflows
Payment resourceSame pmt_ resourceSame pmt_ resource
Status lifecycleIdenticalIdentical
Both endpoints create the same underlying Payment resource. You can create via POST /payments and retrieve via GET /payments/{rail}/{paymentId}, or vice versa.

Required Headers

Every payment creation request requires these headers:
HeaderValuePurpose
AuthorizationBearer {api_key}API authentication
X-SignatureEd25519 signatureRequest integrity and non-repudiation
X-TimestampUnix timestampReplay protection
Idempotency-KeyUUID v4 (recommended)Prevents duplicate payments on retry
Content-Typeapplication/jsonRequest body format
The Idempotency-Key header is mandatory on all payment creation requests. Requests without this header are rejected with a 400 error. Always generate a new UUID for each distinct payment and reuse the same UUID only when retrying the exact same request.

Send Amount vs. Receive Amount

The amountInputSide field controls which side of the payment you are specifying.

Send-side (you control the debit)

{
  "sendAmount": { "currency": "USD", "value": "1000000" },
  "amountInputSide": "send"
}
The API calculates receiveAmount based on FX rate and fees. Your account is debited exactly totalDebitAmount (send amount + fees).

Receive-side (you control the credit)

{
  "receiveAmount": { "currency": "EUR", "value": "900000" },
  "amountInputSide": "receive"
}
The API calculates sendAmount and totalDebitAmount by working backward from the target receive amount through fees and FX rate.
When to use receive-side: Use this when your counterparty expects an exact amount (e.g., an invoice for EUR 9,000.00). The API back-calculates the send amount so the counterparty receives exactly what they expect.

What Happens After Creation

After a 201 Created response:
  1. The payment status is created. A payment.created webhook fires.
  2. The payment moves to processing and then completed.
  3. The totalDebitAmount is held on the source account immediately upon creation.
See Payment Lifecycle for the full state machine.

API Reference