# 🏥 Servicio Premium de Semanas Cotizadas del IMSS (asíncrono)

Endpoint asíncrono para obtener el reporte Premium de Semanas Cotizadas del IMSS. El cliente envía la solicitud y elige cómo recibir el resultado:

  • Modo webhook: provee webhookUrl y entregamos el PDF a esa URL cuando el reporte esté listo (firmado con HMAC-SHA256).
  • Modo polling-only: omite webhookUrl y consulta el resultado por GET /v3/sc_premium/{uuid} cuando lo necesites.

Ambos modos cuestan los mismos 3 créditos por submit; lo único que cambia es cómo te enteras del resultado.

# 📑 Contenido

  1. Submit — POST /v3/sc_premium
  2. Webhook de entrega — POST a tu webhookUrl
  3. Consultar resultado — GET /v3/sc_premium/{uuid}
  4. Reintentar entrega — POST /v3/sc_premium/{uuid}/replay
  5. Flujo completo
  6. Preguntas frecuentes
  7. Modo Mock
  8. Changelog

# 1. Submit — POST /v3/sc_premium

Envía una consulta para una CURP e indica a dónde entregar el resultado.

# Request — modo webhook (push)

POST /v3/sc_premium HTTP/1.1
Host: consultaunica.mx
X-API-Key: <tu_api_key>
Content-Type: application/json

{
  "curp": "AAAA800101HDFBBB01",
  "webhookUrl": "https://tu-dominio.com/webhook/sc-premium"
}

# Request — modo polling-only (pull)

Si tu infraestructura no puede recibir webhooks (corres detrás de NAT, no tienes endpoint HTTPS público, etc.), omite webhookUrl y consulta el resultado por GET /v3/sc_premium/{uuid}.

POST /v3/sc_premium HTTP/1.1
Host: consultaunica.mx
X-API-Key: <tu_api_key>
Content-Type: application/json

{
  "curp": "AAAA800101HDFBBB01"
}

# Campos

Campo Tipo Requerido Descripción
curp string CURP de 18 caracteres, se normaliza a mayúsculas y se valida contra regex.
webhookUrl string no URL HTTPS pública a la que te entregaremos el resultado. Si lo omites o envías null, no enviaremos webhook y deberás consultar el estado por GET /v3/sc_premium/{uuid}. Ver requisitos.

# Requisitos del webhookUrl

Aplican sólo si envías webhookUrl. En modo polling-only no hay validación de red ni resolución DNS.

