Reference

KYC

Verify a user's identity so they can withdraw INR, gift assets or take physical delivery. Email, an instant PAN check, then Aadhaar by DigiLocker or by photos. All capability `kyc`.

KYC is required before a user can withdraw, gift or take physical delivery. Buying, selling and *receiving* gifts do not require it.

KYC is global, not per-partner: a user verified anywhere on OroPocket is verified everywhere. See sub-wallets.
Migrating from Aadhaar OTP? Aadhaar OTP verification has been retired. POST kyc/aadhaar/otp/send and kyc/aadhaar/otp/verify now answer 410 AADHAAR_OTP_RETIRED, in live and test mode. Keep your email and PAN steps, and replace the OTP screens with DigiLocker (instant) or Aadhaar photos (reviewed). The PAN step now answers nextStep: "aadhaar".

The flow

Every call is under /partner/users/:user_code/kyc/. Always start by reading the status — it tells you the exact next step, so the flow resumes cleanly if a user drops off.

KYC
Your serverThe userOroPocket
  1. Your server:

    Verify the email

    Send a 6-digit code to the user's email and pass back what they type.

    POST kyc/email/send · kyc/email/verify
  2. Your server:

    Check the PAN

    PAN, date of birth and name as on the card. Answered instantly.

    POST kyc/pan/verify

    A PAN photo is the fallback, only after an outage or two failed checks.

  3. The user:

    Aadhaar by DigiLocker

    The user signs in to DigiLocker and taps Allow. Instant when everything matches.

    POST kyc/aadhaar/digilocker

    Or send Aadhaar photos instead: kyc/aadhaar/photos.

  4. OroPocket:

    Approve or review

    Instant approval, or OroPocket's review — usually within 24–48 h.

  5. OroPocket:

    We call your webhook

    kyc.approved, kyc.under_review or kyc.rejected lands on your endpoint.

Steps 1 and 2 are your server's calls. In step 3 the user is on DigiLocker's pages, or your app collects Aadhaar photos.

When a user is approved

PathOutcome
Instant PAN + DigiLockerApproved in seconds when the name and date of birth match across PAN and Aadhaar. kyc.approved fires.
Anything elseA PAN photo, Aadhaar photos, a name or date-of-birth mismatch, or a possible duplicate goes to OroPocket's review (kyc.under_review), usually within 24–48 h, then kyc.approved or kyc.rejected.

Check status

GET/partner/users/:user_code/kyc/statuskyc

Drive your UI off v2.nextStep. It is the one field you need; the rest is detail.

FieldTypeDescription
statusstringnot_started | email_pending | email_verified | details_pending | documents_pending | under_review | approved | rejected.
emailVerifiedbooleanStage 1 done.
rejectionReasonstring | nullThe reviewer's message after a rejection.
flowstring"v2".
v2.nextStepstring | nullemail_verification | pan | aadhaar | awaiting_review, or null once approved.
v2.panVerifiedbooleanThe instant PAN check passed.
v2.panMethodstring | nullinstant | photo.
v2.panPhotoAllowedbooleanTrue when the PAN photo fallback is open for this user.
v2.aadhaarVerifiedbooleanAadhaar is verified.
v2.aadhaarMethodstring | nulldigilocker | photos | instant (verified earlier by the retired Aadhaar OTP).
v2.aadhaarLast4string | nullFor display.
v2.digilockerobject | nullThe latest DigiLocker session: {sessionId, status, failureCode}.
v2.nextStepShow the user
email_verificationThe email screen (Stage 1).
panThe PAN screen (Stage 2).
aadhaarA choice: DigiLocker (Stage 3a) or Aadhaar photos (Stage 3b).
awaiting_review“We're checking your documents.” Wait for kyc.approved or kyc.rejected.
nullDone — status is approved.
curl https://api.oropocket.com/partner/users/usr_xxx/kyc/status \
  -H "Authorization: Bearer oro_live_xxx"

Test mode returns the same shape, plus simulated: true.

