Skip to content

Search is only available in production builds. Try building and previewing the site to test it out locally.

FX Behavior & Rate Locking

Cashela provides transparent foreign exchange (FX) rates for all cross-currency transactions. This guide explains how rates are calculated, locked, and applied.


Overview

When processing cross-currency payments, Cashela:

  1. Quotes real-time rates from wholesale FX markets
  2. Applies transparent spreads disclosed in each quotation
  3. Locks rates for a defined period (typically 1 hour)
  4. Discloses all fees upfront before transaction confirmation

FX Rate Calculation

Rate Components

ComponentDescription
wholesale_fx_rateBase market rate from wholesale FX providers
applied_spreadCashela’s spread percentage (disclosed)
final_rateRate applied to transaction (wholesale + spread)

Example Calculation

Source: 1,000 USD
Destination: BRL
Wholesale Rate: 5.4200
Applied Spread: 0.5%
Final Rate: 5.4200 × (1 - 0.005) = 5.3929
Destination Amount: 1,000 × 5.3929 = 5,392.90 BRL

Rate Locking

How It Works

  1. Request a quotation – Receive locked rate valid for a specific period
  2. Review terms – Display rate and fees to your user
  3. Execute within validity – Create transaction before expiration
  4. Rate guaranteed – Transaction uses locked rate regardless of market movement

Quotation Response

{
"success": true,
"data": {
"id": "qtn_01HJ3KBCD8E9F0G1H2I3J4K5L6",
"source": {
"amount": 1000.00,
"currency": "USD"
},
"destination": {
"amount": 5392.90,
"currency": "BRL"
},
"fx_rate": {
"wholesale_rate": 5.4200,
"applied_rate": 5.3929,
"spread_percentage": 0.50
},
"fee": {
"amount": 5.00,
"currency": "USD"
},
"total_cost": {
"amount": 1005.00,
"currency": "USD"
},
"expiration_date": "2024-01-15T11:30:00.000Z",
"valid_for_seconds": 3600
}
}

Lock Duration

ProductTypical Lock Duration
Pay-InA deposit keeps the quote it was created with until it is paid or its payment window (validity_period) ends. Rates returned by Get exchange rates are indicative and not locked.
Pay-Out quotation30 minutes. See Quotation rate locking.

Fee Structure

Fee Types

Fee TypeDescription
transaction_feeFixed or percentage fee per transaction
fx_spreadBuilt into the exchange rate
network_feeFees from payment networks (if applicable)

Fee Display

Always display fees clearly to your users:

function displayPricingBreakdown(quotation) {
const breakdown = {
sourceAmount: `${quotation.source.amount} ${quotation.source.currency}`,
exchangeRate: `1 ${quotation.source.currency} = ${quotation.fx_rate.applied_rate} ${quotation.destination.currency}`,
transactionFee: `${quotation.fee.amount} ${quotation.fee.currency}`,
totalCost: `${quotation.total_cost.amount} ${quotation.total_cost.currency}`,
destinationAmount: `${quotation.destination.amount} ${quotation.destination.currency}`,
rateLockedUntil: new Date(quotation.expiration_date).toLocaleString()
};
return breakdown;
}

Handling Rate Expiration

Check Before Transaction

async function createTransactionSafely(quotationId, transactionData) {
// 1. Get current quotation status
const quotation = await api.getQuotation(quotationId);
// 2. Check if expired
const expiresAt = new Date(quotation.expiration_date);
const now = new Date();
if (now >= expiresAt) {
// Rate expired - need new quotation
throw new QuotationExpiredError('Rate lock has expired. Please get a new quote.');
}
// 3. Warn if expiring soon (within 5 minutes)
const minutesRemaining = (expiresAt - now) / (1000 * 60);
if (minutesRemaining < 5) {
console.warn('Quotation expiring soon:', minutesRemaining.toFixed(1), 'minutes');
}
// 4. Create transaction
return await api.createTransaction({
quotation_id: quotationId,
...transactionData
});
}

Auto-Refresh Pattern

async function getQuotationWithAutoRefresh(params, minValidMinutes = 10) {
let quotation = await api.createQuotation(params);
// If expires in less than threshold, immediately get a fresh quote
const expiresAt = new Date(quotation.expiration_date);
const now = new Date();
const minutesRemaining = (expiresAt - now) / (1000 * 60);
if (minutesRemaining < minValidMinutes) {
// Wait briefly and get a fresh quote
await sleep(1000);
quotation = await api.createQuotation(params);
}
return quotation;
}

Exchange Rate API

Pay-In: indicative rate

POST/api/v1/pay-in/deposit-creation/exchange-rates

Returns data.country, data.amount and data.exchange_rates for a country and payment type. The rate is indicative; the amounts that apply to a deposit are the ones in Get payment methods and in the deposit. See Get exchange rates and, for card payments charged in USD, Card payments.


Best Practices

1. Always Show Locked Rates

Display the locked rate and expiration time to users:

<div class="rate-lock-info">
<p>Exchange Rate: 1 USD = 5.3929 BRL</p>
<p>Rate locked until: 11:30 AM (in 45 minutes)</p>
<p class="warning" id="expiry-warning" style="display:none;">
⚠️ Rate expires soon. Complete your transfer now.
</p>
</div>
<script>
// Show warning when < 10 minutes remaining
setInterval(() => {
const remaining = (expiryDate - new Date()) / 60000;
if (remaining < 10 && remaining > 0) {
document.getElementById('expiry-warning').style.display = 'block';
}
}, 30000);
</script>

2. Handle Rate Changes Gracefully

When a quotation expires mid-flow:

try {
await createTransaction(quotationId, data);
} catch (error) {
if (error.code === 'QUOTATION_EXPIRED') {
// Get new quote and show updated rate to user
const newQuotation = await createQuotation(params);
if (hasRateChangedSignificantly(oldQuotation, newQuotation)) {
// Ask user to confirm new rate
const confirmed = await showRateChangeConfirmation(oldQuotation, newQuotation);
if (!confirmed) {
throw new UserCancelledError();
}
}
// Retry with new quotation
await createTransaction(newQuotation.id, data);
}
}

3. Log Rate Information

Keep records for reconciliation and compliance:

await db.transactionLogs.create({
transactionId: transaction.id,
quotationId: quotation.id,
wholesaleRate: quotation.fx_rate.wholesale_rate,
appliedRate: quotation.fx_rate.applied_rate,
spreadPercentage: quotation.fx_rate.spread_percentage,
sourceAmount: quotation.source.amount,
destinationAmount: quotation.destination.amount,
fee: quotation.fee.amount,
timestamp: new Date()
});