Rechazamos la solicitud si la URL no cumple:

  • Esquema HTTPS (no http://).
  • Sin credenciales embebidas (https://user:pass@... no se acepta).
  • Debe resolver vía DNS a una IP pública. Rechazamos loopback (127.0.0.0/8, ::1), rangos privados (10/8, 172.16/12, 192.168/16, fc00::/7), link-local (169.254/16, fe80::/10), reservados, multicast y 0.0.0.0.
  • Timeout de resolución DNS: 2 segundos. Si tu dominio tarda más, la solicitud se rechaza.

# Costo

Cada solicitud exitosa consume 3 créditos de tu saldo.

# Rate limit

Máximo 5 requests por minuto por X-API-Key en este endpoint. Al rebasarlo devolvemos 429 Too Many Requests con el mensaje de la tabla de errores más abajo. El rate limit es independiente del límite global de la cuenta.

# Idempotency-Key (opcional)

Para protegerte de cobros duplicados si tu cliente reintenta el submit por un error de red, envía el header Idempotency-Key. Reenviar con la misma key devuelve la respuesta de la consulta original sin volver a consumir créditos.

  • Formato: ^[A-Za-z0-9_-]{1,64}$ (64 chars máx, alfanuméricos + - + _).
  • TTL: 24 horas. Después de eso, la misma key vuelve a tratarse como consulta nueva.
  • Scope: por usuario. Dos usuarios distintos pueden usar la misma key sin colisión.
  • Respuesta de replay:
    • Body idéntico al de la consulta original.
    • Header X-Idempotent-Replay: true.
    • Header X-Remaining-Requests: refleja el saldo inmediatamente después del submit original, no el saldo vivo actual. Si necesitas el saldo al día, consúltalo desde tu dashboard.

Errores:

HTTP Caso detail
400 Key con formato inválido invalid_idempotency_key
409 Key ya en uso por otra request concurrente que aún no termina concurrent_idempotent_request

Ejemplo curl con idempotency:

curl -X POST https://api.consultaunica.mx/v3/sc_premium \
  -H "X-API-Key: $CU_API_KEY" \
  -H "Idempotency-Key: order-12345-retry-1" \
  -H "Content-Type: application/json" \
  -d '{"curp":"AAAA800101HDFBBB01","webhookUrl":"https://tu-dominio.com/hook"}'

Genera un valor único por cada submit lógico de tu lado (por ejemplo, el ID de la orden interna + contador de reintento).

# ✅ Respuesta exitosa — 200 OK

{
  "uuid": "9f2b7c1a-4d3e-4a5f-8b0c-2e1d0f9a8b77",
  "message": "Solicitud recibida, pendiente de resultado",
  "curp": "AAAA800101HDFBBB01",
  "nss": "12345678901",
  "fullname": "Juan Pérez López"
}
Campo Tipo Descripción
uuid string Identificador único de la solicitud. Guárdalo: llegará idéntico en el webhook y te sirve para deduplicar.
message string Mensaje descriptivo del estado.
curp string CURP confirmado.
nss string Número de Seguridad Social asociado al CURP. Útil para prellenar otros flujos.
fullname string Nombre completo del titular.

Headers de respuesta:

  • X-Remaining-Requests: créditos restantes después de este consumo.

# Ejemplo curl

curl -X POST https://api.consultaunica.mx/v3/sc_premium \
  -H "X-API-Key: $CU_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "curp": "AAAA800101HDFBBB01",
    "webhookUrl": "https://tu-dominio.com/webhook/sc-premium"
  }'

# ❌ Respuestas de error

Todos los errores respetan el formato estándar de nuestro servidor:

{ "detail": "<mensaje o arreglo>" }

A continuación la lista completa de respuestas que puede recibir tu cliente. Usa el mensaje exacto (columna detail) para armar tu catálogo; son cadenas estables entre versiones.

# 400 — Webhook inválido

HTTP/1.1 400 Bad Request
{
  "detail": "webhookUrl debe ser https y apuntar a un host público accesible"
}

# 401 — Autenticación

HTTP/1.1 401 Unauthorized
{ "detail": "No se encontró la Clave para realizar consultas" }
{ "detail": "Cuenta no válida, favor de verificar su clave de consulta" }
{ "detail": "Usuario no encontrado" }
Caso detail
Header X-API-Key ausente o vacío No se encontró la Clave para realizar consultas
API key no corresponde a una cuenta activa Cuenta no válida, favor de verificar su clave de consulta
Cuenta sin registro post-autenticación (caso raro) Usuario no encontrado

# 422 — Validación del payload

Nuestro servidor devuelve un arreglo describiendo cada campo inválido.

HTTP/1.1 422 Unprocessable Entity
{
  "detail": [
    {
      "type": "string_pattern_mismatch",
      "loc": ["body", "curp"],
      "msg": "String should match pattern '^[A-Z]{4}[0-9]{6}[HM][A-Z]{5}[0-9A-Z][0-9]$'",
      "input": "no-es-un-curp"
    }
  ]
}

Campos que pueden disparar 422:

  • curp faltante, longitud distinta de 18, o fuera del patrón.
  • webhookUrl con esquema distinto a https o formato inválido (sólo si lo envías; omitirlo es válido y activa modo polling-only).
  • JSON malformado.

# 422 — CURP no localizado o datos inconsistentes

HTTP/1.1 422 Unprocessable Entity
{ "detail": "La CURP ingresada no es válida, favor de verificar" }
{ "detail": "No se encontró información en el IMSS para la CURP ingresada" }
{ "detail": "El IMSS reportó inconsistencias en los datos de esta CURP" }
{ "detail": "Ya existe una consulta en curso para esta CURP, espere a que termine antes de reintentar" }

# 429 — Sin créditos

HTTP/1.1 429 Too Many Requests
{
  "detail": "Has alcanzado el límite de consultas diarias, visita nuestros paquetes para más: https://consultaunica.mx/#prices"
}

# 429 — Rate limit

HTTP/1.1 429 Too Many Requests
{
  "detail": "Debe esperar 60 segundos para volver a consultar, evitemos congestionar el servicio"
}

El número de segundos es dinámico; haz match por prefijo Debe esperar si necesitas detectarlo.

# 503 — Servicio no disponible

HTTP/1.1 503 Service Unavailable
{ "detail": "El servicio no se encuentra disponible, favor de consultar más tarde" }
{ "detail": "El sistema se encuentra a máxima capacidad, favor de esperar unos minutos antes de reintentar" }
{ "detail": "El servicio de semanas cotizadas premium no está disponible, favor de intentar más tarde" }

Recomendación: reintenta con backoff exponencial empezando en 60 s.

# 500 — Error interno

HTTP/1.1 500 Internal Server Error
{ "detail": "Ocurrió un error inesperado, favor de reintentar" }

# Catálogo de mensajes para armar tu diccionario

HTTP detail exacto Código sugerido
400 webhookUrl debe ser https y apuntar a un host público accesible invalid_webhook_url
401 No se encontró la Clave para realizar consultas missing_api_key
401 Cuenta no válida, favor de verificar su clave de consulta unauthorized_user
401 Usuario no encontrado user_not_found
422 La CURP ingresada no es válida, favor de verificar invalid_curp
422 No se encontró información en el IMSS para la CURP ingresada no_data_found
422 El IMSS reportó inconsistencias en los datos de esta CURP imss_data_inconsistency
422 Ya existe una consulta en curso para esta CURP, espere a que termine antes de reintentar duplicate_in_progress
422 (arreglo del validador de solicitudes) validation_error
429 Has alcanzado el límite de consultas diarias, visita nuestros paquetes para más: … insufficient_credits
429 Debe esperar {N} segundos para volver a consultar, evitemos congestionar el servicio rate_limit_exceeded
503 El servicio no se encuentra disponible, favor de consultar más tarde service_unavailable
503 El sistema se encuentra a máxima capacidad, favor de esperar unos minutos antes de reintentar system_at_capacity
503 El servicio de semanas cotizadas premium no está disponible, favor de intentar más tarde system_paused
500 Ocurrió un error inesperado, favor de reintentar unknown

# 2. Webhook de entrega — POST a tu webhookUrl

Aplica sólo si enviaste webhookUrl en el submit. En modo polling-only no enviamos webhook — salta a §3 Consultar resultado.

Cuando el reporte está listo (o falla), hacemos POST a la URL que nos diste.

# Headers

Header Ejemplo Descripción
Content-Type application/json Siempre JSON.
User-Agent consultaunica-webhook/1.0 Identifica nuestro tráfico.
X-CU-Uuid 9f2b7c1a-4d3e-4a5f-8b0c-2e1d0f9a8b77 Mismo uuid que devolvió el submit.
X-CU-Timestamp 1745336400 Unix epoch (segundos) en el que se generó la firma. Nuevo en cada reintento.
X-CU-Signature sha256=a1b2c3... HMAC-SHA256 de "{X-CU-Timestamp}." + <cuerpo bruto>, firmado con tu X-API-Key.

# Body — entrega exitosa

{
  "version": 1,
  "uuid": "9f2b7c1a-4d3e-4a5f-8b0c-2e1d0f9a8b77",
  "status": "completed",
  "pdfBase64": "JVBERi0xLjQKJ...",
  "errorMessage": null
}
  • version es la versión del contrato del webhook (entero). Ver Versionado del contrato abajo.
  • pdfBase64 es el PDF del reporte codificado en base64 estándar. Decodifícalo directamente a bytes y guárdalo / muéstralo.

# Body — entrega fallida

{
  "version": 1,
  "uuid": "9f2b7c1a-4d3e-4a5f-8b0c-2e1d0f9a8b77",
  "status": "failed",
  "pdfBase64": null,
  "errorMessage": "La CURP ingresada no es válida, favor de verificar"
}

# Versionado del contrato

El campo version del body te permite detectar cambios de esquema sin parsear headers.

  • Estamos en version: 1.
  • Cambios aditivos (campos nuevos opcionales) → mantenemos la misma versión.
  • Cambios breaking (renames, removals, cambios de tipo) → bump a version: 2 y damos aviso con anticipación.
  • La versión va dentro del body firmado, así que un atacante no puede cambiarla sin invalidar la firma.

Recomendación: en tu validador, rechaza versiones distintas a las que conoces en vez de silenciosamente aceptarlas.

# Estados posibles

status Significado ¿Habrá más callbacks?
completed Reporte listo, pdfBase64 presente. No, es terminal.
failed No se pudo generar o entregar el reporte. No, es terminal.

# Catálogo de errorMessage en status: "failed"

Son los mismos mensajes que devuelve el submit síncrono cuando aplican durante la generación asíncrona, más tres específicos del flujo de entrega. Máximo 500 caracteres.

errorMessage exacto Código sugerido Caso
La CURP ingresada no es válida, favor de verificar invalid_curp CURP rechazado durante el procesamiento.
No se encontró información en el IMSS para la CURP ingresada no_data_found CURP válido pero sin registros.
El IMSS reportó inconsistencias en los datos de esta CURP imss_data_inconsistency Datos incongruentes en la fuente.
El servicio no se encuentra disponible, favor de consultar más tarde service_unavailable Falla transitoria del sistema durante la generación.
No fue posible recuperar el PDF del reporte, favor de reintentar la consulta pdf_download_failed El reporte se generó pero no pudimos recuperar el PDF para entregártelo. No se cobró el PDF.
Ocurrió un error inesperado, favor de reintentar unknown Error interno no especificado.

# Validación de la firma (obligatorio)

Antes de confiar en un callback debes realizar dos verificaciones:

  1. Tolerancia de tiempo. El X-CU-Timestamp (unix epoch en segundos) debe estar dentro de una ventana cercana a tu reloj actual. Recomendamos ±300 segundos (5 min). Esto protege contra replay.
  2. Firma HMAC. Recomputas HMAC-SHA256 sobre los bytes "{X-CU-Timestamp}." + <cuerpo_crudo> usando tu X-API-Key como secreto. Comparas contra X-CU-Signature en tiempo constante.

Importante: firmamos los bytes exactos del cuerpo recibido. No parsees ni re-serialices el JSON antes de validar — los separadores/espacios cambiarían la firma.

# Pseudocódigo

secret     = TU_API_KEY                          # misma que usas en X-API-Key
raw_body   = bytes_del_request                    # NO parsear ni re-serializar
timestamp  = request.headers["X-CU-Timestamp"]    # string de dígitos
signature  = request.headers["X-CU-Signature"]    # "sha256=<hex>"

if abs(now_unix() - int(timestamp)) > 300:
    return 401  # fuera de ventana

signed    = (timestamp + ".").encode() + raw_body
expected  = "sha256=" + hex(hmac_sha256(secret, signed))

if not constant_time_equals(expected, signature):
    return 401

# Python

import hmac, hashlib, time

secret = os.environ["CU_API_KEY"].encode()
raw = request.get_data()  # Flask: raw bytes
ts = request.headers.get("X-CU-Timestamp", "")
sig = request.headers.get("X-CU-Signature", "")

if not ts.isdigit() or abs(int(time.time()) - int(ts)) > 300:
    abort(401)

signed = f"{ts}.".encode() + raw
digest = hmac.new(secret, signed, hashlib.sha256).hexdigest()
if not hmac.compare_digest(f"sha256={digest}", sig):
    abort(401)

# Node.js

const crypto = require('crypto');

function verify(rawBody, headers, apiKey) {
  const ts = headers['x-cu-timestamp'] || '';
  const sig = headers['x-cu-signature'] || '';

  if (!/^\d+$/.test(ts) || Math.abs(Math.floor(Date.now() / 1000) - Number(ts)) > 300) {
    return false;
  }

  const signed = Buffer.concat([Buffer.from(`${ts}.`), rawBody]);
  const digest = crypto.createHmac('sha256', apiKey).update(signed).digest('hex');
  const expected = `sha256=${digest}`;

  return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(sig));
}

