Quickstart · API REST

Integra DSI ⓥerify en 4 pasos

Descripción y tipos de datos a la izquierda · request y response a la derecha. Cambia de stack con las pestañas.

v1 · Base URL https://verify.dsi-mx.com.mx

Pasos
  1. Genera tus credenciales
  2. Crea una verificación
  3. El usuario completa el flow
  4. Revisa la respuesta
  5. Bonus · Webhook
  6. Referencia · Estados del webhook

Genera tus credenciales

Para que tu sistema pueda hablar con la API, necesitas una API key. Es la contraseña que identifica a tu sistema.

Cómo generarla

  1. Entra al panel admin · verify.dsi-mx.com.mx/admin
  2. Configuración → API Keys
  3. Click en "Generar nuevo API key"
  4. Ponle nombre (ej. "Backend producción") y crea

⚠ Cópialo al momento. Es la única vez que verás el key completo. Después solo verás el prefijo y los últimos 4 caracteres.

Guárdalo en variables de entorno o un secrets manager. Nunca en frontend.

Tu API key
dsi_live_8iLBI5uWzxU0h0676rbFrKQ1cR2GYbh7
Cómo se usa en cada request
Authorization: Bearer dsi_live_xxx
Accept: application/json

Crea una verificación

Llamas POST /api/v1/sessions. Te devolvemos un id y una url única que compartes con tu cliente.

Datos que envías

CampoTipo · EjemploRequeridoDefaultDescripción
tipo_documento string · enum
ej. "ine"
Opcional "ine" ine, pasaporte_mx, pasaporte_extranjero
return_url string · URL
ej. "https://miempresa.com/exito"
Opcional null Botón "Volver al sitio" al final apunta aquí. Máx 500 chars.
webhook_url string · URL HTTPS
ej. "https://miempresa.com/webhooks/dsi"
Opcional null POST automático con el resultado al terminar. Máx 500 chars.
metadata object
ej. {"customer_id":"CLI-12345"}
Opcional null Cajita libre con TUS datos para identificar la sesión cuando llegue el webhook. DSI no la lee ni la modifica — solo la guarda y te la regresa intacta.

Ejemplo más completo: { "customer_id": "CLI-12345", "order_id": "ORD-2026-9988", "source": "app-movil" }
expires_in integer · seg
ej. 3600
Opcional 86400 Vigencia. Min 300, max 604800.

Datos que recibes

CampoTipo · EjemploDescripción
id string · ULID 26 chars
ej. "01KQB7ZDTPJCNFGCHFV86B6R21"
Guárdalo — lo usas en el Paso 4
reference string
ej. "KYC-VVYW4YYJF6"
Referencia interna corta
url string · URL
ej. "https://verify.dsi-mx.com.mx/?token=01KQB..."
Compártela con tu cliente
hosted_url string · URL Alias de url
status string · enum
ej. "requires_input"
Inicial: requires_input
expires_at string · ISO 8601
ej. "2026-04-30T05:01:51Z"
Cuándo expira la sesión
metadata object · null
ej. {"customer_id":"CLI-12345"}
Lo que mandaste, intacto
created_at string · ISO 8601
ej. "2026-04-29T05:01:51Z"
Timestamp de creación
POST/api/v1/sessions
curl -X POST https://verify.dsi-mx.com.mx/api/v1/sessions \
  -H "Authorization: Bearer $DSI_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{
    "tipo_documento": "ine",
    "return_url":     "https://miempresa.com/kyc/exito",
    "webhook_url":    "https://miempresa.com/webhooks/dsi",
    "metadata":       { "customer_id": "CLI-12345" },
    "expires_in":     3600
  }'
use Illuminate\Support\Facades\Http;

$resp = Http::withToken(config('services.dsi.api_key'))
    ->acceptJson()
    ->post('https://verify.dsi-mx.com.mx/api/v1/sessions', [
        'tipo_documento' => 'ine',
        'return_url'     => 'https://miempresa.com/kyc/exito',
        'webhook_url'    => 'https://miempresa.com/webhooks/dsi',
        'metadata'       => ['customer_id' => 'CLI-12345'],
        'expires_in'     => 3600,
    ]);

$session = $resp->throw()->json();
$urlParaUsuario = $session['url'];
$client = new \GuzzleHttp\Client();

