Extract Aadhaar Data with n8n: Gmail to Sheets (Masked UID Handling)
n8n: Gmail attachments → Cryvis POST /v1/documents/aadhaar with one or two file parts → IF is_masked → Google Sheets with PII controls.
Aadhaar shows up in inboxes as KYC threads with one or two JPEG attachments. This n8n workflow watches Gmail, sends attachments to Cryvis POST /v1/documents/aadhaar, and writes structured fields to Google Sheets — with explicit handling for is_masked and PII hygiene.
Passport (two differently named parts): Extract passport data. HTTP building block: HTTP Request OCR. Hub: /blog/n8n.
Why is_masked is first-class
Cryvis returns whatever is printed: full UID or masked (e.g. XXXX XXXX 9012) plus is_masked: true|false. Treat that flag as a control for storage policy, not an afterthought. This is engineering guidance, not legal advice — confirm retention with counsel under applicable UIDAI / DPDP rules.
Architecture
Gmail label: kyc-aadhaar
|
v
Gmail Trigger → attachment binaries
|
v
IF MIME image/pdf; cap at 2 files
|
v
HTTP Request
POST https://api.cryvis.com/v1/documents/aadhaar
Form-Data: file [, file]
Authorization: Bearer ...
|
v
IF {{ $json.success }}
→ Switch on {{ $json.data.is_masked }}
→ Google Sheets AppendStep 1: Gmail intake
- Label
kyc-aadhaarvia Gmail filter (attachment + subject keywords). - Gmail Trigger filtered by that label.
- Instruct senders: front only, back only, or front+back. Cryvis accepts 1–2 files, both named
file.
Step 2: Collect binaries
Loop attachments; IF MIME ∈ image/jpeg, image/png, image/webp, application/pdf.
Cap at two. If more arrive, take the first two and Set a note extra_attachments_ignored.
When you need two parts on one item for HTTP Request:
$binary.data → first attachment
$binary.data_back → second attachment (optional)Use Move Binary Data / Code so the second file is not overwritten if both downloads default to data.
Step 3: Cryvis Aadhaar HTTP Request
| Setting | Value |
|---|---|
| Method | POST |
| URL | https://api.cryvis.com/v1/documents/aadhaar |
| Auth | Authorization: Bearer <API_KEY> |
| Body Content Type | Form-Data |
| Name | Type | Input Data Field Name | When |
|---|---|---|---|
file | File | data | Always |
file | File | data_back | If second attachment exists |
Both parts must be named file — not front/back (those are Indian Vehicle RC) and not first_page/last_page (passport).
cURL equivalent:
curl -X POST https://api.cryvis.com/v1/documents/aadhaar \
-H "Authorization: Bearer YOUR_API_KEY" \
-F "file=@aadhaar-front.jpg" \
-F "file=@aadhaar-back.jpg"Docs: extractAadhaar · Aadhaar.
Step 4: Response schema
{
"success": true,
"data": {
"aadhaar_number": "1234 5678 9012",
"full_name": "RAHUL SHARMA",
"date_of_birth": "1992-08-14",
"year_of_birth": null,
"gender": "Male",
"care_of": "S/O SURESH SHARMA",
"address": "12 MG Road, Bengaluru, Karnataka 560001",
"pincode": "560001",
"is_masked": false
},
"meta": {}
}| Field | Notes |
|---|---|
aadhaar_number | Full or masked string as printed |
date_of_birth | May be null on YOB-only cards |
year_of_birth | Use when DOB absent |
gender | Male / Female / Transgender |
care_of | Guardian line when present |
address / pincode | Often from back image |
is_masked | Boolean — drives storage policy |
Expressions: {{ $json.data.full_name }}, {{ $json.data.is_masked }}, etc.
Step 5: Sheets columns
| Column | Access |
|---|---|
| Email / Thread ID | Ops |
| Full name | Ops |
| DOB or YOB | Ops |
| Gender | Ops |
| Care of | Ops |
| Address | Ops |
| Pincode | Ops |
| Aadhaar number | Restricted — KYC role only, or last-4 only |
| Is masked | Ops |
| KYC status | Ops |
| Gmail link | Ops |
| Processed at | Ops |
Status values: extracted_full, extracted_masked, needs_resubmit, error.
Step 6: Switch on is_masked
Set node fields:
name = {{ $json.data.full_name }}
dob_or_yob = {{ $json.data.date_of_birth || $json.data.year_of_birth }}
gender = {{ $json.data.gender }}
aadhaar = {{ $json.data.aadhaar_number }}
is_masked = {{ $json.data.is_masked }}Switch on {{ $json.data.is_masked }}:
| Policy | Masked branch | Unmasked branch |
|---|---|---|
| Accept masked for address proof | Status extracted_masked (terminal) | extracted_full |
| Require full UID | Status needs_resubmit + Gmail reply | extracted_full |
| Never store full UID | Write last 4 only via Code/expression | Same |
Pick one policy and document it next to the workflow. Do not log full Aadhaar in Slack.
Step 7: Optional Gmail reply
Gmail Send: success acknowledgment without echoing aadhaar_number. Masked + require-full policy → ask for unmasked resubmit only if your legal basis allows.
Step 8: Errors and retention
HTTP Cryvis
|
+-- success → Sheets → optional Gmail reply
|
+-- failure → Sheets status=error
→ Slack: message id + HTTP status onlyEnable Retry on Fail for 5xx only. Error Trigger for auth/node crashes.
Self-hosted / Cloud: execution history may retain attachment binaries. Shorten retention for Aadhaar workflows; archive Gmail threads per your schedule outside n8n.
Testing
- Front+back unmasked →
is_masked=false, address filled,extracted_full. - Masked front only →
is_masked=true, status per policy. - YOB-only card → DOB null, YOB set; Sheets uses YOB.
- Three attachments → cap at 2; note field set.
Why two parts share the name file
OpenAPI defines Aadhaar uploads as 1–2 binaries under file. Different from RC (front/back) and passport (first_page/last_page). In n8n Form-Data, add two rows both named file with different Input Data Field Names — do not rename the second part to file2.
Credits
1 credit per image. Front+back = two credits. Prefer that over one blurry combined photo — Credits.
Related
Sign up · Pricing · Aadhaar · extractAadhaar. POST one or two multipart parts named file to https://api.cryvis.com/v1/documents/aadhaar, then Switch on {{ $json.data.is_masked }} before writing Sheets.