Stage 1 — email

POST/partner/users/:user_code/kyc/email/sendkyc
FieldTypeDescription
emailstringrequiredThe user's permanent email. Must not already belong to another OroPocket account; disposable addresses are refused.
FieldTypeDescription
user_codestringThe user.
emailstringWhere the code went.
expiresAtstringISO time the code stops working (30 minutes).
POST/partner/users/:user_code/kyc/email/verifykyc
FieldTypeDescription
verification_codestringrequiredThe 6-digit code (alias: "code"). 123456 in test mode.
FieldTypeDescription
user_codestringThe user.
emailstringThe verified email.
verifiedAtstringISO time it was verified. Next: the PAN.

Failures come back as errors, not as success: true — for example 400 DISPOSABLE_EMAIL, or 400 INVALID_CODE for a wrong or expired code. Show error.message to the user.

Stage 2 — PAN

POST/partner/users/:user_code/kyc/pan/verifykyc

An instant check against the income-tax record. The email must be verified first.

FieldTypeDescription
panstringrequiredIndividual PAN, format AAAAA9999A.
dateOfBirthstringrequiredYYYY-MM-DD. Must match the PAN record; the user must be 18 or older.
nameAsPerPanstringrequiredThe name exactly as printed on the PAN card.
FieldTypeDescription
panVerifiedbooleantrue.
kycStatusstringThe user's KYC status after the check.
nextStepstring"aadhaar".
curl -X POST https://api.oropocket.com/partner/users/usr_xxx/kyc/pan/verify \
  -H "Authorization: Bearer oro_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{"pan":"ABCPK1234Z","dateOfBirth":"1990-08-15","nameAsPerPan":"ASHA KUMARI"}'
Error codeMeaning
400 INVALID_INPUTerror.details.field says which field: pan, dateOfBirth or nameAsPerPan. Bad format, PAN not valid or inactive, not an individual PAN, name or date of birth doesn't match, under 18, or the PAN is already registered to another account. Show error.message beside that field.
400 EMAIL_NOT_VERIFIEDDo Stage 1 first.
409 UNDER_REVIEW · ALREADY_APPROVEDNothing to do — the KYC is with our review, or already approved.
429 RATE_LIMITED5 PAN checks per user per day. The PAN photo route opens after two failed checks.
503 PROVIDER_UNAVAILABLEThe verification service is briefly unavailable. Retry later, or use the PAN photo.

Stage 2b — PAN photo (fallback only)

POST/partner/users/:user_code/kyc/pan/photokyc

Only open when v2.panPhotoAllowed is true: after the instant check hit a service outage, or the user failed it twice. Otherwise it answers 400 PAN_PHOTO_NOT_ALLOWED. A PAN photo always means the KYC ends in OroPocket's review.

FieldTypeDescription
panstringrequiredAAAAA9999A.
dateOfBirthstringrequiredYYYY-MM-DD.
nameAsPerPanstringrequiredAs printed on the card.
imagestringrequiredPhoto of the PAN card: base64 JPEG, PNG or PDF, up to 4 MB.
FieldTypeDescription
panMethodstring"photo".
nextStepstring"aadhaar".

Stage 3a — Aadhaar by DigiLocker (recommended)

POST/partner/users/:user_code/kyc/aadhaar/digilockerkyc

DigiLocker is the Government of India's document wallet. The user signs in there (mobile number or Aadhaar, plus an OTP from DigiLocker) and taps Allow to share their Aadhaar with OroPocket. The PAN step must be done first.