$resp = $client->post('https://verify.dsi-mx.com.mx/api/v1/sessions', [
    'headers' => [
        'Authorization' => 'Bearer ' . getenv('DSI_API_KEY'),
        'Accept'        => 'application/json',
    ],
    'json' => [
        'tipo_documento' => 'ine',
        'webhook_url'    => 'https://miempresa.com/webhooks/dsi',
        'metadata'       => ['customer_id' => 'CLI-12345'],
    ],
]);

$session = json_decode($resp->getBody(), true);
const resp = await fetch('https://verify.dsi-mx.com.mx/api/v1/sessions', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${process.env.DSI_API_KEY}`,
    'Content-Type':  'application/json',
    'Accept':        'application/json',
  },
  body: JSON.stringify({
    tipo_documento: 'ine',
    return_url:     'https://miempresa.com/kyc/exito',
    webhook_url:    'https://miempresa.com/webhooks/dsi',
    metadata:       { customer_id: 'CLI-12345' },
    expires_in:     3600,
  }),
});

if (!resp.ok) throw new Error(`DSI ${resp.status}`);
const session = await resp.json();
const axios = require('axios');

const { data: session } = await axios.post(
  'https://verify.dsi-mx.com.mx/api/v1/sessions',
  {
    tipo_documento: 'ine',
    webhook_url:    'https://miempresa.com/webhooks/dsi',
    metadata:       { customer_id: 'CLI-12345' },
  },
  {
    headers: { Authorization: `Bearer ${process.env.DSI_API_KEY}` },
  }
);
import os, requests

resp = requests.post(
    'https://verify.dsi-mx.com.mx/api/v1/sessions',
    headers={
        'Authorization': f'Bearer {os.environ["DSI_API_KEY"]}',
        'Accept':        'application/json',
    },
    json={
        'tipo_documento': 'ine',
        'return_url':     'https://miempresa.com/kyc/exito',
        'webhook_url':    'https://miempresa.com/webhooks/dsi',
        'metadata':       {'customer_id': 'CLI-12345'},
        'expires_in':     3600,
    },
    timeout=15,
)
resp.raise_for_status()
session = resp.json()
201 Created application/json
{
  "id":         "01KQB7ZDTPJCNFGCHFV86B6R21",
  "reference":  "KYC-VVYW4YYJF6",
  "url":        "https://verify.dsi-mx.com.mx/?token=01KQB7ZDTPJCNFGCHFV86B6R21",
  "hosted_url": "https://verify.dsi-mx.com.mx/?token=01KQB7ZDTPJCNFGCHFV86B6R21",
  "status":     "requires_input",
  "expires_at": "2026-04-30T05:01:51Z",
  "metadata":   { "customer_id": "CLI-12345" },
  "created_at": "2026-04-29T05:01:51Z"
}
401 Unauthorized API key faltante o inválida
{
  "error":   "unauthenticated",
  "message": "API key faltante. Envía Authorization: Bearer dsi_live_xxx"
}
422 Unprocessable Validación de algún campo falló
{
  "message": "El campo webhook_url debe ser una URL válida.",
  "errors": {
    "webhook_url": [
      "El campo webhook_url debe ser una URL válida."
    ],
    "expires_in": [
      "El campo expires_in debe estar entre 300 y 604800."
    ]
  }
}
429 Too Many Requests Rate limit excedido
{ "message": "Too Many Attempts." }

El usuario completa el flow

Tu cliente abre la URL y completa solo:

  1. Selecciona tipo de documento
  2. Toma foto del frente del documento
  3. Toma foto del reverso (solo INE)
  4. Toma una selfie
  5. Espera ~10 seg mientras el motor valida
  6. Ve su resultado

Mientras esto pasa, no haces nada. Tu sistema espera. Consulta después (Paso 4) o configura webhook (Paso 5).

Capas que corren en paralelo
CapaAplica
ocrTodos
mrzINE, Pasaporte
renapoINE, Pas. MX
satINE, Pas. MX
livenessTodos
facialTodos
ofacPasaporte
vigenciaTodos

Revisa la respuesta

Llamas GET /api/v1/sessions/{id} con el id del Paso 2. Devolvemos el estado y, si terminó, el resultado completo.

Datos que envías

Solo el path param id y el header Authorization. Sin body.

Datos que recibes

CampoTipo · EjemploCuándoDescripción
id string · ULID
ej. "01KQB7ZDTPJCNFGCHFV86B6R21"
siempre El mismo de la sesión
reference string
ej. "KYC-VVYW4YYJF6"
siempre Referencia interna corta
url string · URL
ej. "https://verify.dsi-mx.com.mx/?token=01KQB..."
siempre URL hosteada
status string · enum
ej. "verified"
siempre requires_input, verified, requires_review, rejected
expires_at string · ISO 8601
ej. "2026-04-30T05:01:51Z"
siempre Cuándo expira la sesión
metadata object · null
ej. {"customer_id":"CLI-12345"}
siempre Lo que mandaste al crear, intacto
created_at string · ISO 8601 siempre Timestamp de creación
updated_at string · ISO 8601 siempre Última modificación
result object solo si terminó Bloque con el resultado completo de la verificación.
Sub-campoTipo · EjemploDescripción
estado string · enum
ej. "aprobado"
aprobado, rechazado, revision_manual
score integer · 0-100
ej. 96
Puntaje agregado ponderado
tipo_documento string
ej. "ine"
Igual al solicitado
titular object Datos del titular extraídos del documento:
Sub-campoTipo · Ejemplo
nombre_completostring · null
ej. "MARIANO LOPEZ BALDERAS"
curpstring · 18 chars · null
ej. "LOMA900615HDFRRR07"
rfcstring · 13 chars · null
ej. "LOMA900615AB1"
numero_documentostring · null
ej. "N03341163" (solo pasaporte)
nacionalidadstring · 3 chars · null
ej. "MEX"
fecha_vencimientostring · ISO date · null
ej. "2034-12-31"
capas object Cada capa que corrió, con estructura común: { "passed": true, "confidence": 96.0, "error": null, "data": { ... } }
webhook object si webhook configurado Estado del envío del webhook al cliente:
Sub-campoTipo · EjemploDescripción
url string
ej. "https://miempresa.com/webhooks/dsi"
URL configurada al crear
status string · enum
ej. "delivered"
pending, sending, delivered, failed
attempts integer
ej. 1
Intentos realizados
last_attempt_at string · ISO 8601 · null
ej. "2026-04-29T05:08:13Z"
Último intento
last_error string · null
ej. "HTTP 500: ..."
Mensaje del último error
GET/api/v1/sessions/{id}
curl https://verify.dsi-mx.com.mx/api/v1/sessions/01KQB7ZDTPJCNFGCHFV86B6R21 \
  -H "Authorization: Bearer $DSI_API_KEY" \
  -H "Accept: application/json"
$resp = Http::withToken(config('services.dsi.api_key'))
    ->acceptJson()
    ->get("https://verify.dsi-mx.com.mx/api/v1/sessions/{$sessionId}");

$data = $resp->throw()->json();

if ($data['status'] === 'verified') {
    $score   = $data['result']['score'];
    $titular = $data['result']['titular'];
}
$client = new \GuzzleHttp\Client();

$resp = $client->get(
    "https://verify.dsi-mx.com.mx/api/v1/sessions/{$sessionId}",
    [
        'headers' => [
            'Authorization' => 'Bearer ' . getenv('DSI_API_KEY'),
            'Accept'        => 'application/json',
        ],
    ]
);

$data = json_decode($resp->getBody(), true);
const resp = await fetch(`https://verify.dsi-mx.com.mx/api/v1/sessions/${id}`, {
  headers: {
    'Authorization': `Bearer ${process.env.DSI_API_KEY}`,
    'Accept':        'application/json',
  },
});

