> For the complete documentation index, see [llms.txt](https://vnx.gitbook.io/vnx-global/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://vnx.gitbook.io/vnx-global/developer/guides/integrate-onramp-offramp.md).

# Integrate Onramp Offramp

*Convert fiat to stablecoins and back through VNX's regulated payment rails, brokered through third-party providers.*

## Use case and audience

You are a fintech, exchange, or treasury app that needs a regulated on/off-ramp surface — TradFi to digital assets and back — without integrating multiple OTC and exchange counterparties. VNX brokers the conversion through regulated third-party providers and exposes it as a single **payment rail**: you register a rail with `/private/addPaymentRail`, then poll `/private/queryPaymentRails` and `/private/queryPayments` for status.

## Architecture

See the verified flow in [Crypto Exchange Flow](/vnx-global/developer/flows/on-offramp-flow.md).

```mermaid
sequenceDiagram
    participant App as Your service
    participant V as VNX API
    participant P as External regulated provider
    participant B as Bank/PSP
    App->>V: POST /private/addPaymentRail (ECDSA-signed, JSON)
    V-->>App: railid + status
    V->>P: Broker conversion
    P->>B: Fiat settlement to/from VNX
    App->>V: Poll /private/queryPaymentRails + /private/queryPayments
```

## Prerequisites

* KYB cleared.
* An ECDSA key-pair registered with VNX; the private key signs each request.
* A `User-Agent` string on every request and adherence to the 1 req/sec per-public-key rate limit.
* Source-side funds: fiat for a FiatToStablecoin rail (on-ramp) or stablecoins for a StablecoinToFiat rail (off-ramp).
* For external-wallet legs: whitelisted address + Travel Rule data.

## Sign every request

Every `/private/*` request carries `x-app-public-key`, `x-app-nonce` (always-increasing uint64, UNIX ms), and `x-app-signed-data` (base64url ECDSA-SHA256 signature over the URI path + canonical-JSON POST data + nonce), plus a `User-Agent`. Canonical JSON sorts object keys lexicographically; arrays keep order. `addPaymentRail` is the one `/private/*` call that uses `Content-Type: application/json` (sign the canonical JSON body).

## Add a payment rail (on-ramp: FiatToStablecoin)

```bash
curl -X POST https://api.vnx.io/api/v1/private/addPaymentRail \  -H "User-Agent: MyClient/1.0" \  -H "x-app-public-key: ${PUBLIC_KEY}" \  -H "x-app-nonce: ${NONCE}" \  -H "x-app-signed-data: ${SIGNATURE}" \  -H "Content-Type: application/json" \  -d '{    "railtype": "FiatToStablecoin",    "sender": {      "partyname": { "name": "Acme Treasury Ltd" },      "address": { "country": "GB" },      "partytype": "company"    },    "wallet": { "destination": "0xabc...", "memo": "cust_123" },    "instructions": { "asset": "VCHF", "currency": "CHF", "blockchain": "ETH" }  }'
```

```json
{  "railid": "0c2e4f6a-...-uuid",  "status": "pending",  "reason": null}
```

## Poll rail and payment status

There are no webhooks. Poll `/private/queryPaymentRails` to track the rail and `/private/queryPayments` for the underlying settlement; match on `railid`.

```bash
curl -X POST https://api.vnx.io/api/v1/private/queryPaymentRails \  -H "User-Agent: MyClient/1.0" \  -H "x-app-public-key: ${PUBLIC_KEY}" \  -H "x-app-nonce: ${NONCE}" \  -H "x-app-signed-data: ${SIGNATURE}" \  -H "Content-Type: application/x-www-form-urlencoded"
```

Respect the 1 req/sec rate limit when polling; back off on `{code, status, message}` errors.

## Reverse direction (off-ramp: StablecoinToFiat)

To convert stablecoins back to fiat, set `railtype: "StablecoinToFiat"` and provide a `bankaccount` destination instead of a `wallet`:

```json
{  "railtype": "StablecoinToFiat",  "sender": {    "partyname": { "name": "Acme Treasury Ltd" },    "address": { "country": "GB" },    "partytype": "company"  },  "bankaccount": { "destination": "GB29NWBK60161331926819" },  "instructions": { "asset": "VGBP", "currency": "GBP", "blockchain": "ETH" }}
```

The crypto-exchange fee in the published schedule is 0% (network fees only on withdrawal). Expect \~3 hours including Travel Rule exchange when an external wallet is involved.

## Test in UAT

Use the test base URL `https://api.uat.vnx.io/api/v1/` with your UAT credentials. Sign requests exactly as in production.

```bash
# Add a FiatToStablecoin rail
curl -X POST https://api.uat.vnx.io/api/v1/private/addPaymentRail \  -H "User-Agent: MyClient/1.0" \  -H "x-app-public-key: ${PUBLIC_KEY}" \  -H "x-app-nonce: ${NONCE}" \  -H "x-app-signed-data: ${SIGNATURE}" \  -H "Content-Type: application/json" \  -d '{    "railtype": "FiatToStablecoin",    "sender": { "partyname": { "name": "Test Co" }, "address": { "country": "CH" }, "partytype": "company" },    "wallet": { "destination": "0xabc..." },    "instructions": { "asset": "VCHF", "currency": "CHF", "blockchain": "ETH" }  }'
```

Expected UAT effects:

* A response with a `railid` (UUID) and `status`.
* `queryPaymentRails` shows the rail; `queryPayments` shows settlement.

## Troubleshooting

| Symptom                              | Likely cause                                        | Fix                                          |
| ------------------------------------ | --------------------------------------------------- | -------------------------------------------- |
| Rail `status` rejected with `reason` | Invalid party/instruction data                      | Inspect `reason`; correct and resubmit       |
| 401 signature/nonce error            | Bad signature, stale nonce, or wrong canonical JSON | Recompute over path + canonical body + nonce |
| Held rail                            | AML/sanctions/Travel Rule                           | Coordinate with VNX support                  |
| 429 rate limited                     | Exceeded 1 req/sec per public key                   | Throttle and back off                        |
| Long delay on fiat↔crypto leg        | Travel Rule exchange                                | Up to several hours; poll status             |

## Production checklist

* Always-increasing nonce (UNIX ms) per request; never reuse or decrease.
* Sign `addPaymentRail` over its canonical JSON body; other calls use form-urlencoded.
* Rate-limit budget mapped to 1 req/sec; back off on 429 and `{code, status, message}` errors.
* Audit log captures `railid` and nonce.
* Status reconciliation by polling `queryPaymentRails` and `queryPayments`.
* Fees displayed include VNX commission and provider spread.

## References

* [Crypto Exchange Flow](/vnx-global/developer/flows/on-offramp-flow.md)
* [Quotes and Conversions](/vnx-global/developer/api/quotes-and-conversions.md)
* [Travel Rule](/vnx-global/developer/concepts/travel-rule.md)
* [On Off ramp](/vnx-global/institutional/products/on-off-ramp.md)
* [Glossary](/vnx-global/glossary.md)
