Get KYC Status
Poll the current KYC verification status for a ramp intent. The checkout widget calls this endpoint every 5 seconds while a customer is verifying — use the same endpoint if you embed verification yourself.
GET
https://api.zenpayz.com/payment/api/v1/ramp-intents/:intentId/kyc-statusBearer · API key
Request
Headers
| Header | Description |
|---|---|
x-request-id OPTIONAL | Custom request ID for tracing |
Public Endpoint
This endpoint does not require authentication headers. It is designed to be called from your frontend / checkout page.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
intentId REQUIRED | string | The ramp intent ID (e.g. ri_1710345678000_a1b2c3d4e5f6g7h8) |
Response
Success (200 OK)
{
"success": true,
"data": {
"verified": false,
"kycStatus": "in_review",
"intentStatus": "active",
"cancelReason": null
},
"message": "KYC status retrieved"
}
Response Fields
| Parameter | Type | Description |
|---|---|---|
verified OPTIONAL | boolean | Convenience flag — true when kycStatus === 'approved'. |
kycStatus OPTIONAL | string | pending, in_review, approved, declined, or expired. |
intentStatus OPTIONAL | string | Current ramp intent lifecycle state (created, active, completed, expired, cancelled). |
cancelReason OPTIONAL | 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. Stop polling. |
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. |
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.
Recommended pattern:
- Poll every 5 seconds.
- Stop on any of:
kycStatus === 'approved','declined', or'expired'. - After ~60 attempts with no terminal state, surface a non-blocking "we're still confirming your verification" message with a manual refresh.
Error Responses
| Code | HTTP | Message |
|---|---|---|
| NOT_FOUND | 404 | Intent with the given ID does not exist |
Examples
- cURL
- JavaScript
- Python
curl https://api.zenpayz.com/payment/api/v1/ramp-intents/ri_1710345678000_a1b2c3d4e5f6g7h8/kyc-status
const intentId = 'ri_1710345678000_a1b2c3d4e5f6g7h8';
async function pollKycStatus() {
const res = await fetch(
`https://api.zenpayz.com/payment/api/v1/ramp-intents/${intentId}/kyc-status`
);
const { data } = await res.json();
switch (data.kycStatus) {
case 'approved':
console.log('KYC verified — proceed with checkout');
return 'done';
case 'in_review':
console.log('Identity OK, under review — keep waiting');
return 'continue';
case 'declined':
case 'expired':
console.log(`Terminal: ${data.cancelReason}`);
return 'stop';
default:
return 'continue';
}
}
const intervalId = setInterval(async () => {
const next = await pollKycStatus();
if (next !== 'continue') clearInterval(intervalId);
}, 5000);
import time, requests
intent_id = "ri_1710345678000_a1b2c3d4e5f6g7h8"
for _ in range(60): # 5 minutes worth of polls
res = requests.get(
f"https://api.zenpayz.com/payment/api/v1/ramp-intents/{intent_id}/kyc-status"
)
data = res.json()["data"]
if data["kycStatus"] == "approved":
print("KYC verified — proceed")
break
if data["kycStatus"] in ("declined", "expired"):
print(f"Terminal: {data['cancelReason']}")
break
time.sleep(5)
else:
print("Still confirming — show a manual refresh UI")
Next Steps
- Get Ramp Intent — Fetch the full intent including the widget URL
- KYC Webhooks — Get notified when KYC state changes without polling
- On-Ramp Flow — End-to-end buy integration