Importante: usa comparación en tiempo constante (hmac.compare_digest, crypto.timingSafeEqual). Nunca ==.

# Bash / curl (debug)

TS=1745336400
printf '%s.%s' "$TS" '<cuerpo_crudo>' | openssl dgst -sha256 -hmac "$CU_API_KEY"

# Qué debe responder tu webhook

  • Respuesta exitosa aceptada: HTTP 200, 201, 202 o 204. Cualquier otra cosa (incluido 299) se considera fallida y la reintentamos.
  • Sin cuerpo de respuesta requerido.
  • Timeout: 15 segundos por intento. Si no respondemos a tiempo, cuenta como fallo de ese intento.
  • Responde lo más rápido posible (idealmente <1s): encola el trabajo y responde 202.

# Reintentos

Si tu endpoint no responde con un status aceptado:

Intento Delay desde el anterior
1 0 s
2 30 s
3 120 s

Después del 3er intento dejamos de intentar. No hay cola persistente: si perdiste los 3 intentos tienes que re-emitir la consulta manualmente o usar el endpoint de replay.

# Idempotencia y deduplicación

  • Reenviamos el mismo uuid en cada reintento. Tu endpoint debe ser idempotente: si ves un uuid que ya procesaste, responde 200 sin volver a hacer el trabajo.
  • Cada reintento trae un X-CU-Timestamp nuevo y firma nueva. La ventana de tolerancia (±5 min) cierra replays viejos; el chequeo de uuid cierra duplicados dentro de la ventana.
  • Recomendación: persiste los uuid por al menos 24 horas.

