Lolo Cab: DigiLocker KYC is a status machine, not a file upload
Backend notes from Lolo Cab — provider-backed KYC for drivers and vendors, what never goes to the client, and why admin gates on verification state.
Lolo Cab was a rebuild for Triptota: three Flutter apps (riders, drivers, vendors), a React admin console, and one Node backend. Booking moved off phone calls. Drivers and vendors still had to prove who they were before they could operate.
This log is the KYC path only — DigiLocker / provider verification, status, and what we refused to put on the wire.
KYC is not “upload a selfie”
The naive version is: client uploads a PDF or photo, admin squints at it, someone flips a checkbox. That does not scale, and it does not prove the document is real.
We wired verification through a provider (Cashfree’s verification APIs, DigiLocker-backed where the product needed it). The backend took the identifiers a role was allowed to submit — driving license + DOB, PAN, bank account + IFSC, and so on — called the provider server-side, and wrote a result.
What the product actually cares about is a status, not a file:
pending → verified | failed / rejected
Admin filters and ops gates hang off that status. A driver or vendor who is still pending does not get treated like someone who cleared KYC. A rejection stores a reason so support can explain the next step — not a vague “try again.”
Fix: treat KYC as a state machine owned by the backend. Uploads and image URLs can exist for audit. They are not the source of truth for “allowed to go live.”
What the client must never see
Three Flutter apps and an admin UI all hit the same API. That makes it tempting to return the provider’s full response “for debugging.”
Don’t.
Provider credentials, request signatures, and raw identity payloads stay on the server. Clients get a success/failure shape plus the fields the UI needs to show progress — not Aadhaar dumps, not bank account full responses, not the signing material used to talk to the KYC API.
// illustrative — persist outcome, not the whole provider blob
await Doc.updateOne(
{ userId, documentType },
{
$set: {
isVerified: result.ok,
verifiedAt: result.ok ? new Date() : null,
rejectionReason: result.ok ? null : result.reason,
},
},
);
Fix: the mobile apps submit identifiers. The server talks to DigiLocker / the verification provider. The apps and admin read status, not secrets.
Same pipeline, different required docs
Drivers and vendors share the verification service. They do not share the same checklist.
- Drivers lean on driving license (and related identity checks).
- Vendors need a wider set — PAN, Aadhaar-related docs, GST / shop license, voter ID variants, bank proof for payouts.
One KYC module, role-specific required documentTypes, unique per vendor/doc so you do not get three competing “PAN” rows. Admin approval for a profile still waits until the required set is verified — or an admin explicitly rejects with a reason.
Fix: one verification client in the backend. Per-role document requirements in data, not copy-pasted endpoints per Flutter app.
Takeaways
- KYC for a multi-sided ride platform is a status machine, not a file picker.
- Never put provider credentials or full identity payloads in client responses.
- Drivers and vendors can share the same verification service with different required docs.
- Admin should gate ops on
pending/verified/rejected— squinting at uploads does not scale.
More Lolo notes later if we cover payment webhooks or shared-ride seat locking. The private setup guides stay in the project notes — this log is the lessons, not the inventory.
Related
Author

Fawad Naeem
Full-stack developer in Lahore building React Native apps, Astro sites, and AWS-backed products. This log is where the shipping notes live.
View portfolio ↗Hire
Working on something?
If you want help shipping a mobile or web product, email me — or see past work on the portfolio.
Newsletter
Occasional shipping notes
No spam — just new build logs when they ship. Form is ready; provider wiring comes later.