const data = await resp.json();
if (data.status === 'verified') {
  // data.result.score, data.result.titular, data.result.capas
}
const axios = require('axios');

const { data } = await axios.get(
  `https://verify.dsi-mx.com.mx/api/v1/sessions/${id}`,
  {
    headers: { Authorization: `Bearer ${process.env.DSI_API_KEY}` },
  }
);

if (data.status === 'verified') {
  // data.result.score, data.result.titular, data.result.capas
}
resp = requests.get(
    f'https://verify.dsi-mx.com.mx/api/v1/sessions/{session_id}',
    headers={'Authorization': f'Bearer {os.environ["DSI_API_KEY"]}'},
    timeout=15,
)
data = resp.json()
if data['status'] == 'verified':
    score   = data['result']['score']
    titular = data['result']['titular']
200 OK application/json
{
  "id":         "01KQB7ZDTPJCNFGCHFV86B6R21",
  "status":     "requires_input",
  "expires_at": "2026-04-30T05:01:51Z",
  "metadata":   { "customer_id": "CLI-12345" },
  "created_at": "2026-04-29T05:01:51Z",
  "updated_at": "2026-04-29T05:01:51Z"
}
{
  "id":     "01KQB7ZDTPJCNFGCHFV86B6R21",
  "status": "verified",
  "metadata": { "customer_id": "CLI-12345" },
  "result": {
    "estado":         "aprobado",
    "score":          96,
    "tipo_documento": "ine",
    "titular": {
      "nombre_completo":   "MARIANO LOPEZ BALDERAS",
      "curp":              "LOMA900615HDFRRR07",
      "rfc":               "LOMA900615AB1",
      "numero_documento":  null,
      "nacionalidad":      null,
      "fecha_vencimiento": "2034-12-31"
    },
    "capas": {
      "ocr":      { "passed": true, "confidence": 96.0 },
      "renapo":   { "passed": true, "confidence": 95.0 },
      "sat":      { "passed": true, "confidence": 90.0 },
      "liveness": { "passed": true, "confidence": 92.5 },
      "facial":   { "passed": true, "confidence": 97.8 },
      "vigencia": { "passed": true, "confidence": 98.0 }
    }
  },
  "webhook": {
    "status":   "delivered",
    "attempts": 1
  }
}
401 Unauthorized API key faltante o inválida
{ "error": "unauthenticated" }
404 Not Found El id no existe o pertenece a otro tenant
{ "error": "session_not_found" }

