DEVCON 8MUMBAISee you at the event · 3—6 November, 2026, Mumbai, India Meet us there ↗
Coincashy
Personal
Buy cryptoCard, Apple Pay, SEPASell cryptoDirect to bank accountConvert crypto0% slippage swapsPersonal crypto cardEveryday card spending
How it works, in four steps
Business
Crypto processingOTC & liquidityOn/off-rampvIBANsWallet as a ServiceCorporate cardsStablecoin settlementAPIs & webhooks

Tell us how your money needs to move.

Share your markets, currencies, volumes and settlement requirements.Talk to our team
Solutions

By Audience

IndividualsFintechs & PlatformsMerchants & PSPsOTC Desks & Brokers

By Use Case

Crypto Payment ProcessingOn/Off Ramp IntegrationTreasury & SettlementWallet as a Service

Tell us how your money needs to move.

Share your markets, currencies, volumes and settlement requirements.Talk to our team
Company

Discover Coincashy

About UsCompliance & SecurityLife at CoincashyContact

Careers

Open RolesJoin our team

Resources

Help CenterAPI DocumentationPrivacy PolicyTerms
Log inTalk to salesGet startedGet started
Home
Personal
OverviewBuy cryptoSell cryptoConvert cryptoPersonal crypto cardHow it works
Business
OverviewCrypto processingOTC & liquidityOn/off-rampvIBANsWallet as a ServiceCorporate cardsStablecoin settlementAPIs & webhooks
Solutions
IndividualsFintechs & PlatformsMerchants & PSPsOTC DesksPayment ProcessingOn/Off RampTreasury & SettlementWallet as a Service
Company
About UsSecurity & ComplianceCareersHelp CenterAPI DocsPrivacy
Buy cryptoLoginTalk to our team
00Overview01Headers on every call02Log in (JWT)03Create the guest04Create the onramp link05Payload reference06What happens next07Withdrawal to external wallet08Status codes09To verify

Overview

Three calls mint a payment link a Coincashy guest can open to buy crypto with fiat. This page walks the calls in order, then documents every field and everything that comes back. Every path below is called against trade.coincashy.io.

LIVE IN PRODUCTION
Hosted On-Ramp & Payment Links

JWT authentication, guest profile provisioning, webhook events, and hosted fiat-to-crypto checkout links.

PARTNER PILOT / SANDBOX
Direct Programmatic Payouts

Automated wallet payouts, institutional RFQ quotes, and custom multi-currency ledger routing.

System architecture flow

System architecture flow

Steps 1-3 are yours to call from the Coincashy backend. Everything from "guest opens link" onward is the widget's job — it's covered in §06 so you know what to expect on the other end, not because you need to build it. Once the fiat lands, paying the crypto out is a separate call you make yourself — §07.

01Headers on every call

One header. Send it on every call and you're covered.

