---
title: KYC Magic Link
description: Executes the identification process in Mexico, collecting national identity documents (INE) and facial biometrics via a hosted journey distributed by a unique link.
canonical: https://developer.unico.io/dual-api/products/sign-up/kyc-magic-link/index
locale: en
generated_by: markdown-export
---

- [/](/)
- [Build](/dual-api/products/)
- Regional Guide
- KYC Magic Link

**On this page# KYC Magic Link

Separate regional contractThis use case **does not use** the `POST /v1/process` Unico endpoint nor the API contract. The integration is with **Trully.ai** (host `api.trully.ai`), with **authentication via `x-api-key`** instead of Bearer JWT, and a **proprietary response schema**. For other countries, use Onboarding (Global).
### What this use case solves​

KYC Magic Link addresses the challenge of executing the identification process in Mexico, collecting national identity documents (INE) and facial biometrics. With a journey hosted by Unico, you eliminate front-end development friction through a link sent via your own channels (WhatsApp, SMS, email).
**Use this use case when:**

You operate in **Mexico** and the identity document used is the **INE (mandatory)**.

**Do not use this use case when:**

The user is outside Mexico or uses other documents → look at the other onboarding use cases.

### Capabilities involved​

Pipeline executed within a single process:
[Document Capture (INE)](/capabilities/document-reuse-and-capture)[Liveness](/capabilities/liveness)[Fraud Risk Classification](/capabilities/fraud-risk-classification)[Identity Verification](/capabilities/identity-verification)
CapabilityRequiredRole in the flow**Document Capture**RequiredCaptures the INE document image. Document reuse is **not available** in this use case — each session requires a new capture.**Liveness**RequiredLiveness check — mandatory selfie that anchors the process.**Fraud Risk Classification**RequiredCross-references behavioral signals to flag fraud risk associated with the CPF.**Identity Verification**Optional (if contracted)Verifies whether the transaction face belongs to the holder of the provided government identifier, using Unico's identity base and additional signals.**[RENAPO Verification](/capabilities/renapo-verification)**Optional (if contracted)Queries RENAPO, Mexico's national population registry, with the user's CURP and returns the registry record alongside the Identity Verification result.
### Prerequisites​

**API key** — provisioned by your Onboarding Project Manager at Unico. Sent in the `x-api-key` header.
**Public HTTPS endpoint** to receive webhooks (optional, but recommended).
**CORS configuration** — allow origins `https://verification.unico.app` (production) and `https://verification.uat.unico.app` (sandbox) on the server that receives the webhook.

### Step-by-step implementation​

Unlike other use cases, Magic Link does **not have a `flow` field** — the integration is direct with the Trully API. The endpoint creates a unique verification link that you distribute to the user through your own channel.
### 1. Create the Magic Link​