Bonus · Recibir resultado por webhook

En el Paso 4 usaste pull (tú preguntas). Ahora push — nosotros te avisamos automáticamente cuando termina.

⚠ Tú construyes el endpoint receptor. El webhook es una llamada que nosotros hacemos a tu servidor — necesitas exponer una URL pública.

Headers que mandamos

HeaderTipoDescripción
X-DSI-Signaturestring · base64HMAC-SHA256 sobre "{ts}.{raw_body}"
X-DSI-Timestampstring · Unix epochAnti-replay (rechaza fuera de 5 min)
X-DSI-EventstringHoy: session.completed
User-AgentstringDSIVerify-Webhook/1.0

Body que mandamos

CampoTipoDescripción
eventstringHoy: session.completed
created_atstring · ISO 8601Cuándo se generó
session_idstring · ULIDEl id de la sesión
referencestringReferencia interna
statusstring · enumverified, rejected, requires_review
scoreinteger · 0-100Puntaje agregado
metadataobject · nullLo que mandaste al crear
titularobjectMismos campos que GET result.titular

⚠ Crítico: tu endpoint siempre debe validar la firma HMAC antes de procesar. Sin esto, cualquiera puede mandarte requests falsos.

Reintentos

Si tu endpoint NO devuelve 2xx en 15 seg, reintentamos hasta 4 veces con backoff: 1m → 5m → 15m → 1h.

Reentrega manual

POST /api/v1/sessions/{id}/redeliver reagenda el envío. Útil si tu servidor estaba caído.

Body de ejemplo (lo que recibes)
{
  "event":      "session.completed",
  "created_at": "2026-04-29T05:08:13Z",
  "session_id": "01KQB7ZDTPJCNFGCHFV86B6R21",
  "reference":  "KYC-VVYW4YYJF6",
  "status":     "verified",
  "score":      96,
  "metadata":   { "customer_id": "CLI-12345" },
  "titular": {
    "nombre_completo":   "MARIANO LOPEZ BALDERAS",
    "curp":              "LOMA900615HDFRRR07",
    "rfc":               "LOMA900615AB1",
    "tipo_documento":    "ine",
    "fecha_vencimiento": "2034-12-31"
  }
}
Receptor que valida firma
// routes/web.php
Route::post('/webhooks/dsi', function (Request $request) {
    $secret = hash('sha256', config('services.dsi.api_key'));
    $body   = $request->getContent();
    $ts     = $request->header('X-DSI-Timestamp');
    $firma  = $request->header('X-DSI-Signature');

    $esperada = base64_encode(
        hash_hmac('sha256', "{$ts}.{$body}", $secret, true)
    );

    if (!hash_equals($esperada, (string) $firma)) {
        abort(403, 'firma inválida');
    }
    if (abs(time() - (int) $ts) > 300) {
        abort(403, 'timestamp expirado');
    }

    $evento = json_decode($body, true);
    // procesar idempotente

    return response('OK', 200);
})->withoutMiddleware([
    \Illuminate\Foundation\Http\Middleware\VerifyCsrfToken::class,
]);
const crypto  = require('crypto');
const express = require('express');
const app     = express();

