← Back to blog

Automate KYC in Make.com: Typeform to Passport, PAN, Aadhaar, and HubSpot

Build a Make.com KYC scenario that routes Typeform multi-file uploads to Cryvis Passport, PAN, and Aadhaar APIs, then writes contact fields and KYC status to HubSpot.

Manual KYC review does not scale. Applicants upload mixed ID packs, ops teams rename files, and CRM fields stay empty until someone types them in. This tutorial builds a Make.com scenario that takes a Typeform multi-file submission, routes each document by type, calls the matching Cryvis extraction endpoint, and updates a HubSpot contact with structured identity data plus a KYC status.

You will wire:

  • Typeform as the intake form
  • A Router that branches on document type
  • Cryvis POST /v1/documents/passport, /pan, and /aadhaar
  • HubSpot contact create/update with KYC status

If you only need one document type, start with the focused guides for passport, PAN, or Aadhaar. For a webhook-first sequential pipeline, see no-code KYC workflow.

Architecture

Typeform (multi-file KYC)
        |
        v
   Make.com scenario
        |
   +----+----+----+
   |         |    |
   v         v    v
Passport   PAN  Aadhaar
 (Cryvis) (Cryvis) (Cryvis)
   |         |    |
   +----+----+----+
        |
        v
  HubSpot Contact
  + KYC status

Every Cryvis call uses:

POST https://api.cryvis.com/v1/documents/<type>
Authorization: Bearer YOUR_API_KEY
Content-Type: multipart/form-data

Create an API key in the Cryvis dashboard and store it in a Make.com connection or a scenario variable. Do not hard-code keys in shared blueprints.

Step 1: Typeform multi-file intake

Build a Typeform with:

FieldTypePurpose
EmailEmailHubSpot contact key
Full nameShort textFallback display name
Document typeMultiple choicepassport / pan / aadhaar
ID uploadFile upload (multiple)Document images

For passport flows, instruct applicants to upload biodata page and final page as two files. Cryvis passport extraction requires both first_page and last_page — not a single file field. For Aadhaar, allow one or two images (front and/or back) under the same logical upload; the API accepts 1–2 files named file.

In Make.com, add Typeform → Watch Responses (or Get a Response if you prefer webhook delivery from Typeform). Map:

  • email
  • full_name
  • document_type
  • file URL(s) from the upload answer

Use an HTTP → Get a File (or Typeform download helper) module so each upload becomes binary data Make can attach in multipart requests.

Step 2: Normalize files before the Router

Passport needs two distinct parts. PAN needs one. Aadhaar needs one or two under the same field name.

Add a short Set Variables (or Tools → Set multiple variables) block:

doc_type          = {{document_type}}   # lowercase: passport | pan | aadhaar
email             = {{email}}
applicant_name    = {{full_name}}
file_1            = first uploaded binary
file_2            = second uploaded binary (optional)

If Typeform returns files as an array, map index 0 → file_1 and index 1 → file_2. Fail early with a Filter if doc_type is passport and file_2 is empty — passport will reject a single-page call.

Step 3: Router by document type

Add a Router with three routes. Each route Filter condition:

RouteFilter
Passportdoc_type Equal to passport
PANdoc_type Equal to pan
Aadhaardoc_type Equal to aadhaar

Optional fourth route: fallback Slack/email when doc_type is missing or unknown.

Step 4: Cryvis Passport branch

Module: HTTP → Make a request

SettingValue
URLhttps://api.cryvis.com/v1/documents/passport
MethodPOST
Body typeMultipart/form-data
Auth headerAuthorization: Bearer {{cryvis_api_key}}

Multipart fields (exact names):

FieldValue
first_pagefile_1 (biodata page) — required
last_pagefile_2 (final page) — required

Successful responses wrap fields under data:

{
  "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
    }
  }
}

Map HubSpot-bound variables from this branch:

kyc_doc_type        = passport
kyc_id_number       = data.passport_number
kyc_full_name       = data.full_name
kyc_dob             = data.date_of_birth
kyc_nationality     = data.nationality
kyc_expiry          = data.expiry_date
mrz_valid           = data.mrz.check_digits_valid
kyc_status          = IF(mrz_valid; "extracted_ok"; "review_mrz")

Treat check_digits_valid: false as review, not silent pass. API docs: /docs/api/extractPassport. Product page: /apis/passport.

Step 5: Cryvis PAN branch

