Extract Passport Data with Make.com and Cryvis
Typeform passport uploads to Cryvis POST /v1/documents/passport with first_page and last_page, then map biodata and MRZ fields into HubSpot.
Passport KYC fails in automation when the HTTP module sends the wrong multipart shape. Cryvis expects two parts — first_page (biodata) and last_page (final page) — not a single file. This tutorial builds a Make.com scenario from Typeform through Cryvis passport extraction into HubSpot contact properties, including MRZ check-digit handling.
Broader multi-doc KYC: Automate KYC in Make.com. Webhook sequential checks: No-code KYC workflow.
Architecture
Typeform
- email
- passport biodata upload
- passport final-page upload
|
v
Make.com
HTTP Get files
|
v
POST https://api.cryvis.com/v1/documents/passport
multipart: first_page + last_page
header: Authorization: Bearer ...
|
v
HubSpot Contact
name, DOB, passport #, expiry, MRZ flagPrerequisites
- Cryvis API key with access to passport extraction
- Make.com account
- Typeform with two file uploads
- HubSpot private app / connection with permission to create contacts and custom properties
Step 1: Typeform fields
| Question | Type | Notes |
|---|---|---|
| Work email | HubSpot match key | |
| Passport biodata page | File | Photo page with MRZ usually on the same or facing page depending on country booklet layout — still send as first_page |
| Passport final / back page | File | Maps to last_page |
| Consent | Yes/No | Store for compliance; not sent to Cryvis |
Copy text in Typeform: “Upload a clear photo or PDF of the biodata page and the final page. Both are required.”
Step 2: Watch Typeform in Make
Typeform → Watch Responses. Map email and both file answers. If Typeform exposes URLs, chain HTTP → Get a file twice:
first_page_bin = download(biodata_url)
last_page_bin = download(final_url)Add a Filter before Cryvis: both binaries must exist. Passport calls with only one part return a client error.
Step 3: Call Cryvis Passport API
HTTP → Make a request
| Parameter | Value |
|---|---|
| URL | https://api.cryvis.com/v1/documents/passport |
| Method | POST |
| Headers | Authorization: Bearer {{cryvis_api_key}} |
| Body type | Multipart/form-data |
| Multipart field | Maps to |
|---|---|
first_page | first_page_bin (required) |
last_page | last_page_bin (required) |
Supported MIME types: application/pdf, image/jpeg, image/png, image/webp.
Wrong vs right
# Wrong — will not match the OpenAPI contract
-F "file=@passport.jpg"
# Right
-F "first_page=@biodata.jpg" \
-F "last_page=@final.jpg"Step 4: Read the response
On success:
{
"success": true,
"data": {
"full_name": "JOHN DOE",
"given_name": "JOHN",
"surname": "DOE",
"date_of_birth": "1995-04-12",
"nationality": "IND",
"passport_number": "P1234567",
"issue_date": "2020-04-12",
"expiry_date": "2030-04-12",
"issuing_country": "IND",
"sex": "M",
"mrz": {
"line1": "P<INDDOE<<JOHN<<<<<<<<<<<<<<<<<<<<<<<<<<<<",
"line2": "P1234567<3IND9504124M3004123<<<<<<<<<<<<<<<4",
"check_digits_valid": true
}
}
}In Make, parse JSON (HTTP module can parse automatically). Bind:
| Make variable | JSON path |
|---|---|
full_name | data.full_name |
given_name | data.given_name |
surname | data.surname |
date_of_birth | data.date_of_birth |
nationality | data.nationality |
passport_number | data.passport_number |
issue_date | data.issue_date |
expiry_date | data.expiry_date |
issuing_country | data.issuing_country |
sex | data.sex |
mrz_line1 | data.mrz.line1 |
mrz_line2 | data.mrz.line2 |
mrz_ok | data.mrz.check_digits_valid |
Nullability: fields can be null if OCR cannot read a value. Guard HubSpot updates with IFEMPTY so you do not overwrite good CRM data with blanks on a bad scan.
Official docs: /docs/api/extractPassport. API overview: /apis/passport.
Step 5: HubSpot contact mapping
Create custom properties (once):
passport_number(single-line)passport_nationality(single-line)passport_issue_date(date)passport_expiry_date(date)passport_issuing_country(single-line)passport_mrz_valid(boolean)kyc_status(dropdown)
HubSpot → Create/Update a Contact keyed by email:
| HubSpot | Value |
|---|---|
| Typeform email | |
| First name | given_name |
| Last name | surname |
| Date of birth | date_of_birth (if you use HubSpot DOB) |
passport_number | passport_number |
passport_nationality | nationality |
passport_issue_date | issue_date |
passport_expiry_date | expiry_date |
passport_issuing_country | issuing_country |
passport_mrz_valid | mrz_ok |
kyc_status | passport_extracted or passport_mrz_review |
Status rule:
IF mrz_ok = true
THEN kyc_status = passport_extracted
ELSE kyc_status = passport_mrz_reviewOptional: create a HubSpot task for ops when mrz_ok is false. Do not auto-approve expired passports — compare expiry_date to now in a Filter if your policy requires a future expiry.
Step 6: Expiry gate (optional)
After Cryvis:
Filter: parseDate(expiry_date) > addDays(now; 30)
YES -> kyc_status = passport_extracted
NO -> kyc_status = passport_expiring_or_expiredThis is Make-side logic; Cryvis returns the date string, it does not enforce your business rule.
Step 7: Errors and retries
Attach an Error handler to the HTTP module:
| Error | Action |
|---|---|
| 401 | Alert “Cryvis auth failed”; stop |
| 400 | Alert “multipart missing first_page/last_page or bad image”; ask applicant to resubmit via Typeform |
| 5xx | Enable Make retry (2–3 attempts) then ops Slack |
Never retry endlessly on 400 — fix the payload.
Testing checklist
- Submit two clear JPEG pages → expect
success: trueand non-nullpassport_number. - Submit only biodata → expect failure; confirm Filter catches it before HTTP.
- Blurry scan → possible nulls; confirm HubSpot does not clear existing properties.
- MRZ-tampered fixture (if you have one) →
check_digits_valid: false→ HubSpot review status.
Credit note
Passport extraction is metered per image (see pricing on /apis/passport). Two pages means two image credits for a typical dual-image call — plan Typeform volume accordingly.
Make.com mapping pitfalls
- Mapping Typeform file URL into multipart as Text — Cryvis needs the downloaded binary. Always Get a file first.
- Swapping first_page and last_page — biodata must be
first_page. Swapped pages degrade fields and MRZ. - Reading
passport_numberinstead ofdata.passport_number— response wrapper includessuccessanddata. - Overwriting HubSpot DOB with null — use
IFEMPTYguards on every mapped property.
Storing MRZ lines
Most CRMs do not need mrz.line1 / line2 on the contact. Prefer:
- Contact:
passport_mrz_validboolean - Private Data store or secure vault: raw MRZ lines if fraud review needs them
That keeps HubSpot layouts clean and reduces PII sprawl.
Resubmission loop
When kyc_status = passport_mrz_review or expiry fails:
- HubSpot workflow sends Typeform partial-submit link
- New Watch Responses run creates a fresh Cryvis call
- Update contact; set
kyc_status = passport_extractedonly whenmrz_okand expiry policy pass
Keep the previous passport number in a passport_number_previous property for audit when the number changes (reissue).
Related reading
- Automate full KYC routing
- Extract PAN card data
- Extract Aadhaar data
- Make.com category: /blog/make-com
CTA
Extract passport biodata and MRZ with Cryvis: /apis/passport · Docs: /docs/api/extractPassport. In Make, POST multipart first_page + last_page to https://api.cryvis.com/v1/documents/passport with Authorization: Bearer.