# Ejemplo mínimo de receptor (Flask)

import base64, hmac, hashlib, os, json, time
from flask import Flask, request, abort

app = Flask(__name__)
SECRET = os.environ["CU_API_KEY"].encode()
PROCESSED = set()  # usa redis/db en prod

@app.post("/webhook/sc-premium")
def webhook():
    raw = request.get_data()
    ts = request.headers.get("X-CU-Timestamp", "")
    sig = request.headers.get("X-CU-Signature", "")

    if not ts.isdigit() or abs(int(time.time()) - int(ts)) > 300:
        abort(401)

    signed = f"{ts}.".encode() + raw
    digest = hmac.new(SECRET, signed, hashlib.sha256).hexdigest()
    if not hmac.compare_digest(f"sha256={digest}", sig):
        abort(401)

    payload = json.loads(raw)
    uid = payload["uuid"]
    if uid in PROCESSED:
        return "", 200
    PROCESSED.add(uid)

    if payload["status"] == "completed":
        pdf = base64.b64decode(payload["pdfBase64"])
        with open(f"/tmp/{uid}.pdf", "wb") as f:
            f.write(pdf)
    else:
        # log payload["errorMessage"]
        pass

    return "", 202

# 3. Consultar resultado — GET /v3/sc_premium/{uuid}

