Testing Rate Locks

Exercise locked pricing and drift cancellation in the sandbox

Rate locks end in one of three ways: they expire, you cancel them, or we cancel them because the market moved. The first two you already know about — you set the expiry and you made the call. Drift cancellation is the one that arrives unannounced, which makes it the one your integration has to handle and the one worth testing deliberately.

The sandbox is built for exactly that. Sandbox corridors are priced from a fixed mid-market rate (midRate, the rate before your markup), isolated from the live market, so a lock’s rate is stable and predictable — except for one corridor that moves on purpose.

Sandbox corridors

PairSandbox midRateBehaviour
USD → XOF600Fixed. Only ever ends by expiry.
USD → XAF600Fixed. Only ever ends by expiry.
USD → NGN1550Fixed. Only ever ends by expiry.
USD → KES~129, driftingDrift pair — see below.

These are the corridors seeded for sandbox organisations. A pair configured on your account with no entry here has no rate, and creating a lock against it fails with a market-rate error rather than returning a lock.

The mid is fixed, not the all-in rate: your markupBps is applied on top as it is in production, so allInRate still reflects your own configuration.

Testing the happy path

Create a lock on any fixed corridor and it behaves exactly as production does, minus the market:

curl -X POST 'https://api.crisscross.money/v1/rate-locks' \
-H 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
-H 'Content-Type: application/json' \
--data-raw '{ "base": "USD", "quote": "XOF" }'

POST /v1/rate-locks — API reference

Pass: an active lock with midRate of 600, your configured markupBps, and an allInRate combining the two. Reuse the rateLockId across as many checkout sessions as you like; every one prices at allInRate.

Because the mid never moves on these corridors, a lock here will not be cancelled out from under you — which is what makes them the right choice for testing everything other than drift.

Testing drift cancellation

USD → KES is the corridor that moves. Its mid ramps steadily from a base of about 129 up by 3% over 10 minutes, then resets to the base and repeats.

A lock snapshots the mid at the moment it is created, so from then on the live sandbox mid diverges from the lock’s reference at a steady rate. Once that gap crosses your configured drift threshold, the monitor cancels the lock. At the sandbox default the cancellation lands roughly five minutes after the lock is minted.

To exercise it:

  1. Create a lock on USD → KES.
  2. Wait. Nothing else is required — no market event to arrange, no support request.
  3. Your endpoint receives rate_lock.cancelled with cancellationReason: market_drift.
  4. GET /v1/rate-locks/{rateLockId} now returns cancelled.

Pass: you receive the webhook, stop using that rateLockId, and fetch a replacement lock. A cancellation is permanent — a lock cancelled by drift stays cancelled even when the sandbox mid resets on its next cycle.

Do not use USD → KES for any other rate lock test. A lock on this corridor will be cancelled within minutes whether or not that is what you are testing, which makes it a confusing default for checkout or settlement scenarios. Use a fixed corridor for those.

What your integration has to do

When you receive rate_lock.cancelled, stop pricing new orders with that lock and create a new one. Payments already in progress still settle at the locked rate, but new checkout sessions that use the cancelled lock are rejected.

The five minutes is approximate, so wait for the webhook in your test rather than a timer.

What the sandbox does not cover

  • Real rates. Sandbox rates are round numbers chosen for predictability. They say nothing about the rate you will get in production on the same corridor.
  • Corridors outside the table. Only the four above are seeded. If you need another for testing, ask your solutions engineer.
  • Your production drift threshold. It is configured per account, so the roughly-five-minute figure applies to the sandbox default rather than to your live configuration.