app.post('/webhooks/dsi',
  express.raw({ type: 'application/json' }),
  (req, res) => {
    const secret = crypto.createHash('sha256')
      .update(process.env.DSI_API_KEY).digest('hex');
    const ts    = req.headers['x-dsi-timestamp'];
    const firma = req.headers['x-dsi-signature'];
    const body  = req.body.toString('utf8');

    const esperada = crypto.createHmac('sha256', secret)
      .update(`${ts}.${body}`).digest('base64');

    if (!crypto.timingSafeEqual(Buffer.from(esperada), Buffer.from(firma))) {
      return res.status(403).send('firma inválida');
    }
    if (Math.abs(Date.now() / 1000 - parseInt(ts)) > 300) {
      return res.status(403).send('timestamp expirado');
    }

    const evento = JSON.parse(body);
    res.status(200).send('OK');
  });
import base64, hashlib, hmac, json, os, time
from flask import Flask, request, abort

app = Flask(__name__)

@app.post('/webhooks/dsi')
def dsi_webhook():
    secret = hashlib.sha256(
        os.environ['DSI_API_KEY'].encode()
    ).hexdigest()
    body  = request.get_data(as_text=True)
    ts    = request.headers.get('X-DSI-Timestamp', '')
    firma = request.headers.get('X-DSI-Signature', '')

    esperada = base64.b64encode(
        hmac.new(
            secret.encode(),
            f'{ts}.{body}'.encode(),
            hashlib.sha256
        ).digest()
    ).decode()

    if not hmac.compare_digest(esperada, firma):
        abort(403, 'firma inválida')
    if abs(int(time.time()) - int(ts)) > 300:
        abort(403, 'timestamp expirado')

    evento = json.loads(body)
    return 'OK', 200
POST/api/v1/sessions/{id}/redeliver
curl -X POST \
  https://verify.dsi-mx.com.mx/api/v1/sessions/01KQB.../redeliver \
  -H "Authorization: Bearer $DSI_API_KEY"
Http::withToken(config('services.dsi.api_key'))
    ->post("https://verify.dsi-mx.com.mx/api/v1/sessions/{$id}/redeliver");
$client = new \GuzzleHttp\Client();

$client->post(
    "https://verify.dsi-mx.com.mx/api/v1/sessions/{$id}/redeliver",
    ['headers' => ['Authorization' => 'Bearer ' . getenv('DSI_API_KEY')]]
);
await fetch(
  `https://verify.dsi-mx.com.mx/api/v1/sessions/${id}/redeliver`,
  {
    method: 'POST',
    headers: { Authorization: `Bearer ${process.env.DSI_API_KEY}` },
  }
);
const axios = require('axios');

await axios.post(
  `https://verify.dsi-mx.com.mx/api/v1/sessions/${id}/redeliver`,
  null,
  { headers: { Authorization: `Bearer ${process.env.DSI_API_KEY}` } }
);
requests.post(
    f'https://verify.dsi-mx.com.mx/api/v1/sessions/{session_id}/redeliver',
    headers={'Authorization': f'Bearer {os.environ["DSI_API_KEY"]}'},
)
200 OK
{
  "queued":  true,
  "message": "Webhook agendado para reenvío..."
}
401 Unauthorized API key faltante o inválida
{ "error": "unauthenticated" }
404 Not Found El id no existe o pertenece a otro tenant
{ "error": "session_not_found" }
422 Unprocessable La sesión NO tiene webhook_url configurado
{ "error": "webhook_not_configured" }
422 Unprocessable La verificación todavía no termina
{
  "error":   "session_not_completed",
  "message": "La sesión aún no termina, no hay resultado que entregar."
}

Referencia · Estados del webhook

El campo webhook.status en GET /sessions/{id} refleja el estado de delivery del último intento de entrega. Los errores de cada endpoint se documentan junto al endpoint correspondiente.

webhook.status — valores posibles
ValorSignificado
pendingAgendado, sin enviar todavía
sendingIntentando entregar ahora mismo
deliveredRecibió 2xx — terminal exitoso
failedReintentos agotados — pide reentrega
¿Atorado? Mándanos un correo a mariano.lopez@dsi-mx.com con el prefix de tu API key (los primeros 12 caracteres, ej. dsi_live_8iLB). Nunca compartas el key completo.
ISO/IEC 27001 HTTPS · TLS 1.3 AES-256 en reposo Cifrado E2E LFPDPPP · México Datos no almacenados localmente
© 2026 DSI Servicios Digitales — Una entidad de DSI Servicios Financieros
API · Docs Aviso de Privacidad Términos y Condiciones Soporte