Consulta el estado actual de una solicitud por uuid. Autenticado con X-API-Key. No consume créditos.

Es la única vía para obtener el resultado en modo polling-only (cuando no enviaste webhookUrl), y también funciona como complemento al webhook si necesitas recuperar un PDF que se perdió en el primer intento.

# Request

curl https://api.consultaunica.mx/v3/sc_premium/9f2b7c1a-4d3e-4a5f-8b0c-2e1d0f9a8b77 \
  -H "X-API-Key: $CU_API_KEY"

# Respuesta

El shape es idéntico al cuerpo del webhook, con un estado adicional pending mientras la consulta aún se genera:

// pending — generación en curso; vuelve a consultar en unos segundos
{ "version": 1, "uuid": "...", "status": "pending", "pdfBase64": null, "errorMessage": null }
// completed — reporte listo
{ "version": 1, "uuid": "...", "status": "completed", "pdfBase64": "JVBE...", "errorMessage": null }
// failed — no se pudo generar
{ "version": 1, "uuid": "...", "status": "failed", "pdfBase64": null, "errorMessage": "..." }

# Errores

HTTP Caso detail
404 El uuid no existe o no pertenece a tu cuenta not_found
401 X-API-Key ausente, inválida o revocada ver 401 — Autenticación

# Cuándo usarlo

  • Modo polling-only: enviaste el submit sin webhookUrl. Consulta hasta que status deje de ser pending.
  • Tu webhook estuvo caído y quieres recuperar el reporte.
  • Necesitas verificar el estado antes de tomar una decisión en tu UI.