HEADERVALUEAPPLIES TO
AuthorizationBearer <token> from loginEvery /api/v3/* call — §03 and §04. You won't have one yet when calling /jwt/generate itself.

02Log in and get a JWT

Server-to-server — no password, no OTP screen. api_key / api_secret are the integration credentials the platform team issues to Coincashy; username is the merchant account they're tied to.

RequestPOST/api/jwt/generate
Illustrative Code Example
{
  "api_key": "<coincashy api key>",
  "api_secret": "<coincashy api secret>",
  "username": "ops@coincashy.io"
}
Response · 200JWTController::getToken
Illustrative Code Example
{
  "iat": 1758100000,
  "exp": 1758103600,
  "type": "Bearer",
  "token": "eyJhbGciOiJIUzI1NiIs...",
  "scopes": [ "..." ],
  "api_data": {
    "user": { "user_type": "customer" }
  }
}

Note the path

This one call lives at /api/jwt/generate — not under /api/v3/ like everything else in this guide. From here on, use token exactly the same way: Authorization: Bearer <token> on every call in §03 and §04.

Two things worth knowing

The token is good for 1 hour, fixed. There's no separate refresh call — minting a new token immediately invalidates whichever one was last issued to that username, so just call /jwt/generate again when you need a fresh one (don't run two of these at once for the same username).

A wrong api_key / api_secret / username combination comes back as a plain 401 with the text Invalid Credentials — not the JSON error body used elsewhere in this guide.

03Create the guest

The guest is the person who'll open the link and buy crypto. You have two options, depending on whether you want the link to auto-log them in.

Option A — pre-create the guest (auto-login link)

Register the guest's email against your merchant account before minting the link. Pass this guest's email as payload.username in step 4 and the widget skips straight to the purchase — no OTP screen.

RequestPOST/api/v3/auth/create-guest-with-pii
Illustrative Code Example
{
  "email": "demo.guest@coincashy.io"
}
Response · 200per the route's Swagger contract
Illustrative Code Example
{
  "message": "Guest user created/updated successfully",
  "userId": "10432",
  "isNew": true
}

Confirm before relying on this

auth.controller.ts currently declares two handlers on the identical route POST /auth/create-guest-with-pii — one bare-bones (just writes a PII blob), one full-featured (also accepts KYC files/questionnaire answers, links the guest to your merchant account, and kicks off Sumsub onboarding). Express/Nest only ever runs the first-declared handler for a duplicated route — the second is dead code. So the response you actually get, and whether the guest ends up linked to your merchant, may not match what the fuller description promises. Worth one live test against staging before depending on it.

Option B — let the guest self-register (no pre-creation)

Omit username entirely in step 4. The link still works — opening it returns login_required, and the widget itself calls the guest self-onboarding routes (/auth/guest/register then /auth/guest/login with an emailed OTP). Simplest option if you don't already know the guest's email ahead of time, or don't need an auto-login link.

04Create the onramp link

This is the endpoint you started with — corrected. The body needs a payload wrapper; a bare { flow, username } object at the top level fails validation.

RequestPOST/api/v3/payments/guests/onramp
Illustrative Code Example
{
  "payload": {
    "flow": "onramp",
    "username": "demo.guest@coincashy.io",
    "user_id": "<coincashy merchant id>",
    "fiat_currency": "EUR",
    "receive_currency": "USDT",
    "network": "TRON"
  }
}
Response · 201CreateGuestPaymentResponseDto
Illustrative Code Example
{
  "paymentLink": "https://trade.coincashy.io/widget/token?paymentId=pl%3A6f9c9a2e-3b7a-4e2b-9a1e-2c9b6a5b9f10"
}

Where trade.coincashy.io comes from

The link's host isn't a request field — it's read from the target merchant's WIDGET_URI setting server-side (falling back to a platform-wide default if unset). If the returned link doesn't point at trade.coincashy.io, that's a setting to fix on the merchant record you're logged in as, not a payload option.

The token after pl: is an opaque, server-minted secret — not an encrypted id you decode. It's the credential: whoever presents it gets the guest session it's bound to, so treat the whole link like a bearer token in transit (send over HTTPS, don't log it in plaintext, don't put it somewhere it'll be cached or indexed).

05Payload reference

Everything payload and the optional notification object accept.

payload

FieldTypeNotes
flowstring (req)always "onramp" for Coincashy
user_idstring (req)the Coincashy merchant id — scopes the link to your merchant account. The caller must be an operator authorized to target it.
usernameemailan existing guest's email → auto-login link. Absent → self-service link.
external_idstringyour idempotency / reference key. Must be unique. Server generates a UUID if omitted.
fiat_currencystringone of USD, EUR, AED
fiat_amountnum strpositive, up to 2 decimal places, e.g. "250.00"
receive_currencystringone of USDC, USDT, BTC, TRX — the crypto the guest ends up with
networkstringone of ETHEREUM, BITCOIN, TRON, BINANCE_SMART_CHAIN
vendor_idstringpre-selects a PSP provider — skips the picker screen
methodstringpre-selects a payment method, e.g. "Wire Transfer"

notification (optional)

FieldTypeNotes
webhook_urlURLserver-to-server callback once the payment settles
success_urlURLwhere the widget redirects the guest on success
fail_urlURLwhere the widget redirects the guest on failure

Access Control

Only a non-guest caller can hit this endpoint — a guest JWT gets a flat 403. That's an identity check on you (the caller), separate from the guest the link is being created for.

06What happens after you send the link

In plain terms:

  1. The guest opens the link.
  2. If you set username in step 04, they're logged straight in. Otherwise they get a one-time code by email first.
  3. First payment ever on this account? They're asked to verify their identity (KYC) before they can continue. Returning guests skip this.
  4. They pick a payment method and pay.
  5. Done — success or fail. You find out two ways: your notification.webhook_url gets called, and you can poll for the status yourself any time.
  6. Fiat confirmed — the merchant's crypto balance updates. Withdrawing it to an external wallet is a separate step you trigger — §07.

Checking payment status — poll or wait for the webhook

notification.webhook_url fires once the payment reaches a final state. If you'd rather check directly — or the webhook never arrives — poll this endpoint with the external_id you set in step 04.

RequestGET/api/v3/psp/onramp?fB=cid&fV=3fa1c2e0-...-uuid&fT=S&fO=EQ

fB/fV/fT/fO = filter field / value / type / operator · S = string, EQ = equals

Response · 200PspOnrampListResponseDto
Illustrative Code Example
{
  "totalItems": 1,
  "result": [
    {
      "id": "58213",
      "status": "PENDING_PAYMENT",
      "cid": "3fa1c2e0-...-uuid",
      "external_payment_id": "psp-ref-88213",
      "merchant_id": "77",
      "created_at": "2026-09-17T16:02:11.000Z",
      "updated_at": "2026-09-17T16:04:40.000Z"
    }
  ]
}
statusMeans
PENDING_PAYMENTstill in progress — guest is somewhere in checkout
PENDING_SIGNATURE, PENDING_TRANSFERstill in progress
COMPLETEDdone — the guest has their crypto
REJECTED / REJECTED_BY_ADMINfailed / closed out
expired / EXPIERDguest never finished it in time
FULL_REFUND / CHARGEBACKEDmoney went back — treat as failed

It won't exist right away

This row only appears once the guest actually reaches the PSP checkout step inside the widget — not the moment you create the link. An empty result right after sending the link usually just means the guest hasn't gotten that far yet.

One more thing worth knowing: the link's expiry mirrors the access-token lifetime, so it dies on the same clock as the JWT it hands out — ask the platform team for the current JWT_ACCESS_EXPIRE_IN value rather than assuming one. Past that, opening the link fails with "payment link expired".

07Withdrawal to external wallet

Once the fiat deposit is approved (§06), the Coincashy merchant's crypto balance updates. From there, the merchant can withdraw that balance to an external wallet — the usual three things: wallet address, currency, and network.

Whose balance this spends

This pays out of Coincashy's own account balance on the platform — not an amount escrowed per deposit. A confirmed fiat deposit credits that balance; the merchant then chooses to withdraw from it. Keep it funded, or the call fails with INSUFFICIENT_FUNDS.

Look up the currency_id

Public, no auth needed. The same currency can appear several times — once per network it's issued on — each with its own id. Match the network the destination wallet is actually on.

RequestGET/api/currencies
Response · 200api_message: CURRENCY_GET_LIST_SUCCESS
Illustrative Code Example
{
  "api_data": {
    "currencies": [
      { "id": "bkE0RmNjbEhCUmc9", "name": "USD",  "network": "FIAT",   "type": "FIAT" },
      { "id": "eGFCV203K3VSOGM9", "name": "BTC",  "network": "BITCOIN", "type": "CRYPTO" },
      { "id": "ckk3THQ2KzVmd009", "name": "USDC", "network": "BINANCE_SMART_CHAIN", "type": "CRYPTO" },
      { "id": "aFpaK2ViQlVob0U9", "name": "USDC", "network": "TRON",     "type": "CRYPTO" },
      { "id": "OTJqTmNBRk1LMVU9", "name": "USDC", "network": "ETHEREUM", "type": "CRYPTO" }
    ]
  }
}

Three rows above are all named "USDC" with three different ids — one per network. Sending the wrong one doesn't fail loudly; it just tries to pay out on the wrong chain. Always pick by network, never by name alone.

Send it

FieldTypeNotes
amountnum str (req)plain decimal in the currency's normal units, e.g. "1" = 1 USDC — not base units
recipient_addressstring (req)the destination wallet — validated against the address format for the resolved network
currency_idstring (rec)the id from the currency list above — pins the exact network
currencystring (fallback)a bare name like "USDC" — only use without currency_id; falls back to a platform default network
initial_ratenum str (opt)the exchange rate to lock in for this payout
initial_rate_currency_idstring (opt)which currency initial_rate is priced against
RequestPOST/api/transfers
Illustrative Code Example
{
  "amount": "1",
  "currency": "USDC",
  "currency_id": "ckk3THQ2KzVmd009",
  "recipient_address": "0xA10fC656A5c470A5A45b5a468E774E46FC039Ffb",
  "initial_rate": "0.99985735",
  "initial_rate_currency_id": "bkE0RmNjbEhCUmc9"
}
Response · 200api_message: TRANSFER_POST_SUCCESS
Illustrative Code Example
{
  "api_data": {
    "transfer": {
      "id": "94217",
      "status": "PENDING",
      "amount": "1",
      "currency": "USDC",
      "recipient_address": "0xA10fC656A5c470A5A45b5a468E774E46FC039Ffb"
    }
  }
}

Checking withdrawal status

Poll by the id the create call returned. A webhook also fires on status changes — ask the platform team how Coincashy's is configured, since (unlike the onramp link) this call has no per-request webhook_url field of its own.

RequestGET/api/transfers/94217
Response · 200transfer.status
Illustrative Code Example
{
  "api_data": {
    "transfer": {
      "id": "94217",
      "status": "CONFIRMED"
    }
  }
}

08Status codes

For §04's onramp-link call.

CodeMeaning hereTypical cause
201Link created—
400Bad requestunknown fiat_currency / receive_currency / network, a reused external_id, a username that isn't a guest, or missing payload/flow
401Unauthorizedmissing or expired bearer token
403Forbiddencaller is a guest, or a non-operator caller passed someone else's user_id
500Server errorunhandled — worth reporting with the request id / timestamp

Example error body

Illustrative Code Example
{
  "statusCode": 400,
  "timestamp": "2026-09-17T10:04:12.441Z",
  "path": "/api/v3/payments/guests/onramp",
  "method": "POST",
  "error": "Bad Request",
  "message": "invalid EURX fiat_currency"
}

09To verify against a live call

Everything on this page is read straight from the current source. These points are flagged because the code disagrees with itself, or because the value isn't in the code at all — confirm them once against staging, then this list is done.

  1. Get the Coincashy api_key / api_secret from the platform team — they don't come back from any endpoint.
  2. Confirm a /jwt/generate token is actually accepted as Authorization: Bearer on /api/v3/* calls — it's issued by a different, older auth system than the rest of the API.
  3. Confirm the exact fB/fV/fT/fO filter query on GET /psp/onramp (§06) still resolves by cid the way it's documented here.
  4. Test /auth/create-guest-with-pii live (§03) — confirm which of the two duplicate handlers actually answers, and whether the guest ends up linked to your merchant.
  5. Confirm the Coincashy merchant's WIDGET_URI is set to trade.coincashy.io, so links resolve to the right domain.
  6. Get the live JWT_ACCESS_EXPIRE_IN value from the platform team — it's the actual onramp-link TTL.
  7. Confirm the Coincashy merchant account is pre-funded with enough crypto balance to cover payouts (§07) — /transfers pays out of that balance and returns INSUFFICIENT_FUNDS when it's short.
Coincashy

Crypto, stablecoins and fiat rails for people and businesses. Buy, sell and spend, or accept, convert and settle at scale.

Products

Buy cryptoSell cryptoConvert cryptoCrypto cardCrypto processingOTC & liquidityOn/off-rampvIBANsWallet as a ServiceCorporate cardsStablecoin settlement

Solutions

IndividualsMerchants & PSPsOTC desks & brokersFintechs & platformsTreasury & accounts

Developers

API overviewPayments APIWallet APIQuotes APIWebhooks

Resources

How it worksHelp centerFees & limitsSystem statusFAQ

Company

About CoincashyComplianceCareersContactLog inGet started
Supportsupport@coincashy.io
  • Terms of Service
  • Privacy Policy
  • Cookie Policy
  • AML Policy
  • Complaints & disclosures
  • Disclaimer

© 2026 Coincashy. All rights reserved. Products and availability are subject to jurisdiction, onboarding and partner approval.

Crypto-assets are volatile and their value can go down as well as up. Card, account and vIBAN services are partner-enabled and subject to eligibility. Visa, Mastercard, Apple Pay, Google Pay, SEPA, Bitcoin, Ethereum and Tether are marks of their respective owners, shown as accepted methods.

Disclaimer
Coincashy Sp. z o.o. only provides services to customers resident in the UK who fall within an exemption available under the UK financial promotion regime (Investment professionals, High net worth companies, unincorporated associations etc., Certified sophisticated investors, Communication to overseas recipients, etc).

Your message is ready

Nothing has been sent yet. Send it from your email app, or copy it into any email.

Tosupport@coincashy.io
Subject
Message
Open email app