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
| Field | Type | Required | Description |
|---|---|---|---|
userId | string | Yes | Unique user identifier from your system — used as the KYC record key |
kycVerified | boolean | Yes | When true, ZenPays KYC verification is skipped. When false, the widget may prompt the user for identity verification. |
type | string | Yes | buy (fiat → crypto) or sell (crypto → fiat) |
wallets | array | Yes | Array of { network, address } objects (min 1) |
fiatCurrency | string | Yes | Fiat currency ISO code (e.g. USD, EUR, INR) |
cryptoCurrency | string | No | Pre-selected crypto (e.g. btc_bitcoin, usdt_tron) |
amount | number | No | Pre-filled amount in the widget |
successUrl | string | No | URL to redirect on success |
cancelUrl | string | No | URL to redirect on cancel |
customerEmail | string | No | Customer email for pre-filling |
metadata | object | No | Custom key-value pairs (returned in webhooks) |
- JavaScript
- Python
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',
})
intent = zenpays.ramp_intents.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"
}
| Field | Type | Description |
|---|---|---|
intentId | string | Unique intent identifier (ri_{timestamp}_{hex}) |
redirectUrl | string | URL to redirect the customer to the ramp widget |
expiresAt | string | ISO 8601 expiry timestamp (1 hour from creation) |
list
- JavaScript
- Python
const { data, meta } = await zenpays.rampIntents.list({
type: 'buy',
status: 'active',
page: 1,
limit: 20,
})
result = zenpays.ramp_intents.list({"type": "buy", "status": "active", "limit": 20})
Filters
| Field | Type | Description |
|---|---|---|
page | number | Page number (default: 1) |
limit | number | Items per page (default: 20, max: 100) |
status | string | Filter by status: created, active, completed, expired, cancelled |
type | string | Filter by direction: buy or sell |
search | string | Search 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
}
| Field | Type | Description |
|---|---|---|
intent | object | Full intent details |
intent.status | string | Lifecycle state: created, active, completed, expired, or cancelled |
intent.kycStatus | string | KYC sub-state: pending, in_review, approved, declined, or expired |
intent.cancelReason | string | null | Set when status='cancelled'. kyc_rejected when KYC was declined; kyc_expired for expiry; otherwise a free-form string. |
widgetUrl | string | null | Signed widget URL for iframe embedding. null if KYC required or expired. |
kycRequired | boolean | Whether ZenPays KYC verification is needed |
kycVerificationUrl | string | null | Verification URL (call initiateKyc if null and KYC is required) |
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.
- JavaScript
- Python
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
}
kyc = zenpays.ramp_intents.initiate_kyc("ri_xxx", {
"email": "user@example.com",
"firstName": "John",
"lastName": "Doe",
})
if kyc["kycVerificationUrl"]:
print(f"Redirect to: {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.
- JavaScript
- Python
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
}
status = zenpays.ramp_intents.get_kyc_status("ri_xxx")
if status["kycStatus"] == "approved":
print("KYC verified — proceed")
elif status["kycStatus"] == "in_review":
print("Identity OK, flagged for review — keep waiting")
elif status["kycStatus"] in ("declined", "expired"):
print(f"Terminal: {status['cancelReason']}")
else:
print("Still pending — keep polling")
Response
{
"verified": false,
"kycStatus": "in_review",
"intentStatus": "active",
"cancelReason": null
}
| Field | Type | Description |
|---|---|---|
verified | boolean | Convenience flag — true when kycStatus === 'approved' |
kycStatus | string | pending, in_review, approved, declined, or expired |
intentStatus | string | Current ramp intent lifecycle state (created, active, completed, expired, cancelled) |
cancelReason | string | null | Set when the intent moved to cancelled because of KYC. kyc_rejected for declines, kyc_expired for expiry. |
Understanding KYC states
kycStatus | What it means | What to do |
|---|---|---|
pending | Customer hasn't completed the Didit flow yet | Keep polling. Optionally show a "verification in progress" UI. |
in_review | Identity passed but AML or face-match was flagged for manual review | Keep polling. Surface a friendly "under review" message — this can take a few minutes. |
approved | KYC and AML both cleared | Proceed with the checkout / surface widgetUrl. |
declined | Hard 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. |
expired | Verification session expired before the customer finished | Stop polling. Same handling as declined. Optionally offer to start a new intent. |
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.