Frecuencia recomendada: no más de 1 request cada 5 segundos por uuid. Consultas muy frecuentes pueden devolver siempre pending sin avance real. Empieza polleando 30 s después del submit; el resultado típico tarda 30 s – 3 min y hasta 10 min en escenarios degradados.


# 4. Reintentar entrega — POST /v3/sc_premium/{uuid}/replay

Aplica sólo a solicitudes que se enviaron con webhookUrl. Solicitudes en modo polling-only no tienen URL a la cual reenviar y devuelven 422 — usa GET /v3/sc_premium/{uuid} para obtener el resultado.

Si las 3 entregas automáticas a tu webhook fallaron (o quieres una copia adicional), reintenta manualmente. Autenticado con X-API-Key. No consume créditos.

# Request

curl -X POST https://api.consultaunica.mx/v3/sc_premium/9f2b7c1a-4d3e-4a5f-8b0c-2e1d0f9a8b77/replay \
  -H "X-API-Key: $CU_API_KEY"

# Respuesta — 202 Accepted

{ "uuid": "9f2b7c1a-...", "scheduled": true }

Disparamos una nueva entrega al webhookUrl original en background, con los mismos 3 intentos y backoff que una entrega regular.

# Cooldown

Solo un replay por uuid cada 60 segundos. El segundo request dentro de esa ventana recibe 429:

HTTP/1.1 429 Too Many Requests
Retry-After: 47
{ "detail": "replay_cooldown_active" }

Retry-After indica los segundos restantes del cooldown.

# Errores

HTTP Caso detail
404 uuid no existe o no pertenece a tu cuenta not_found
409 La consulta aún está en status=pending (no hay nada que reenviar) replay_not_available_while_pending
422 La solicitud original se envió en modo polling-only (sin webhookUrl) replay_unavailable_polling_only
429 Cooldown activo por uuid replay_cooldown_active + Retry-After
401 Auth ver 401 — Autenticación

# Flujo típico de recuperación

  1. Consultas GET /sc_premium/{uuid}status=completed (o failed).
  2. Llamas POST /sc_premium/{uuid}/replay → 202.
  3. Tu endpoint webhook recibe el mismo body que habría recibido originalmente (mismo uuid, firma fresca con nuevo X-CU-Timestamp).

# 5. Flujo completo

# Modo webhook (push)

┌─────────┐                                         ┌──────────────────┐
│ Cliente │                                         │ Consulta Única   │
└────┬────┘                                         └────────┬─────────┘
     │   POST /v3/sc_premium                             │
     │   (X-API-Key, curp, webhookUrl)                       │
     │──────────────────────────────────────────────────────>│
     │                                                       │
     │   200 OK { uuid, message, curp, nss, fullname }       │
     │<──────────────────────────────────────────────────────│
     │                                                       │
     │                                 ... generación ...    │
     │                                                       │
     │   POST <webhookUrl>                                   │
     │   X-CU-Timestamp: <unix_ts>                           │
     │   X-CU-Signature: sha256=...                          │
     │   X-CU-Uuid: <uuid>                                   │
     │   { uuid, status, pdfBase64, errorMessage }           │
     │<──────────────────────────────────────────────────────│
     │                                                       │
     │   200 / 201 / 202 / 204                               │
     │──────────────────────────────────────────────────────>│