SettingValue
URLhttps://api.cryvis.com/v1/documents/pan
Multipartfile = file_1 (required)

Response data includes:

  • pan_number
  • full_name
  • father_name
  • date_of_birth
  • holder_type_code / holder_type
  • pan_structure (series, holder_type_code, name_initial, sequence, check_digit)

Map:

kyc_doc_type    = pan
kyc_id_number   = data.pan_number
kyc_full_name   = data.full_name
kyc_dob         = data.date_of_birth
kyc_father      = data.father_name
holder_type     = data.holder_type
kyc_status      = IF(EMPTY(data.pan_number); "review_missing_pan"; "extracted_ok")

Docs: /docs/api/extractPan · /apis/pan.

Step 6: Cryvis Aadhaar branch (PII-aware)

SettingValue
URLhttps://api.cryvis.com/v1/documents/aadhaar
Multipartone or two parts named file

In Make.com, add two multipart rows both named file when front and back exist. Omit the second row when only one image was uploaded.

Response data:

  • aadhaar_number
  • full_name
  • date_of_birth / year_of_birth
  • gender
  • care_of
  • address
  • pincode
  • is_masked

PII handling matters here. Aadhaar numbers are sensitive. In HubSpot:

  1. Prefer storing a masked display value when is_masked is true (e.g. XXXX-XXXX-9012).
  2. Restrict HubSpot property visibility to KYC-capable roles.
  3. Avoid logging full aadhaar_number in Make.com execution history beyond what compliance allows — use scenario data retention settings accordingly.
  4. If is_masked is true and your policy requires a full UID for verification, set kyc_status to needs_unmasked_resubmit instead of marking complete.

Example mapping:

kyc_doc_type     = aadhaar
kyc_id_number    = data.aadhaar_number
kyc_full_name    = data.full_name
kyc_dob          = IFEMPTY(data.date_of_birth; data.year_of_birth)
kyc_address      = data.address
kyc_pincode      = data.pincode
is_masked        = data.is_masked
kyc_status       = IF(is_masked; "extracted_masked"; "extracted_ok")

Docs: /docs/api/extractAadhaar · /apis/aadhaar.

Step 7: Converge into HubSpot

After each Cryvis branch, use the same HubSpot module so all routes write a consistent contact shape.

HubSpot → Create/Update a Contact (match on email):

HubSpot propertySource
Emailemail
First name / Last namesplit kyc_full_name or Typeform name
Custom: kyc_document_typekyc_doc_type
Custom: kyc_id_numberkyc_id_number
Custom: kyc_date_of_birthkyc_dob
Custom: kyc_statuskyc_status
Custom: kyc_extracted_atnow

Create those custom properties in HubSpot before go-live. For passport, optionally store kyc_nationality and kyc_expiry. For PAN, store holder_type. For Aadhaar, store kyc_pincode and a boolean aadhaar_is_masked.

Suggested status vocabulary:

StatusMeaning
extracted_okFields present; ready for downstream checks
extracted_maskedAadhaar returned masked UID
review_mrzPassport MRZ check digits failed
review_missing_panPAN number null
needs_unmasked_resubmitPolicy requires full Aadhaar
errorHTTP non-2xx from Cryvis

Step 8: Error handling

Wrap each HTTP module with Make's Error handler:

Cryvis HTTP
   |
   +-- Success --> HubSpot
   |
   +-- Error handler
         |
         +-- Set kyc_status = error
         +-- HubSpot update (status only)
         +-- Slack/email ops alert

Common failures:

  • 400 — wrong multipart field names (especially passport first_page/last_page)
  • 401 — bad or missing Bearer token
  • 413 / unsupported type — wrong MIME; Cryvis accepts PDF, JPEG, PNG, WebP

Log statusCode and a short error body in the ops channel, not the full document.

End-to-end checklist

  1. Typeform returns email, doc type, and 1–2 file URLs.
  2. Passport route always sends both first_page and last_page.
  3. PAN route sends a single file.
  4. Aadhaar route sends 1–2 parts named file; HubSpot respects is_masked.
  5. All routes set kyc_status and upsert HubSpot by email.
  6. Error handler never silently drops failed extractions.

CTA

Ship the multi-doc KYC router with Cryvis extraction APIs: Passport, PAN, and Aadhaar. Grab an API key, paste it into Make as a Bearer token, and map data.* fields straight into HubSpot.