**Endpoint:** `POST https://sandbox.trully.ai/v2/magic-link`
**Headers:**
HeaderRequiredDescription`x-api-key`YesAPI key provisioned by your Onboarding Project Manager at Unico.`Content-Type`Yes`application/json`
**Body (application/json):**
FieldTypeRequiredDescription`external_id`stringNoAn external identifier for the request, used for tracking and reference purposes.`metadata`objectNoContainer for configuration parameters.`metadata.phone`stringNoUser's phone number in international format (e.g., `521234567890`).`metadata.redirect_url`stringNoURL to redirect the user after the KYC process is completed.`metadata.webhook_url`stringNoURL to send the KYC process data after the KYC process is completed. This webhook will be called in the client side. For security reasons the endpoint should use HTTPS. The component will wait one minute for your webhook server to respond — after that, it will drop the communication. The process won't be affected in any way by webhook communication. Make sure to allow `https://verification.unico.app` and `https://verification.uat.unico.app` in your CORS configuration for production and sandbox respectively.`metadata.track_webhook_url`stringNoURL to send each KYC step processed by the user (see webhook events below). This webhook will be called in the client side. For security reasons the endpoint should use HTTPS. The component will wait one minute for your webhook server to respond — after that, it will drop the communication. The process won't be affected in any way by webhook communication. Make sure to allow `https://verification.unico.app` and `https://verification.uat.unico.app` in your CORS configuration for production and sandbox respectively.
`data.step` (sent to `track_webhook_url`) reports the same steps tracked by Webhook V2 — see the [Supported steps table](#webhook-v2) below.
**Example request:**
```
curl -X POST https://sandbox.trully.ai/v2/magic-link \  -H "x-api-key: $TRULLY_API_KEY" \  -H "Content-Type: application/json" \  -d '{    "external_id": "user-mx-12345",    "metadata": {      "phone": "521234567890",      "redirect_url": "https://app.client.com.mx/kyc-done",      "webhook_url": "https://app.client.com.mx/webhook/result",      "track_webhook_url": "https://app.client.com.mx/webhook/track"    }  }'
```

### 2. Receive the token and link URL​

**Response fields:**
FieldTypeDescription`data.external_id`string (nullable)An external identifier for the request, used for tracking and reference purposes.`data.created_on`string (date-time)The date when the magic link was created.`data.is_active`booleanIf `true`, the magic link is active.`data.token`stringThe token of the magic link which will be used to link the KYC processes to the magic link.`data.magic_link_url`string (URI)The magic URL link to start the KYC process.`data.metadata.redirect_url`string (nullable)URL to redirect the user after the KYC process is completed.`data.metadata.webhook_url`string (nullable)URL to send the KYC process data after the KYC process is completed. This webhook will be called in the client side.`data.metadata.track_webhook_url`string (nullable)URL to send each KYC step processed by the user. This webhook will be called in the client side.`data.version`stringThe version of the magic link.`version`stringThe version of the API that processed the request, useful for tracking changes and compatibility.`status`stringA textual representation of the response status, indicating success or failure of the operation.`status_code`integerThe HTTP status code of the response, providing a standardized indicator of the request's outcome.`request_date`string (date-time)The date and time when the request was made, in ISO 8601 format.`request.metadata.redirect_url`string (nullable)URL to redirect the user after the KYC process is completed (echo of the request).`request.metadata.webhook_url`string (nullable)URL to send the KYC process data after completion (echo of the request).`request.metadata.track_webhook_url`string (nullable)URL to send each KYC step processed by the user (echo of the request).
**Example response:**
```
{  "data": {    "external_id": null,    "created_on": "2025-07-28T18:11:54.430048399Z",    "is_active": true,    "token": "3f6dbcc1-49ba-4935-be90-dd8dd59b5530",    "magic_link_url": "https://verification.uat.unico.app/link/v2/3f6dbcc1-49ba-4935-be90-dd8dd59b5530",    "metadata": {      "redirect_url": null,      "webhook_url": null,      "track_webhook_url": null    },    "version": "v2"  },  "version": "v1.4.2",  "status": "ok",  "status_code": 200,  "request_date": "2025-07-28T18:11:54+0000",  "request": {    "metadata": {      "redirect_url": null,      "webhook_url": null,      "track_webhook_url": null    }  }}
```

**Error responses — POST /v2/magic-link**CodeMessageDescription`400 Bad Request``data provided in the field is invalid`Invalid data structure or field values in the request payload. Check required fields and formats.`403 Forbidden``Forbidden`API key missing, expired or without permission. Verify the `x-api-key` header.`500 Internal Server Error``internal server error`Server-side processing failure. Retry with exponential backoff. If persistent, report to support.**Example 400 response:**```
{  "data": {    "error": "data provided in the field is invalid"  },  "version": "v1.4.2",  "status": "bad request",  "status_code": 400,  "request_date": "2025-07-28T20:22:29+0000",  "request": {    "metadata": null  }}
```

### 3. Distribute magic_link_url to the user​

Send via WhatsApp, SMS, email or embed. The user accesses the link on their own device and completes the hosted journey.
If you'd rather embed the journey inside your own app instead of opening the link in the browser, you can do so via WebView or CustomTab. Reference PoCs:

**Android/Kotlin:** [TrullyAi-AndroidMagicLink](https://github.com/unico-labs/TrullyAi-AndroidMagicLink)
**React Native:** [TrullyAI-ReactNativeMagicLink](https://github.com/unico-labs/TrullyAI-ReactNativeMagicLink)

### 4. Receive the result​

You have full flexibility to decide how to receive the final result of the KYC process — choose the strategy that best fits your system's architecture. You can actively poll for the result (GET) or receive it passively through notifications (Webhook).
**Option A — Active polling via GET**
Retrieve the result by calling `GET /v2/history/request?magic_link_token={token}` (see [Polling via GET](#polling-via-get) below). The final decision is populated in `data.response.unico.result`.
Best practice for GETInstead of polling periodically from the moment the link is created, use the tracking webhooks (see [Webhook V2](#webhook-v2) below) to know exactly when to query. Once you receive the `form_decision_maker` step, that is the exact, safe moment to trigger this GET call.
**Option B — Automatic reception via Webhook**
Have your system notified automatically as soon as the final result is ready (see [Webhook V2](#webhook-v2) below). Two versions are available:

**Webhook V1** — you receive the result at the URL sent in `metadata.webhook_url` when creating the token.
**Webhook V2** — configured beforehand with your Onboarding PM. You receive the final verdict by listening to the `MAGIC_LINK_RESULTS` event.

Resilience — fallbackIf Webhook (Option B) is your primary method, also implement the GET query (Option A) as a fallback. This lets you recover the result through reconciliation routines in case of network failures, temporary server outages, or a webhook that fails to process correctly.

### Polling via GET​

**Endpoint:** `GET https://sandbox.trully.ai/v2/history/request`
**Query parameters:**
ParameterTypeRequiredDescription`magic_link_token`stringYesToken returned when the Magic Link was created.
**Headers:**
HeaderRequiredDescription`x-api-key`YesAPI key provisioned by your Onboarding Project Manager at Unico.
**Example request:**
```
curl "https://sandbox.trully.ai/v2/history/request?magic_link_token=$TOKEN" \  -H "x-api-key: $TRULLY_API_KEY"
```

**Example response:**
```
{  "data": {    "images": {      "document_image": "/9j/4ASu7bmV[...]fyPjOKfgif//Z",      "document_image_back": "/9j/4ASu7bmV[...]fyPjOKfgif//Z",      "selfie": "/9j/4ASu7bmV[...]fyPjOKfgif//Z"    },    "response": {      "curp": {        "age": 58,        "curp": "GOCJ850627HDFRRL09",        "date_of_birth": "14/11/1956",        "deceased": false,        "gender": "M",        "government_name": "LUKE SKYWALKER",        "government_valid": true,        "is_mexican": true,        "name_to_CURP_valid": true,        "state_iso": "MX-NLE",        "state_of_birth": "Nuevo León"      },      "document": {        "back": {          "cic": "237457894",          "citizen_id": "237457894",          "mrz": "IDMEX999999999999<9 VADER<SKYWALKER<<LUKE"        },        "details": {          "detected": true,          "document_id": 229928,          "forensics": { "is_valid": "no" }        },        "front": {          "face_analysis": {            "face_id": 237437,            "face_id_v2": 199068,            "first_seen": "12/22/2022, 18:54:09",            "inquiry_date": "07/28/2025, 20:53:12",            "last_seen": "07/28/2025, 18:47:46",            "last_seen_by_your_company": "07/24/2025, 21:38:21",            "match": true,            "match_fraud_flag": true,            "seen_by_your_company": true,            "seen_different_companies": 46,            "times_seen_by_your_company": 3,            "times_seen_last_month": 111,            "unique_face_id_v2": 126880,            "warnings": {              "external_id": "User found in the company with other external_ids: ['abc-123']"            }          },          "information": {            "address": { "text": "DOMICILIO/ADDRESS, HARLINGEN, TX 78552", "valid": false },            "birthdate": { "text": "14/11/1956", "valid": true },            "complete_name": { "text": "LUKE SKYWALKER", "valid": true },            "curp": { "text": "GOCJ850627HDFRRL09", "valid": true },            "electoral_key": { "text": "GRCRSN82031007M500", "valid": true },            "last_name": { "text": "SKYWALKER", "valid": true },            "mother_last_name": { "text": "ORGANA", "valid": true },            "name": { "text": "LUKE", "valid": true },            "registration_year": { "text": "1998", "valid": true },            "sex": { "text": "H", "valid": true },            "valid_thru": { "text": "2027", "valid": true }          }        }      },      "face_match": false,      "label": null,      "reason": null,      "request_id": "d1kxp9ah8f0s71uv9zx0",      "selfie": {        "face_id": 237436,        "face_id_v2": 4378,        "first_seen": "02/05/2025, 02:36:19",        "first_seen_image": true,        "inquiry_date": "07/28/2025, 20:52:49",        "last_seen": "07/28/2025, 20:52:51",        "last_seen_by_your_company": "07/23/2025, 18:14:27",        "match": true,        "match_fraud_flag": true,        "seen_by_your_company": true,        "seen_different_companies": 2,        "times_seen_by_your_company": 2,        "times_seen_last_month": 7,        "unique_face_id_v2": 494,        "warnings": {}      },      "unico": {        "process_id": "d333dfac-9ddb-4066-8e2c-44eaf4c86b4a",        "result": "PROCESS_RESULT_LIVE"      }    },    "user_id": ""  },  "request_date": "2025-07-28T20:53:38",  "status": "Request fulfilled, document follows",  "status_code": 200,  "version": "v3.6.0"}
```

**Response fields:**
**Root level:**
FieldTypeDescription`status`stringTextual representation of the response status.`status_code`integerHTTP status code.`request_date`string (date-time)The date and time when the request was made.`version`stringAPI version that processed the request.`data.user_id`stringThe user ID set in the original request.
**`data.images`** — Base64-encoded captured images:
FieldTypeDescription`data.images.document_image`stringBase64-encoded image of the front of the document.`data.images.document_image_back`stringBase64-encoded image of the back of the document.`data.images.selfie`stringBase64-encoded selfie image captured during verification.
**`data.response.unico`** — Consolidated verdict:
FieldTypeDescription`data.response.unico.process_id`string (UUID)Internal identifier related to the verification process.`data.response.unico.result`stringProcess result. Possible values described below.
**Possible values for `data.response.unico.result`:**
The result value encodes one of three evaluation dimensions:

**Identity assessment** — whether the captured face belongs to the document holder (`PROCESS_RESULT_VERIFIED`, `PROCESS_RESULT_NOT_APPROVED`).
**Fraud behavior** — whether behavioral or network signals indicate fraud risk (`PROCESS_RESULT_LIVE`, `PROCESS_RESULT_HIGH_RISK`, `PROCESS_RESULT_CRITICAL_RISK`, `PROCESS_RESULT_NOT_APPROVED`).
**Liveness** — whether a live face was detected at capture time (`PROCESS_RESULT_NOT_LIVE`).

ResultRecommendationMeaningSignal`PROCESS_RESULT_ERROR`**Reject**Process finished with error.System`PROCESS_RESULT_VERIFIED`**Accept**The user was active at the time of capture; it is the face of the ID holder and no evidence related to fraud was found.Identity: confirmed`PROCESS_RESULT_LIVE`**Review / Accept**The user was active at the time of the capture; we did not find sufficient evidence to guarantee that they are the ID holder and no evidence of fraud.Identity: inconclusive · Fraud: none`PROCESS_RESULT_HIGH_RISK`**Recommend rejection**We found at least one strong evidence of fraud. Final decision is yours.Fraud behavior: 1 strong signal`PROCESS_RESULT_CRITICAL_RISK`**Recommend rejection**We found at least 2 strong evidences of fraud. Final decision is yours.Fraud behavior: 2+ strong signals`PROCESS_RESULT_NOT_APPROVED`**Reject**Rejection recommended as multiple indications of fraud were detected.Identity: rejected · Fraud behavior: multiple signals`PROCESS_RESULT_NOT_LIVE`**Allow up to 2 retries**The user was not live at the time of capture, although we found no other indications of fraud.Liveness: face not live
note`PROCESS_RESULT_VERIFIED` **requires activation**. By default, this result is **not active** — align with your Unico Project Manager if you want to enable it.
**Auxiliary signals for your decision:**
For `LIVE`, `HIGH_RISK` or `CRITICAL_RISK` cases, complement your decision with these fields:
FieldTypeMeaning`face_match`booleanFace matching status between document and selfie.`document.front.face_analysis.match_fraud_flag`booleanWhether the document face has been associated with fraudulent activity.`selfie.match_fraud_flag`booleanWhether the selfie face has been associated with fraudulent activity.`warnings.external_id`stringWarning indicating the user's face is associated with multiple external IDs.
**`data.response`** — Top-level result signals:
FieldTypeDescription`data.response.face_match`booleanWhether the selfie face matches the document face.`data.response.label`string (nullable)Reserved for results from specific sub-processes; `null` if not applicable.`data.response.reason`string (nullable)Reasons for the assigned label.`data.response.request_id`stringUnique identifier for the API request.
**`data.response.curp`** — CURP validation against RENAPO:
FieldTypeDescription`curp`stringThe CURP string being analyzed.`government_valid`booleanWhether the CURP is valid according to RENAPO.`government_name`stringFull name associated with the CURP in the official database.`name_to_CURP_valid`booleanWhether the provided name is consistent with the CURP.`is_mexican`booleanWhether the CURP corresponds to a Mexican citizen.`deceased`booleanWhether the CURP is marked as deceased in the official database.`date_of_birth`stringDate of birth as decoded from the CURP (DD/MM/YYYY).`age`integerCalculated age of the user.`gender`stringGender as decoded from the CURP (`M` or `F`).`state_of_birth`stringState of birth as decoded from the CURP.`state_iso`stringISO 3166-2 code for the state of birth.
**`data.response.document.details`** — Document detection and forensics:
FieldTypeDescription`detected`booleanWhether a document was successfully detected in the image.`document_id`integerUnique identifier for the processed document.`forensics.is_valid`stringForensic analysis result: `yes`, `no` or `inconclusive`.
**`data.response.document.front.information`** — OCR-extracted fields from the INE front. Each field contains `text` (extracted value) and `valid` (whether it could be structurally validated):
FieldDescription`name`First name(s).`last_name`Paternal last name.`mother_last_name`Maternal last name.`complete_name`Full name.`birthdate`Date of birth (DD/MM/YYYY).`sex`Sex/gender (`H` or `M`).`curp`CURP extracted from the document.`electoral_key`Electoral key.`address`Address.`registration_year`Year the document was registered.`valid_thru`Document expiration year.
**`data.response.document.front.face_analysis`** — Analysis of the face found on the INE:
FieldTypeDescription`face_id`integerUnique identifier for this face instance.`face_id_v2`integerUnique identifier (version 2).`unique_face_id_v2`integerPersistent identifier for this physical face across all inquiries.`first_seen`stringTimestamp of the first time this face was ever seen in the system.`last_seen`stringTimestamp of the most recent time this face was seen in the network.`last_seen_by_your_company`stringMost recent time this face was seen in an inquiry by your company.`inquiry_date`stringTimestamp of the current verification inquiry.`match`booleanWhether this face is a potential match with other faces in the database.`match_fraud_flag`booleanWhether this face has been associated with fraudulent activity.`seen_by_your_company`booleanWhether this face was previously seen by your company.`seen_different_companies`integerNumber of other companies in the network that have seen this face.`times_seen_by_your_company`integerTotal times this face was seen in inquiries by your company.`times_seen_last_month`integerNumber of times seen in the last month across the network.`warnings.external_id`stringWarning if the user's face is associated with multiple external IDs.
**`data.response.document.back`** — MRZ data from the INE back:
FieldTypeDescription`mrz`stringFull Machine-Readable Zone string.`cic`stringCIC number extracted from the MRZ.`citizen_id`stringCitizen identification number from the MRZ.
**`data.response.selfie`** — Analysis of the captured selfie:
FieldTypeDescription`face_id`integerUnique identifier for the selfie face instance.`face_id_v2`integerUnique identifier (version 2).`unique_face_id_v2`integerPersistent identifier for this physical face across all inquiries.`first_seen`stringFirst time this selfie's face was ever seen in the system.`first_seen_image`booleanWhether this is the first time this exact image was seen.`last_seen`stringMost recent time this face was seen in the network.`last_seen_by_your_company`stringMost recent time this face was seen by your company.`inquiry_date`stringTimestamp of the current verification inquiry.`match`booleanWhether the selfie face matches other faces in the database.`match_fraud_flag`booleanWhether the selfie face is associated with fraudulent activity.`seen_by_your_company`booleanWhether this face was previously seen by your company.`seen_different_companies`integerNumber of other companies in the network that have seen this face.`times_seen_by_your_company`integerTotal times this face was seen in inquiries by your company.`times_seen_last_month`integerNumber of times seen in the last month across the network.`warnings`objectSpecific warnings related to selfie analysis (same structure as document face_analysis).
Use polling with **exponential backoff** — do not call in a tight loop.
**Error responses — GET /v2/history/request**CodeMessageDescription`400 Bad Request``Input should be a valid UUID, invalid group length...`The `magic_link_token` parameter is malformed or not a valid UUID format.`404 Not Found``This magic link have no requests associated`The provided token exists but has no completed KYC processes linked to it. The user may not have finished the journey yet.`500 Internal Server Error``internal server error`Server-side processing failure. Retry with exponential backoff. If persistent, report to support.**Example 400 response:**```
{  "data": {    "error": [      {        "attribute": ["magic_link_token"],        "message": "Input should be a valid UUID, invalid group length in group 4: expected 12, found 11",        "type": "uuid_parsing"      }    ]  },  "request_date": "2025-07-28T20:43:34",  "status": "There are some errors in the request",  "status_code": 400,  "version": "v3.6.0"}
```

**Example 404 response:**```
{  "data": {    "error": "This magic link have no requests associated"  },  "request_date": "2025-07-28T20:40:50",  "status": "Nothing matches the given URI",  "status_code": 404,  "version": "v3.6.0"}
```

**Example 500 response:**```
{  "data": {    "error": "internal server error"  },  "version": "v1.4.2",  "status": "bad request",  "status_code": 400,  "request_date": "2025-07-28T20:22:29+0000",  "request": {    "metadata": null  }}
```

### Webhook V2​

Three webhook events are available. To activate, request setup from your Onboarding Project Manager providing: endpoint **URL** (HTTPS required), **auth type** (`basic_auth`, `api_key`, `oauth2` or `NoAuth`), **auth configuration**, **max retry attempts**, **retry interval (s)** and **timeout (s)**.
MAGIC_LINK_RESULTS
This event is sent at the end of the flow, when the Decision Maker has finished processing the information sent.
```
{  "data": {    "images": {      "document_image": "base64str",      "document_image_back": "base64str",      "selfie": "base64str"    },    "response": {      "document": {        "details": {          "detected": true,          "forensics": {            "is_valid": "yes"          },          "document_id": 123        },        "front": {          "information": {            "birthdate": { "text": "14/11/1956", "valid": true },            "sex": { "text": "H", "valid": true },            "registration_year": { "text": "1998", "valid": true },            "name": { "text": "LUKE", "valid": true },            "mother_last_name": { "text": "ORGANA", "valid": true },            "last_name": { "text": "SKYWALKER", "valid": true },            "electoral_key": { "text": "GRCRSN82031007M500", "valid": true },            "curp": { "text": "GOCJ850627HDFRRL09", "valid": true },            "address": { "text": "DOMICILIO/ADDRESS, HARLINGEN, TX 78552", "valid": false },            "complete_name": { "text": "LUKE SKYWALKER", "valid": true },            "valid_thru": { "text": "2027", "valid": true }          },          "face_analysis": {            "face_id": 237437,            "first_seen": "12/22/2022, 18:54:09",            "unique_face_id_v2": 126880,            "face_id_v2": 199068,            "inquiry_date": "07/28/2025, 20:53:12",            "last_seen": "07/28/2025, 18:47:46",            "last_seen_by_your_company": "07/24/2025, 21:38:21",            "match": true,            "match_fraud_flag": true,            "seen_by_your_company": true,            "seen_different_companies": 46,            "times_seen_by_your_company": 3,            "times_seen_last_month": 111,            "warnings": {              "external_id": "User found in the company with other external_ids: ['abc-123']"            }          }        },        "back": {          "mrz": "IDMEX999999999999<9 VADER<SKYWALKER<<LUKE",          "cic": "237457894"        }      },      "selfie": {        "face_id": 237436,        "first_seen": "02/05/2025, 02:36:19",        "unique_face_id_v2": 494,        "face_id_v2": 4378,        "inquiry_date": "07/28/2025, 20:52:49",        "last_seen": "07/28/2025, 20:52:51",        "last_seen_by_your_company": "07/23/2025, 18:14:27",        "match": true,        "match_fraud_flag": true,        "seen_by_your_company": true,        "seen_different_companies": 2,        "times_seen_by_your_company": 2,        "times_seen_last_month": 7,        "warnings": {          "external_id": "User found in the company with other external_ids: ['abc-123']"        },        "first_seen_image": true      },      "face_match": false,      "curp": {        "curp": "GOCJ850627HDFRRL09",        "state_of_birth": "Nuevo León",        "state_iso": "MX-NLE",        "date_of_birth": "14/11/1956",        "age": 58,        "gender": "M",        "is_mexican": true,        "name_to_CURP_valid": true,        "government_valid": true,        "government_name": "LUKE SKYWALKER",        "deceased": false      },      "unico": {        "process_id": "d333dfac-9ddb-4066-8e2c-44eaf4c86b4a",        "result": "PROCESS_RESULT_LIVE"      },      "label": null,      "reason": null,      "request_id": "d1kxp9ah8f0s71uv9zx0"    },    "user_id": null  },  "event": "MAGIC_LINK_RESULTS",  "magic_link_token": "3f6dbcc1-49ba-4935-be90-dd8dd59b5530",  "user_id": null,  "date": "2025-10-03T21:15:41.299815"}
```

MAGIC_LINK_TRACK
These events are received when the user completed an action. For example, `form_start` will be received when the user is taking the front document, which means that the user clicked and completed the first screen.
```
{  "data": {    "completed_on": "Fri, 03 Oct 2025 21:15:35 GMT",    "started_on": "Fri, 03 Oct 2025 21:15:30 GMT",    "step": "form_start",    "user_id": null  },  "event": "MAGIC_LINK_TRACK",  "magic_link_token": "3f6dbcc1-49ba-4935-be90-dd8dd59b5530",  "user_id": null,  "date": "2025-10-03T21:15:41.299815"}
```

FieldTypeDescription`data.step`stringName of the completed step.`data.user_id`string (nullable)External ID set in the magic link.`data.started_on`datetimeUTC timezone step start time.`data.completed_on`datetimeUTC timezone step completion time.`event`stringEvent type identifier.`magic_link_token`uuidUnique magic link token.`user_id`string (nullable)External identifier.`date`datetimeEvent timestamp.
**Supported steps:**
StepDescription`form_start`User clicked the initial magic link button.`form_document_front`User completed INE front capture.`form_document_back`User completed INE back capture.`form_document`User completed both front and back processes.`form_selfie`User completed the liveness process.`form_decision_maker`User completed the entire flow.
MAGIC_LINK_DOCUMENT_RETAKE_REASONS
This event is sent when a document re-capture is required, either during the front or back process. It indicates that critical data from the INE could not be read.
```
{  "data": {    "document_type": "ine_front",    "invalid_back_ocr": false,    "invalid_curp": false,    "invalid_document": true  },  "event": "MAGIC_LINK_DOCUMENT_RETAKE_REASONS",  "magic_link_token": "3f6dbcc1-49ba-4935-be90-dd8dd59b5530",  "user_id": null,  "date": "2025-10-03T21:12:00.000Z"}
```

FieldTypeDescription`data.document_type`stringDocument side: `ine_front` or `ine_back`.`data.invalid_document`boolean`true` if the captured image is not a valid INE (either side).`data.invalid_curp`boolean`true` if the CURP could not be read (`ine_front` only).`data.invalid_back_ocr`boolean`true` if the MRZ code could not be read (`ine_back` only).`event`stringEvent type identifier.`magic_link_token`uuidUnique magic link token.`user_id`string (nullable)External identifier.`date`datetimeEvent timestamp.
The payload is designed so that only **one** error (`true`) is received for each document side.
**Failure scenarios — INE front:**

Invalid document → only `invalid_document: true`
CURP reading failure → only `invalid_curp: true`

**Failure scenarios — INE back:**

Invalid document → only `invalid_document: true`
MRZ reading failure → only `invalid_back_ocr: true`

V1 / V2 compatibilityIf you still send `webhook_url` in the `metadata` of the request (Webhook V1), that configuration **takes precedence** over the global V2. To migrate, remove `webhook_url` from `metadata` and configure V2 with your Onboarding PM.

### Customizations​

The hosted Magic Link page **does not support visual customization** by the client — it follows the default Unico/Trully visual identity. To customize the journey (logo, colors, texts), use Onboarding (Global) with SDK or Web.
**Availability:** Mexico · **Endpoint:** `POST https://api.trully.ai/v2/magic-link` · **Document:** INE · **Auth:** `x-api-key` (Trully)Last updated on Oct 8, 2026**