# Modo polling-only (pull)

┌─────────┐                                         ┌──────────────────┐
│ Cliente │                                         │ Consulta Única   │
└────┬────┘                                         └────────┬─────────┘
     │   POST /v3/sc_premium                                 │
     │   (X-API-Key, curp)            ← sin webhookUrl       │
     │──────────────────────────────────────────────────────>│
     │                                                       │
     │   200 OK { uuid, message, curp, nss, fullname }       │
     │<──────────────────────────────────────────────────────│
     │                                                       │
     │                                 ... generación ...    │
     │                                                       │
     │   GET /v3/sc_premium/{uuid}    (cada ~30 s)           │
     │──────────────────────────────────────────────────────>│
     │   200 OK { status: "pending", ... }                   │
     │<──────────────────────────────────────────────────────│
     │                       ...                             │
     │   GET /v3/sc_premium/{uuid}                           │
     │──────────────────────────────────────────────────────>│
     │   200 OK { status: "completed", pdfBase64: "JVB..." } │
     │<──────────────────────────────────────────────────────│

Tiempos típicos de generación: 30 s – 3 min entre submit y resultado. Diseña tu UX asumiendo hasta 10 minutos en casos degradados.


# 6. Preguntas frecuentes

¿Cuándo conviene usar polling-only y cuándo webhook?

  • Webhook (push) es la opción recomendada si tu backend tiene un endpoint HTTPS público: te enteras del resultado en cuanto está listo, sin polling extra.
  • Polling-only (pull) es ideal si corres en infraestructura sin endpoint público (jobs cron, máquinas detrás de NAT, scripts locales) o si quieres simplicidad operativa sin manejar firma HMAC ni reintentos en tu lado.

¿Puedo cambiar el webhookUrl después de enviar la solicitud? No. La URL se captura al momento del submit. Si necesitas otra, emite una nueva solicitud. Tampoco puedes pasar de polling-only a webhook (ni viceversa) en una solicitud existente.

¿Qué pasa si mi servidor está caído cuando entregamos? Hacemos 3 intentos (0s / 30s / 120s). Si todos fallan, puedes recuperarte sin pagar de nuevo: consulta GET /v3/sc_premium/{uuid} para ver el estado + descargar el PDF, o llama POST /v3/sc_premium/{uuid}/replay para reintentar la entrega (cooldown 60s).

¿Qué hago si mi cliente pierde la respuesta del submit por un error de red? Reintenta el mismo submit con un header Idempotency-Key estable. Si el primero sí llegó y se procesó, el retry devuelve la misma respuesta sin cobrar de nuevo. Ver Idempotency-Key (opcional).

¿Cómo pruebo en desarrollo? Usa un servicio tipo webhook.site, ngrok o smee.io para exponer tu endpoint local en HTTPS con IP pública. Localhost y túneles HTTP no pasan la validación. Si no quieres montar nada de eso, usa modo polling-only: omite webhookUrl y consulta el resultado por GET.

¿Cuántos créditos cuesta? 3 por cada submit exitoso. Los webhooks de entrega no cobran adicional.

¿El uuid es el mismo que guardan internamente? Sí. Úsalo como referencia si necesitas abrir soporte.


# 7. Modo Mock

Para pruebas de integración del lado del cliente, hay endpoints mock con prefijo /mock que simulan el contrato completo sin ejecutar la consulta real.

# Rutas

Productivo Mock equivalente
POST /v3/sc_premium POST /v3/mock/sc_premium
GET /v3/sc_premium/{uuid} GET /v3/mock/sc_premium/{uuid}
POST /v3/sc_premium/{uuid}/replay POST /v3/mock/sc_premium/{uuid}/replay

