Skip to main content

Ramp Intents

Create and manage ramp intents — merchant-initiated buy/sell requests with embeddable widget URLs. Access via zenpays.rampIntents.

Methods

create

Create a new ramp intent.

Parameters

FieldTypeRequiredDescription
userIdstringYesUnique user identifier from your system — used as the KYC record key
kycVerifiedbooleanYesWhen true, ZenPays KYC verification is skipped. When false, the widget may prompt the user for identity verification.
typestringYesbuy (fiat → crypto) or sell (crypto → fiat)
walletsarrayYesArray of { network, address } objects (min 1)
fiatCurrencystringYesFiat currency ISO code (e.g. USD, EUR, INR)
cryptoCurrencystringNoPre-selected crypto (e.g. btc_bitcoin, usdt_tron)
amountnumberNoPre-filled amount in the widget
successUrlstringNoURL to redirect on success
cancelUrlstringNoURL to redirect on cancel
customerEmailstringNoCustomer email for pre-filling
metadataobjectNoCustom key-value pairs (returned in webhooks)
const intent = await zenpays.rampIntents.create({
userId: 'usr_abc123',
kycVerified: false,
type: 'buy',
wallets: [
{ network: 'ethereum', address: '0x742d35Cc6634C0532925a3b844Bc9e7595f2bD28' },
],
fiatCurrency: 'USD',
cryptoCurrency: 'eth_ethereum',
amount: 100,
successUrl: 'https://yoursite.com/success',
cancelUrl: 'https://yoursite.com/cancel',
})

Response

{
"intentId": "ri_1710345678000_a1b2c3d4e5f6g7h8",
"redirectUrl": "https://checkout.zenpayz.com/payments/ramping/widget/ri_1710345678000_a1b2c3d4e5f6g7h8",
"expiresAt": "2026-03-15T10:00:00.000Z"
}
FieldTypeDescription
intentIdstringUnique intent identifier (ri_{timestamp}_{hex})
redirectUrlstringURL to redirect the customer to the ramp widget
expiresAtstringISO 8601 expiry timestamp (1 hour from creation)

list

const { data, meta } = await zenpays.rampIntents.list({
type: 'buy',
status: 'active',
page: 1,
limit: 20,
})

Filters

FieldTypeDescription
pagenumberPage number (default: 1)
limitnumberItems per page (default: 20, max: 100)
statusstringFilter by status: created, active, completed, expired, cancelled
typestringFilter by direction: buy or sell
searchstringSearch by intent ID or customer email

get

Retrieve intent details including widget URL and KYC status.

const result = await zenpays.rampIntents.get('ri_xxx')

Response

{
"intent": {
"intentId": "ri_xxx",
"type": "buy",
"fiatCurrency": "USD",
"cryptoCurrency": "eth_ethereum",
"amount": 100,
"status": "active",
"kycStatus": "approved",
"cancelReason": null,
"wallets": [{ "network": "ethereum", "address": "0x742d..." }],
"userId": "usr_abc123",
"kycVerified": true,
"successUrl": "https://yoursite.com/success",
"cancelUrl": "https://yoursite.com/cancel",
"expiresAt": "2026-03-15T10:00:00.000Z"
},
"widgetUrl": "https://widget.onramper.com/...signed_url...",
"kycRequired": false,
"kycVerificationUrl": null
}
FieldTypeDescription
intentobjectFull intent details
intent.statusstringLifecycle state: created, active, completed, expired, or cancelled
intent.kycStatusstringKYC sub-state: pending, in_review, approved, declined, or expired
intent.cancelReasonstring | nullSet when status='cancelled'. kyc_rejected when KYC was declined; kyc_expired for expiry; otherwise a free-form string.
widgetUrlstring | nullSigned widget URL for iframe embedding. null if KYC required or expired.
kycRequiredbooleanWhether ZenPays KYC verification is needed
kycVerificationUrlstring | nullVerification URL (call initiateKyc if null and KYC is required)
Distinguishing decline reasons

When a customer's KYC is rejected, intent.status flips to 'cancelled' and intent.cancelReason becomes 'kyc_rejected'. You can also drive your UI off intent.kycStatus === 'declined' directly — both are set together.


updateStatus

const updated = await zenpays.rampIntents.updateStatus('ri_xxx', 'cancelled')

initiateKyc

Start ZenPays KYC verification for a ramp intent. Call this when kycRequired is true and kycVerificationUrl is null.

const kyc = await zenpays.rampIntents.initiateKyc('ri_xxx', {
email: 'user@example.com',
firstName: 'John',
lastName: 'Doe',
})

if (kyc.kycVerificationUrl) {
// Redirect user to verification
window.location.href = kyc.kycVerificationUrl
}

Response

{
"kycRequired": true,
"kycVerificationUrl": "https://verify.zenpayz.com/session/abc123",
"sessionId": "abc123"
}

getKycStatus

Poll the current KYC verification status for a ramp intent. This is the same endpoint the checkout widget polls every 5 seconds while a customer is verifying — call it the same way from your own UI if you embed the widget yourself.

const status = await zenpays.rampIntents.getKycStatus('ri_xxx')

switch (status.kycStatus) {
case 'approved':
// KYC passed — proceed with the checkout
break
case 'in_review':
// Identity OK but flagged (often AML) — keep waiting, surface "under review"
break
case 'declined':
case 'expired':
// Terminal failure — `status.intentStatus` is now 'cancelled' and
// `status.cancelReason` will tell you why ('kyc_rejected' or 'kyc_expired')
break
case 'pending':
default:
// Verification not yet completed — keep polling (or call initiateKyc)
break
}

Response

{
"verified": false,
"kycStatus": "in_review",
"intentStatus": "active",
"cancelReason": null
}
FieldTypeDescription
verifiedbooleanConvenience flag — true when kycStatus === 'approved'
kycStatusstringpending, in_review, approved, declined, or expired
intentStatusstringCurrent ramp intent lifecycle state (created, active, completed, expired, cancelled)
cancelReasonstring | nullSet when the intent moved to cancelled because of KYC. kyc_rejected for declines, kyc_expired for expiry.

Understanding KYC states

kycStatusWhat it meansWhat to do
pendingCustomer hasn't completed the Didit flow yetKeep polling. Optionally show a "verification in progress" UI.
in_reviewIdentity passed but AML or face-match was flagged for manual reviewKeep polling. Surface a friendly "under review" message — this can take a few minutes.
approvedKYC and AML both clearedProceed with the checkout / surface widgetUrl.
declinedHard rejection (identity mismatch, document issue, AML rejected)Stop polling. intentStatus is now cancelled with cancelReason='kyc_rejected'. Show a terminal "verification could not be completed" message.
expiredVerification session expired before the customer finishedStop polling. Same handling as declined. Optionally offer to start a new intent.
Polling cadence

The checkout widget polls every 5 seconds with a hard ceiling of ~5 minutes (60 attempts) before showing a "still confirming your verification" message. If you implement your own polling, mirror that pattern so you don't leave customers on a spinner indefinitely.