Pay foreign currency invoice (FX)
This recipe guides you through integrating the Foreign Exchange (FX) document payment workflow. It allows your application to handle multi-currency cross-border invoice settlements where currency conversion, live exchange rates, and real-time fee acceptance are required.
Prerequisites
- The customer has onboarded the Outbound Payments app and a payment account is available.
- The payment account must include
INITIATE_FX_PAYMENTunder itsavailableOperations.
Step 1: Detect foreign currency payment
Endpoint: /solution/business/v2/spaces/{spaceId}/payments/documents:validate
Path parameter:
spaceId
Requires a JSON request body (see
curlexample below).
The paymentAccountId must be the ID that belongs to the account used to initiate the payment.
{
"documents": [
{
"id": "df8d2deb-fcb0-41c0-8b21-29f750d78d4a"
}
],
"paymentAccountId": "92cc8808-0d19-41fb-a6f9-b70bd5441c6b"
}
Successful response:
The "code": "FX_PAYMENT" present in the response means that it is a foreign currency invoice payment.
In this context, status = "INVALID" means the payment is not yet ready to be processed (for example, due to a missing creditor account). The FX_PAYMENT info flag is the actual detection signal and appears in both INVALID and VALID responses throughout the flow.
{
"status": "INVALID",
"validationResult": {
"paymentMessages": [
{
"code": "FX_PAYMENT",
"type": "INFO"
}
],
"documentsMessages": [
{
"id": "df8d2deb-fcb0-41c0-8b21-29f750d78d4a",
"messages": [
{
"code": "MISSING_CREDITOR_ACCOUNT",
"type": "ERROR"
}
]
}
]
}
}
Step 2: Pre-validation (fees & eligibility)
Step 2.1: Retrieve payment details
Endpoint: /datastore/documents/transaction/v2/spaces/{spaceId}/documents:paydetails
Path parameter:
spaceId
Requires a JSON request body (see
curlexample below).
{
"documents": ["df8d2deb-fcb0-41c0-8b21-29f750d78d4a"]
}
Successful response:
{
"documents": [
{
"id": "df8d2deb-fcb0-41c0-8b21-29f750d78d4a",
"metadata": {
"dueDate": "2026-06-25",
"supplier": {
"address": {
"country": "BE",
"postCode": "1024",
"townName": "Los Angeles",
"streetName": "1234 Havelaan"
},
"legalName": "The SUA Supplier",
"tradeName": "The SUA Supplier"
},
"issueDate": "2026-05-26",
"documentTotals": {
"netAmount": "15",
"vatAmount": "15",
"grossAmount": "15",
"payableAmount": "15"
},
"totalAmount": "15",
"currencyCode": "RON",
"paymentMethod": "CARD",
"documentNumber": "INVOICE-207",
"paymentMeans": [
{
"paymentReference": {
"type": "UNSTRUCTURED",
"value": "us-26051",
"scheme": "OGM_VCS"
}
}
],
"supplierReference": "Supplier reference",
"$schema": "https://btx.unifiedpost.com/btx/datastore/document/document_metadata/v1/document-metadata-schema.json"
},
"status": {
"pay": "UNPAID",
"document": "FULLY_APPROVED"
},
"tasks": [
{
"extensionName": "com.unifiedpost.btx.aggregation:markAsPaid",
"context": {}
},
{
"extensionName": "com.unifiedpost.btx.data:markAsRefused",
"context": {}
},
{
"extensionName": "com.unifiedpost.btx.data:markAsResolved",
"context": {}
},
{
"extensionName": "com.unifiedpost.btx.data:pay",
"context": {}
}
],
"supplier": {
"type": "business",
"id": "9e6e8533-8e85-404f-a0e6-c5495539bf1e",
"name": "The RO Supplier",
"paymentAccounts": [
{
"id": "b9eb36e8-ca32-4661-9bba-94b89a8301ff",
"identifier": "RO71RZBR4836955184545156",
"scheme": "IBAN",
"country": "RO",
"currency": "RON",
"bank": {
"identifier": "RZBRROBU",
"scheme": "BIC"
},
"spaceId": "e57cafcb-16bd-447a-b81c-67ba3152c4d8",
"ownedBySpace": false,
"active": true,
"ownerName": "The BE Customer"
}
]
},
"category": "INVOICE"
}
]
}
Step 2.2: Retrieve FX fees & exchange rate
Endpoint: /solution/business/v2/spaces/{spaceId}/payments/documents:validate
Path parameters:
spaceId
Requires a JSON request body (see
curlexample below).
If you have multiple creditor payment accounts available, mention the one you want to use in the body.
If there is only one, we will automatically select that one.
{
"documents": [
{
"creditorPaymentAccountId": "98f49e1d-2e92-4467-8117-8386126a7653",
"id": "df8d2deb-fcb0-41c0-8b21-29f750d78d4a"
}
],
"paymentAccountId": "92cc8808-0d19-41fb-a6f9-b70bd5441c6b"
}
Successful response:
Key response codes in paymentsMessages
| Code | Type | Action |
|---|---|---|
FX_PAYMENT | INFO | Trigger the foreign exchange (FX) payment flow. |
NEW_FEE_DETECTED | ERROR | Pass the newly detected fee ID as acceptedFeeId in the next validation request. |
FAILED_CONTEXT_VALIDATION | ERROR | Render missing fields for incomplete beneficiary address details. |
RESTRICTED_ZONE | ERROR (sub-code) | Block payment immediately; beneficiary country is restricted. |
RESTRICTED_CURRENCY | ERROR (sub-code) | Block payment immediately; beneficiary currency is restricted. |
SERVICE_UNAVAILABLE | ERROR | Display a retry prompt; the underlying liquidity provider (Currency Cloud) is unreachable. |
PAYMENT_EXECUTION_METHOD_PROVIDER_OUT_OF_WORKING_HOURS | ERROR | Display an out-of-hours message, e.g. FX trading is closed (Mon 00:00 – Fri 21:30 GMT). |
Key response codes in documentsMessages
| Code | Type | Action |
|---|---|---|
MISSING_CREDITOR_ACCOUNT | ERROR | Prompt the user to select or confirm the beneficiary bank account. |
NEW_PAYMENT_MEANS_ACCOUNT_DETECTED | INFO | Display the newly detected account details to the user for confirmation. |
{
"status": "INVALID",
"validationResult": {
"paymentMessages": [
{
"code": "NEW_FEE_DETECTED",
"type": "ERROR",
"data": {
"agreementPolicy": "CONSENT_REQUIRED",
"counterPartyAmount": {
"value": "15",
"currency": "RON"
},
"currencyConversion": {
"buyAmount": {
"value": "15",
"currency": "RON"
},
"conversionRate": 0.1987,
"sellAmount": {
"value": "2.98",
"currency": "EUR"
}
},
"expirationTime": "2026-05-26T12:01:55.043635175Z",
"feeAmount": {
"value": "5.00",
"currency": "EUR"
},
"id": "9708f909-5452-4e76-9a12-3c2d5385e15f",
"originatingPartyAmount": {
"value": "7.98",
"currency": "EUR"
}
}
},
{
"code": "FX_PAYMENT",
"type": "INFO"
}
],
"documentsMessages": []
}
}
Step 2.3: Retrieve creditor account (optional)
Call this endpoint only if you got the error MISSING_CREDITOR_ACCOUNT to identify the creditor account.
Endpoint: /solution/business/v1/spaces/{spaceId}/payments/documents:findCreditorAccount
Path parameters:
spaceId
Requires a JSON request body (see
curlexample below).
{
"documentIds": ["d6b0fbcc-1900-407a-abcf-984b69ba9045"]
}
Successful response:
is this the creditor account????
{
"documents": [
{
"documentId": "d6b0fbcc-1900-407a-abcf-984b69ba9045"
}
]
}
Step 3: Final validation (after user confirmation)
Validates the transaction after adding the creditor account and accepting the fee.
Must be re-invoked on any subsequent address modification or fee refresh.
Endpoint: /solution/business/v2/spaces/{spaceId}/payments/documents:validate
Path parameters:
spaceId
Requires a JSON request body (see
curlexample below).
{
"paymentAccountId": "92cc8808-0d19-41fb-a6f9-b70bd5441c6b",
"acceptedFeeId": "071f9d1b-84dd-4dd9-890b-a86c54931101",
"documents": [
{
"id": "d6b0fbcc-1900-407a-abcf-984b69ba9045",
"creditorPaymentAccountId": "98f49e1d-2e92-4467-8117-8386126a7653"
}
]
}
Successful response:
Proceed to execution only if status = "VALID". If the response returns "INVALID", catch the remaining errors, surface them in the UI, and re-trigger validation after user correction.
{
"status": "VALID",
"validationResult": {
"paymentMessages": [
{
"code": "FX_PAYMENT",
"type": "INFO"
}
],
"documentsMessages": []
}
}
Step 4: Verification of payee (VOP)
Required for all FX payment accounts across all countries and payment schemes. Verification of Payee (VOP) cross-references the provided beneficiary name with the bank account holder record.
Note: For non-SEPA routing (e.g., US ABA), a TECH_ERROR response is expected behavior.
Endpoint: /payments/v1/spaces/{spaceId}/paymentAccounts:verify
Path parameters:
spaceId
Requires a JSON request body (see
curlexample below).
{
"accountHolderName": "The RO Supplier",
"accountIdentifier": {
"id": "b9eb36e8-ca32-4661-9bba-94b89a8301ff",
"identifier": "RO71RZBR4836955184545156",
"scheme": "IBAN",
"country": "RO",
"currency": "RON",
"bank": {
"identifier": "RZBRROBU",
"scheme": "BIC"
},
"spaceId": "e57cafcb-16bd-447a-b81c-67ba3152c4d8",
"ownedBySpace": false,
"active": true,
"ownerName": "The BE Customer"
}
}
Successful response:
Verification of Payee (VOP) response codes
| Code | ID Format | Pass as creditorVerificationId in execute? | Notes |
|---|---|---|---|
MATCH | "VOP-{alphanumeric}" e.g. VOP-2HvJi2TMRUPMLpBV | YES | Exact match |
CLOSE_MATCH | "VOP-{alphanumeric}" e.g. VOP-2HvJi2TMRUPMLpBV | YES | Name is similar but not exact |
NO_MATCH | "VOP-{alphanumeric}" e.g. VOP-2HvJi2TMRUPMLpBV | YES | Warn user of name mismatch |
NOT_POSSIBLE | "VOP-{alphanumeric}" | YES | Warn user that verification was not possible |
TECH_ERROR | "btxErrId{hex}" | YES | Warn user that verification was not possible |
{
"result": "MATCH",
"id": "VOP-gUJ35Vo3LMZppf8k"
}
Step 5: Pay invoice
After execution, redirect the client to redirectUrl for SCA/MFA. Await the return webhook or browser redirect to your callback page - do not poll the API.
Endpoint: /solution/business/v2/spaces/{spaceId}/payments/documents:execute
Path parameters:
spaceId
Requires a JSON request body (see
curlexample below).
{
"documents": [
{
"id": "df8d2deb-fcb0-41c0-8b21-29f750d78d4a",
"creditorPaymentAccountId": "98f49e1d-2e92-4467-8117-8386126a7653",
"creditorVerificationId": "04810220-eab3-40f7-a379-827721f16029",
"partialPaymentAmount": "500.00" // optional
}
],
"paymentAccountId": "92cc8808-0d19-41fb-a6f9-b70bd5441c6b",
"redirectUrl": "https://your-app.com/payments?showPaymentStatus=true",
"locale": "en",
"acceptedFeeId": "071f9d1b-84dd-4dd9-890b-a86c54931101"
}
Successful response:
{
"paymentId": "bd4cfccf-8701-4812-9f19-cf43faceb9ab",
"redirectUrl": "https://accounts.staging.ibis.unifiedpost-payments.com/web/payments/<paymentId>"
}