Migración: elimina /mock del path. El header X-Mock-Mode será ignorado en producción.

# Header X-Mock-Mode

Valor Comportamiento
success Tras ~1 s, webhook con status=completed y un PDF dummy en pdfBase64.
failed Tras ~1 s, webhook con status=failed y errorMessage="mock_service_error".
pending No dispara webhook. GET siempre regresa status=pending.
(ausente) Default success.

Valores no permitidos → 400 invalid_mock_mode.

Polling-only en mock: el mock también soporta omitir webhookUrl. Si lo omites, no dispararemos webhook (independiente del modo) y replay regresará 422 replay_unavailable_polling_only. El GET sigue funcionando idéntico a producción.

# Comportamiento

  • Los uuids generados viven 1 hora en cache (Redis); luego expiran y GET/replay regresan 404.
  • El webhook se dispara al webhook_url provisto, firmado con tu api_key (mismos headers X-CU-Signature, X-CU-Timestamp, X-CU-Uuid que producción). Un único intento, sin reintentos. En polling-only no se dispara.
  • Idempotency-Key se acepta por compatibilidad pero se ignora en mock.
  • Datos mock fijos en la respuesta del POST: nss="12345678901", fullname="JUAN MOCK PEREZ".
  • replay mantiene el cooldown de 60 s y regresa 409 si el modo es pending.
  • Mock no consume créditos. Pruébalo todas las veces que quieras.
  • Rate limit: 5 req/min por X-API-Key en POST /mock/sc_premium, independiente del bucket de producción.
  • La respuesta del POST sí incluye el header X-Remaining-Requests con tu saldo actual (sin deducción, ya que el mock no cobra). Útil para que tu cliente ejercite la lógica de alertas igual que en producción.

# Ejemplos

Submit success:

curl -X POST https://api.consultaunica.mx/v3/mock/sc_premium \
  -H "X-API-Key: $CU_API_KEY" \
  -H "Content-Type: application/json" \
  -H "X-Mock-Mode: success" \
  -d '{"curp":"PEGJ800101HDFRRN09","webhookUrl":"https://tuapp.com/cb"}'

Submit que simula error del servicio:

curl -X POST https://api.consultaunica.mx/v3/mock/sc_premium \
  -H "X-API-Key: $CU_API_KEY" \
  -H "Content-Type: application/json" \
  -H "X-Mock-Mode: failed" \
  -d '{"curp":"PEGJ800101HDFRRN09","webhookUrl":"https://tuapp.com/cb"}'

Submit pending (no dispara webhook):

curl -X POST https://api.consultaunica.mx/v3/mock/sc_premium \
  -H "X-API-Key: $CU_API_KEY" \
  -H "Content-Type: application/json" \
  -H "X-Mock-Mode: pending" \
  -d '{"curp":"PEGJ800101HDFRRN09","webhookUrl":"https://tuapp.com/cb"}'

Polling y replay:

curl https://api.consultaunica.mx/v3/mock/sc_premium/<uuid> \
  -H "X-API-Key: $CU_API_KEY"

curl -X POST https://api.consultaunica.mx/v3/mock/sc_premium/<uuid>/replay \
  -H "X-API-Key: $CU_API_KEY"

# 8. Changelog

  • v1.3webhookUrl ahora es opcional. Omítelo para usar modo polling-only y consultar el resultado por GET /v3/sc_premium/{uuid}. Replay sobre solicitudes polling-only devuelve 422 replay_unavailable_polling_only. Mismo comportamiento en /mock.
  • v1.2 — endpoints mock /v3/mock/sc_premium* con header X-Mock-Mode (success/failed/pending) para pruebas de integración sin consumir créditos.
  • v1.1Idempotency-Key opcional en submit, rate limit específico de 5 req/min, endpoints de consulta y replay, webhook body con campo version: 1 firmado.
  • v1.0 — primer release del endpoint público sc_premium.