FieldTypeDescription
returnUrlstringoptionalWhere to send the user's browser afterwards. https, on an origin you registered (see below).
FieldTypeDescription
sessionIdstringKeep it — you read the outcome with it.
urlstringAn OroPocket link (https://api.oropocket.com/kyc/digilocker/go/…) that forwards to DigiLocker. Open it for the user.
expiresAtstringISO time. The link is valid for about 15 minutes.

Calling it again within about 12 minutes returns the same session, so a double tap doesn't start two.

Opening the url. Use one of these:

WhereHow
Phone browserHand the url to the system browser. Simplest; your returnUrl brings the user back.
In-app browserChrome Custom Tabs on Android, SFSafariViewController on iOS. Recommended for apps.
Full-screen WebViewLoad the url as the top-level page of a WebView, with JavaScript and DOM storage on.
Web appNavigate the same tab (window.location.assign(url)) or open a new tab.
Never put it in an iframe. DigiLocker refuses to be framed, so the user sees a blank box.

returnUrl. When the user finishes, the browser goes to your returnUrl with ?session_id=<id>&status=<status> appended. Treat status there as a hint only — confirm with the GET below or the webhook before you show success. The origin must be listed in your panel under Payments → Checkout → Return origins (the same list payment returns use; if you don't see Payments in your panel, ask your OroPocket contact to add the origin). Without a returnUrl, the user sees "DigiLocker step complete — go back to the app", which suits an in-app browser you close yourself.

GET/partner/users/:user_code/kyc/aadhaar/digilocker/:session_idkyc
FieldTypeDescription
sessionIdstringThe session.
statusstringSee the table below.
failureCodestring | nullWhy the session ended badly — see the codes below. null while pending or after success.
kycStatusstring | nullThe user's overall KYC status once the Aadhaar was checked; null while pending, and when the session ended before that (denied, expired, a DigiLocker failure).
statusWhat to do
pendingThe user hasn't finished yet. Keep waiting.
verifiedAadhaar verified and KYC approved. Done.
reviewAadhaar verified; a person checks the rest. Show “in review” and wait for kyc.approved or kyc.rejected.
deniedThe user didn't tap Allow. Start a new session if they want to try again.
expiredNot finished in time. Start a new session.
not_linkedThat DigiLocker account has no Aadhaar. Offer Aadhaar photos (Stage 3b).
failedSee failureCode. provider_unavailable: try again later or use photos. under_18 and duplicate_aadhaar can't be fixed by retrying.
failureCodeMeaning
consent_deniedWith status denied: the user didn't tap Allow.
link_expiredWith status expired: the user didn't finish in time.
never_startedWith status expired: the link was never opened.
status_unavailableWith status expired: we couldn't confirm the result before the session ran out. Start again.
aadhaar_not_linkedWith status not_linked: no Aadhaar in that DigiLocker account. Use photos.
document_unreadableThe Aadhaar from DigiLocker couldn't be read. Start again or use photos.
provider_unavailableDigiLocker or our verification service was down. Try later or use photos.
provider_failedDigiLocker reported a failure. Start again or use photos.
under_18The Aadhaar shows the user is under 18. KYC can't continue.
duplicate_aadhaarThat Aadhaar already belongs to another OroPocket account.

Polling or webhooks? Webhooks are the reliable signal: kyc.approved or kyc.under_review fires when the session ends well. If you want an immediate answer when the user lands on your returnUrl (or closes the in-app browser), call the GET once, then every 3 seconds or so while it says pending — and stop after a couple of minutes.

# 1) start a session
curl -X POST https://api.oropocket.com/partner/users/usr_xxx/kyc/aadhaar/digilocker \
  -H "Authorization: Bearer oro_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{"returnUrl":"https://app.example.com/kyc/done"}'

# 2) after the user comes back, read the outcome
curl https://api.oropocket.com/partner/users/usr_xxx/kyc/aadhaar/digilocker/9b2c4e7a1f0d5c38e6a4b1d27f90c3e5 \
  -H "Authorization: Bearer oro_live_xxx"
Error codeMeaning
400 PAN_REQUIREDDo Stage 2 first.
400 INVALID_RETURN_URLreturnUrl isn't a valid https URL.
400 RETURN_URL_NOT_ALLOWEDreturnUrl's origin isn't in Payments → Checkout → Return origins.
403 DIGILOCKER_OFFDigiLocker is switched off for your account. Use Aadhaar photos.
409 ALREADY_VERIFIED · ALREADY_APPROVED · UNDER_REVIEWAadhaar is already verified, the KYC is approved, or it's with our review.
429 RATE_LIMITED10 DigiLocker opens per user per hour. Offer photos, or try later.
503 PROVIDER_UNAVAILABLEThe verification service is briefly unavailable. Retry later, or use Aadhaar photos.
404 SESSION_NOT_FOUNDGET only: that session id doesn't exist for this user.

Stage 3b — Aadhaar photos

POST/partner/users/:user_code/kyc/aadhaar/photoskyc

For users without DigiLocker, or when it isn't working. The KYC goes to OroPocket's review (usually within 24–48 h) and you hear back by kyc.approved or kyc.rejected.

FieldTypeDescription
aadhaarNumberstringrequired12 digits.
frontstringrequiredFront of the Aadhaar card: base64 JPEG, PNG or PDF, up to 4 MB.
backstringrequiredBack of the Aadhaar card, same rules.
FieldTypeDescription
kycStatusstring"under_review".
aadhaarMethodstring"photos".
Images are base64 — the raw base64 of the file, or the same with a data:image/jpeg;base64, prefix. JPEG, PNG or PDF, up to 4 MB each before encoding. Remember base64 grows the body by about a third.
curl -X POST https://api.oropocket.com/partner/users/usr_xxx/kyc/aadhaar/photos \
  -H "Authorization: Bearer oro_live_xxx" \
  -H "Content-Type: application/json" \
  -d "{\"aadhaarNumber\":\"234567890124\",\"front\":\"$(base64 -w0 front.jpg)\",\"back\":\"$(base64 -w0 back.jpg)\"}"
Error codeMeaning
400 PAN_REQUIREDDo Stage 2 first.
400 INVALID_INPUTerror.details.field is aadhaarNumber: not a valid 12-digit number, or already registered to another account.
400 INVALID_DOCUMENT · DOCUMENT_TOO_LARGEAn image isn't a readable JPEG, PNG or PDF, or is over 4 MB.
409 UNDER_REVIEW · ALREADY_APPROVEDAlready with our review, or already approved.

After a rejection

kyc.rejected carries the reviewer's message in data.reason (also in rejectionReason on the status). The user can start again: read the status and follow v2.nextStep. Webhook payloads and the kyc.under_review reason codes are on Webhooks.

The older manual path

POST kyc/details (fullName, aadharNumber, panNumber, dateOfBirth, optional address) followed by POST kyc/documents (publicly fetchable panImageUrl, aadharFrontImageUrl, aadharBackImageUrl) still works and ends in review, but it is no longer recommended — use the PAN and Aadhaar steps above. If you already hold a user's verified PAN and Aadhaar record, see Submit collected KYC.

Testing it

With an oro_test_ key nothing real is checked and no real documents are needed.

Test-mode valueResult
Email code 123456Verifies.
PAN ABCDE1234FVerifies. Any other well-formed PAN verifies too; date of birth and name aren't checked.
PAN ZZZZZ9999ZRejected with 400 KYC_REJECTED — use it to test your failure path.
kyc/pan/photoAlways accepted in test mode, so you can build the fallback screen.
kyc/aadhaar/digilockerReturns a url to a DigiLocker simulator (https://api.oropocket.com/partner/sandbox/digilocker/…) with Allow and Deny. Allow approves the KYC and sends kyc.approved; Deny leaves the session denied. Both redirect to your returnUrl, as live does.
kyc/aadhaar/photosAny 12-digit number starting 2–9 and any base64 front and back. Goes to under_review (kyc.under_review), then approves itself about 30 seconds later (kyc.approved).
kyc/aadhaar/otp/*410 AADHAAR_OTP_RETIRED, as in live. There is no Aadhaar OTP magic value.

Webhooks fire in test mode too, with data.mode: "sandbox". Try every call from the Playground; more on test mode in Sandbox.