Saltar al contenido principal
Versión: 1.21.1

📖 Guía Paso a Paso: Tu Primera Emisión de Certificado

En esta guía aprenderás a emitir tu primer certificado digital usando MATICERTS. Seguiremos un flujo completo desde la autenticación hasta la descarga del certificado (.zip con el .p12 y su PIN).

Todos los ejemplos usan Sandbox (https://sandbox-api.maticerts.com/api/v1). En Producción la base es https://api.maticerts.com/api/v1.


📋 Requisitos Previos​

Antes de comenzar, asegúrate de tener:

  • ✅ Una cuenta en MATICERTS activa, con el email verificado y la empresa activa (si no, el login se rechaza)
  • ✅ Un Personal Access Token (PAT) o credenciales de usuario
  • ✅ Saber qué tipo de emisión vas a usar: BIOMETRIC (con verificación biométrica de identidad) o DOCUMENTARY (documental, sin biometría). Los tipos que puede usar tu empresa dependen de company.issuance_provider (ver el Paso 1)
  • ✅ Cupo disponible para ese tipo de emisión y para la vigencia que vas a pedir, de 1 o 2 años. Consúltalo con GET /api/v1/quota/status: en by_flow.BIOMETRIC y by_flow.DOCUMENTARY, available_1_year y available_2_year son los certificados que puedes pedir para cada vigencia (pospago más prepago). Si no tienes cupo para ese tipo y esa vigencia, la creación de la solicitud responde 402; puedes comprar certificados del mismo tipo con POST /api/v1/orders
  • ✅ Una herramienta para hacer peticiones HTTP (cURL, Postman o tu lenguaje favorito)
  • ✅ Datos del titular: NIT o cédula sin dígito de verificación (dni), razón social, dirección, país y ciudad, tipo de organización y, si es Persona Jurídica, tipo de documento constitutivo
  • ✅ Datos del representante legal: tipo y número de documento y nombre. En una solicitud BIOMETRIC, también nombres y apellidos por separado y email
  • ✅ Aceptar la versión vigente de los Términos y Condiciones (accept_terms + terms_version_id)
  • ✅ En una solicitud DOCUMENTARY: 2 a 3 soportes enviados en Base64, por defecto de máximo 7 MB cada uno y 10 MB en total (se recomiendan PDF, JPG o PNG; el servidor no valida el tipo de archivo)

Paso 1️⃣: Obtén tu Token de Acceso​

Opción A: Usando Personal Access Token (PAT)​

Si ya tienes un PAT, puedes usarlo directamente; es el método recomendado para integraciones backend.

Ten en cuenta que los PAT caducan a los 90 días de su creación. Pasado ese plazo, toda petición con el token responde 401 con "message": "No autenticado.".

📅 Vigencia de los PAT

Al crear un PAT puedes enviar expires_in_days (de 1 a 365): ese valor define la fecha expires_at que muestran la respuesta y GET /api/v1/tokens. El token es válido 90 días desde su creación en todos los casos: si pediste más, responde 401 desde el día 90; si pediste menos, sigue autenticando hasta el día 90 aunque ya no aparezca en GET /api/v1/tokens. Planifica la rotación a los 90 días.

  • Créalos desde la aplicación web o con POST /api/v1/tokens (máximo 10 intentos de creación por usuario en 24 horas, contando también los rechazados por validación; si superas el límite, la API responde 429). El valor del token solo aparece en esa respuesta: guárdalo en un lugar seguro.
  • Consulta sus datos (id, expires_at) con GET /api/v1/tokens; recuerda que la vigencia real es de 90 días desde la creación.
  • Antes de que venza, renuévalo con POST /api/v1/tokens/{id}/renew: se emite un token nuevo y el anterior queda revocado, así que actualiza el valor en tu integración. El token renovado vale 90 días.
# Simplemente guarda tu PAT en una variable
export ACCESS_TOKEN="tu-pat-aqui"

Opción B: Autenticarse como Usuario​

Si prefieres autenticarte con credenciales de usuario:

curl -X POST "https://sandbox-api.maticerts.com/api/v1/auth/login" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"email": "tu-email@empresa.com",
"password": "tu-contraseña"
}'

Respuesta esperada (200):

{
"success": true,
"message": "Inicio de sesión exitoso.",
"access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOi...",
"expires_at": "2026-12-26 10:00:00",
"user": { "id": 40, "email": "tu-email@empresa.com", "name": "JUAN PÉREZ", "...": "..." },
"company": {
"has_agreement": false,
"company_type_id": 1,
"issuance_provider": "viafirma",
"company_name": "MI EMPRESA S.A.S.",
"uuid": "550e8400-e29b-41d4-a716-446655440000"
}
}
  • El token se envía en la cabecera Authorization: Bearer <access_token>. Vence en la fecha de expires_at (formato Y-m-d H:i:s, 90 días por defecto) y aparece en GET /api/v1/tokens con tu email como nombre.

  • company.issuance_provider indica qué tipos de emisión puede usar tu empresa y nunca es null:

    • viafirma: solo BIOMETRIC.
    • mail: solo DOCUMENTARY.
    • dual: los dos. Eliges el tipo en cada solicitud con issuance_flow; si no lo envías, la solicitud es DOCUMENTARY.

    Si tu integración recibe un valor que no reconoce, no crees solicitudes y contacta a soporte. company es una foto del inicio de sesión: los valores vigentes de issuance_provider y has_agreement están en GET /api/v1/quota/status.

  • Errores: 404 con Credenciales inválidas., y también 404 cuando el usuario o la empresa están inactivos (el message indica cuál); 400 si el email no está verificado; 422 si email o password faltan o no son válidos.

Guarda tu Token

Almacena el token en una variable de entorno para usarlo en los siguientes pasos:

export ACCESS_TOKEN="tu-token-aqui"

Paso 2️⃣: Verifica tu Token​

Antes de continuar, verifica que tu token funciona correctamente consultando el usuario dueño del token:

curl -X GET "https://sandbox-api.maticerts.com/api/v1/auth/user" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Accept: application/json"

Respuesta esperada (200):

{
"success": true,
"dataRecords": {
"data": [
{ "id": 40, "email": "tu-email@empresa.com", "name": "JUAN PÉREZ", "...": "..." }
]
}
}

✅ Si recibes 200 con "success": true y tus datos en dataRecords.data[0] (un arreglo con un único elemento), tu token funciona. Si recibes 401 con "message": "No autenticado.", el token falta, es inválido, expiró o fue revocado.


Paso 3️⃣: Crea una Solicitud de Certificado​

Antes de crear la solicitud:

  1. Consulta GET /api/v1/terms/current (público) y guarda dataRecords.id: lo enviarás como terms_version_id.
  2. Obtén los IDs de los catálogos con GET /api/v1/countries, /cities, /identity-documents, /organization-type y /entity-document-types (ver Datos Maestros).
  3. Verifica que tienes cupo para el tipo de emisión y la vigencia (life) con GET /api/v1/quota/status: by_flow.<TIPO>.available_1_year o by_flow.<TIPO>.available_2_year.

El ejemplo crea una solicitud BIOMETRIC (con verificación biométrica). issuance_flow es opcional: si tu empresa es viafirma puedes omitirlo; si es dual, envíalo, porque sin él la solicitud se crea DOCUMENTARY y te pedirá adjuntos.

curl -X POST "https://sandbox-api.maticerts.com/api/v1/certificate-request" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"country_id": 45,
"city_id": 149,
"identity_document_id": 1,
"type_organization_id": 1,
"entity_document_type_id": 1,
"document_number": "1234567890",
"address": "Calle 123 # 45-67",
"legal_rep_first_name": "JUAN PABLO",
"legal_rep_last_name": "PÉREZ GÓMEZ",
"legal_rep_email": "juan.perez@empresa.com",
"company_name": "MI EMPRESA S.A.S.",
"dni": "900455420",
"mobile": "3001234567",
"life": 1,
"issuance_flow": "BIOMETRIC",
"accept_terms": true,
"terms_version_id": 3
}'

Respuesta esperada (200):

{
"success": true,
"message": "Solicitud de certificado creada exitosamente",
"dataRecords": {
"id": 636,
"company_id": 1,
"company_name": "MI EMPRESA S.A.S.",
"dni": "900455420",
"dv": 8,
"country_id": 45,
"city_id": 149,
"identity_document_id": 1,
"type_organization_id": 1,
"entity_document_type_id": 1,
"document_number": "1234567890",
"address": "Calle 123 # 45-67",
"legal_representative": null,
"legal_rep_first_name": "JUAN PABLO",
"legal_rep_last_name": "PÉREZ GÓMEZ",
"legal_rep_email": "juan.perez@empresa.com",
"info": "",
"mobile": "3001234567",
"phone": null,
"life": 1,
"issuance_flow": "BIOMETRIC",
"request_status": "PROCESSING",
"created_at": "08-10-2026 10:15:00 am",
"updated_at": "08-10-2026 10:15:00 am",
"created_at_formatted": "08-10-2026 10:15:00 am",
"updated_at_formatted": "08-10-2026 10:15:00 am",
"expiration_date_formatted": null,
"files": []
}
}
  • dni es el NIT o la cédula del titular sin dígito de verificación (lo calcula el servidor).
  • entity_document_type_id es obligatorio cuando type_organization_id es 1 (Persona Jurídica).
  • issuance_flow (opcional): BIOMETRIC o DOCUMENTARY. Sin él se usa el tipo por defecto de tu empresa: BIOMETRIC con viafirma y DOCUMENTARY con mail o dual. Debe ser un tipo habilitado para tu empresa. Queda guardado en la solicitud (dataRecords.issuance_flow) y no cambia después.
  • Solicitud BIOMETRIC: legal_rep_email, legal_rep_first_name y legal_rep_last_name son obligatorios; los adjuntos son opcionales.
  • Solicitud DOCUMENTARY: attachments es obligatorio, con 2 a 3 archivos en Base64 ([{"name": "rut.pdf", "base64": "data:application/pdf;base64,..."}]), máximo 7 MB por archivo y 10 MB en total.
  • La solicitud consume un certificado del mismo tipo de emisión y de la vigencia pedida (life): primero del cupo pospago de ese tipo, si tienes, y si no, de un certificado prepago de ese tipo y esa vigencia.
  • Límite: 10 solicitudes por minuto por usuario.
Guarda el ID de la solicitud

Para los siguientes pasos necesitarás el id numérico (dataRecords.id, que en el ejemplo es 636).

Una solicitud BIOMETRIC nace en PROCESSING y la emisión arranca sola. Una DOCUMENTARY nace en DRAFT.

La respuesta no incluye el uuid. Obtenlo con GET /api/v1/certificate-request/{id} (en dataRecords.data[0].uuid); lo necesitarás para descargar o revocar el certificado.

Posibles errores:

  • 400 con "message": "Los datos de la solicitud son inválidos." y el detalle por campo en errors. Si terms_version_id no es la versión vigente, vuelve a consultar GET /api/v1/terms/current.
  • 400 si ya existe una solicitud en proceso con el mismo NIT y DV.
  • 400 con error_code por el tipo de emisión (message trae el motivo):
    • INVALID_ISSUANCE_FLOW: el valor no es BIOMETRIC ni DOCUMENTARY.
    • ISSUANCE_FLOW_NOT_ALLOWED: tu empresa no tiene habilitado ese tipo.
    • ISSUANCE_FLOW_UNAVAILABLE: la emisión biométrica no está disponible en este momento. La solicitud no se crea ni consume cupo; reintenta más tarde o, si tu empresa lo permite, usa DOCUMENTARY.
  • 402 con error_code = QUOTA_EXHAUSTED si no tienes cupo para ese tipo de emisión y esa vigencia. available_in_other_flows lista los otros tipos habilitados para tu empresa que sí tienen cupo para esa vigencia (puede venir vacío):
{
"success": false,
"error_code": "QUOTA_EXHAUSTED",
"message": "No tiene certificados disponibles de tipo Documental (sin biometría) para vigencia de 1 año(s). Debe adquirir un paquete de certificados para continuar.",
"issuance_flow": "DOCUMENTARY",
"vigencia": 1,
"available_in_other_flows": [{ "issuance_flow": "BIOMETRIC", "available": 41 }]
}

Paso 4️⃣: Inicia la Emisión y la Verificación de Identidad​

El flujo depende del tipo de emisión de la solicitud (issuance_flow, en la respuesta de creación y en GET /api/v1/certificate-request/{id}), no de la empresa: una empresa dual tiene solicitudes de los dos tipos.

Solicitud BIOMETRIC (con verificación biométrica)​

No tienes que disparar la emisión: se encola automáticamente al crear la solicitud, usando el legal_rep_email que informaste. Consulta el avance con GET /api/v1/certificate-request/{id}/issuance (Paso 5).

El representante legal debe completar la verificación de identidad biométrica (KYC):

  1. Cuando dataRecords.data.remote_status sea accreditation, obtén el enlace con GET /api/v1/certificate-request/{id}/kyc-link. Viene en data.link (no en dataRecords) y también se expone en dataRecords.data.kyc_accreditation_link de GET /api/v1/certificate-request/{id}/issuance. Si lo pides antes de ese estado, responde 422 (el message indica el estado remoto actual). Justo después de crear la solicitud, mientras el trámite de Viafirma aún no existe, responde 404 (Recurso no encontrado.). En ambos casos vuelve a consultar más tarde. No lo confundas con el 404 Solicitud de certificado no encontrada., que indica que el id no existe o pertenece a otra empresa.
  2. Envíaselo al suscriptor. Tiene hasta expires_at (96 horas desde el envío del trámite) para completarlo. 24 horas antes del vencimiento se envía un aviso de último llamado al correo de la empresa.
  3. Si no completa la verificación a tiempo, el trámite vence, la solicitud se cancela y el cupo se reintegra.
curl -X GET "https://sandbox-api.maticerts.com/api/v1/certificate-request/636/kyc-link" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Accept: application/json"
Reintentos de la emisión biométrica

En una solicitud BIOMETRIC, POST /api/v1/certificate-request/{id}/issue solo se usa para reintentar cuando el trámite vigente quedó en FAILED, FAILED_RECOVERABLE o EXPIRED. Debe incluir email_certificate y, solo para persona jurídica (FE-PJ), organization_type (RM, PROP, RUNEOL, RNT, ESAL, ESOL, JUEGOS o EXTRANJERAS); en persona natural (FE-PN) está prohibido enviarlo. Responde 201 con dataRecords.status = "submitted". Si ya hay una emisión en curso, responde 409 o 422: no lo llames justo después de crear la solicitud.

No envíes provider en el cuerpo: el proveedor lo decide el tipo de emisión de la solicitud. Un provider que no corresponde a ese tipo responde 422 con error_code = ISSUANCE_PROVIDER_MISMATCH.

Solicitud DOCUMENTARY (documental)​

Dispara la emisión. La solicitud necesita al menos un adjunto de tipo ATTACHED: los attachments que enviaste al crearla ya cuentan como tales (puedes agregar más con POST /api/v1/certificate-request/{id}/files).

curl -X POST "https://sandbox-api.maticerts.com/api/v1/certificate-request/636/issue" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{}'

Responde 200 con dataRecords.status = "sent" y la solicitud pasa a PROCESSING. Sin adjuntos ATTACHED responde 400 o 422.

Límite de /issue: 5 llamadas por minuto por usuario (429 si lo superas).

Comportamiento en Sandbox
  • En una solicitud DOCUMENTARY, unos 30 segundos después de un POST /api/v1/certificate-request/{id}/issue correcto (el tiempo depende de la cola de trabajos) se genera un certificado de prueba autofirmado: la solicitud pasa de PROCESSING a PROCESSED y se emite el evento certificate_request.status_changed. Así puedes probar el monitoreo y la descarga sin esperar.
  • Ese certificado se entrega en un .zip protegido con contraseña: el NIT del titular, o el NIT seguido del DV si es persona jurídica. Dentro está el .p12, que se abre con su PIN.
  • Los certificados de Sandbox son de prueba y no tienen validez legal; no los uses para firmar documentos reales.

Paso 5️⃣: Monitorea el Estado de la Emisión​

Consulta el avance de la emisión con el id numérico de la solicitud:

curl -X GET "https://sandbox-api.maticerts.com/api/v1/certificate-request/636/issuance" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Accept: application/json"

Respuesta (solicitud BIOMETRIC, ejemplo):

{
"success": true,
"message": "Estado del trámite Viafirma.",
"dataRecords": {
"provider": "viafirma",
"status": "processing",
"message": "Estado del trámite Viafirma.",
"external_id": "D4AZEQQG6",
"resource_id": 5,
"data": {
"public_id": "fab8d88e0c5a4cab8bfc2b72be05a098",
"profile_type": "FE-PN",
"identity_type": "IDC",
"internal_state": "POLLING",
"remote_status": "accreditation",
"revocation_code": null,
"revoked_at": null,
"submitted_at": "2026-10-08T15:15:30.000000Z",
"expires_at": "2026-10-12T15:15:30.000000Z",
"history_count": 3,
"kyc_accreditation_link": "https://signup.metamap.com/?merchantToken=...",
"kyc_flow_completed_at": null,
"poll_attempts": 12,
"last_error_code": null,
"last_error_message": null,
"accredited_status": null,
"last_public_note": null,
"public_notes": [],
"can_reject": true
}
}
}
  • dataRecords.status vale submitted, processing, ready (certificado listo) o failed (revocado, fallido, vencido o cancelado). En los fallidos, vencidos y cancelados el motivo está en dataRecords.data.last_error_message; en los revocados (internal_state = REVOKED) ese campo puede venir vacío o con un error anterior: la revocación se identifica por internal_state y revoked_at.
  • En una solicitud DOCUMENTARY, dataRecords.data solo trae request_status, y status es ready cuando la solicitud está PROCESSED y failed cuando está REJECTED.
  • Mientras el titular no complete la verificación de identidad, el trámite no avanza; el enlace está en dataRecords.data.kyc_accreditation_link (ver Paso 4).
  • Si el id no existe o es de otra empresa, este endpoint responde 404.

El estado de la solicitud está en request_status de GET /api/v1/certificate-request/{id} (la solicitud viene en dataRecords.data[0], junto con su uuid): DRAFT, SENT, PENDING, ACCEPTED, PROCESSING, PROCESSED (certificado emitido), REJECTED, REVOKED, EXPIRED o CANCELLED. Si el id no existe o pertenece a otra empresa, ese endpoint responde 200 con dataRecords.data = [null].

Polling o webhooks

No consultes más de una vez por minuto: el avance en Viafirma se sincroniza cada 60 segundos y la verificación de identidad puede tardar hasta 96 horas. El límite general es de 60 peticiones por minuto por usuario, compartido por todas tus llamadas; si lo superas recibes 429. Deja de consultar cuando dataRecords.status sea ready o failed, y antes de descargar confirma que request_status es PROCESSED. Mejor aún, registra un webhook con el evento certificate_request.status_changed y espera new_status = "PROCESSED" (ver Webhooks).


Paso 6️⃣: Descarga el Certificado​

Cuando request_status sea PROCESSED, toma el uuid de la solicitud (GET /api/v1/certificate-request/{id} → dataRecords.data[0].uuid) y descarga el certificado en Base64. La descarga usa el UUID, no el ID numérico.

UUID="550e8400-e29b-41d4-a716-446655440000"

curl -X GET "https://sandbox-api.maticerts.com/api/v1/certificate-request/$UUID/issuance/download/base64" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Accept: application/json" \
-o resp.json

# Decodifica el .zip y extrae el .p12
jq -r .dataRecords.data.p12_base64 resp.json | base64 -d > certificado.zip
unzip certificado.zip
jq -r .dataRecords.data.p12_pin resp.json # PIN para abrir el .p12

Respuesta esperada (200):

{
"success": true,
"dataRecords": {
"data": {
"p12_pin": "X3kP9aQ1mZv7nR2sYwQe8BcZ4uVhKpTf",
"p12_filename": "empresa-sas_636.zip",
"p12_base64": "UEsDBBQAAAAI..."
}
}
}
Es un .zip

p12_base64 es un .zip que contiene el .p12: decodifícalo, descomprímelo y abre el .p12 con p12_pin. Si p12_pin vale NO_PIN, la solicitud no tiene PIN registrado.

En una solicitud DOCUMENTARY (también en Sandbox) el .zip viene protegido con contraseña: el NIT del titular, o el NIT seguido del DV si es persona jurídica. Si unzip no admite el cifrado del archivo, ábrelo con 7-Zip (7z x certificado.zip) u otra herramienta compatible con ZIP AES.

Alternativa: GET /api/v1/certificate-request/{uuid}/issuance/download devuelve download_url (URL firmada válida 24 horas, hasta expires_at; puede venir en null si el almacenamiento no admite URLs firmadas), p12_filename y p12_pin.

Posibles errores:

  • 409: la solicitud no está en PROCESSED (revocada, vencida, cancelada, rechazada o aún en trámite); el estado actual viene en status.
  • 404: la solicitud no existe o es de otra empresa, todavía no hay certificado emitido, o el archivo ya no está disponible.
  • 410 (solo en /download/base64): el archivo no se pudo leer.

✅ ¡Tu certificado está listo para usar!


Paso 7️⃣: Verifica el Certificado Localmente​

La API no tiene un endpoint para validar certificados. Puedes revisar el .p12 descargado con OpenSSL, usando el PIN de p12_pin:

unzip empresa-sas_636.zip
P12_PIN="<valor de p12_pin>"
openssl pkcs12 -in empresa-sas_636.p12 -nokeys -passin pass:"$P12_PIN" \
| openssl x509 -noout -subject -issuer -serial -dates

Comprueba que el sujeto corresponde al titular y que las fechas notBefore/notAfter son las que esperas. Si OpenSSL 3 rechaza el archivo por un algoritmo no soportado, repite el comando con la opción -legacy.


🎉 ¡Felicidades!​

Has completado el flujo de emisión de certificados. Ahora puedes:

  • 📝 Firmar documentos electrónicos con el certificado descargado
  • 📊 Consultar tus solicitudes con GET /api/v1/certificate-request y los certificados próximos a vencer con GET /api/v1/certificates/expiring (los dos aceptan ?issuance_flow=BIOMETRIC o ?issuance_flow=DOCUMENTARY para filtrar por tipo de emisión)
  • 🔄 Renovar por 1 año más los certificados biométricos de 1 año, desde la emisión hasta el fin del periodo de gracia
  • ⛔ Revocar un certificado biométrico emitido (irreversible)
  • 🔔 Recibir los cambios de estado en tu sistema mediante webhooks

🔄 Después de la Emisión​

Renovación (certificados biométricos de 1 año)​

Puedes extender el certificado 1 año más sin reemitirlo. La renovación solo existe para certificados emitidos en solicitudes BIOMETRIC y se cotiza siempre con las tarifas de la emisión biométrica. Se puede renovar desde que se emite y hasta el fin del periodo de gracia (revocation_date), que es la fecha de vencimiento más 15 días por defecto.

  • Consulta si se puede renovar y cuánto cuesta con GET /api/v1/certificate-request/{id}/issuance/renewal ({id} es el ID numérico). La respuesta trae eligible, reason (NOT_RENEWABLE_PROVIDER, NOT_ACTIVE, MAX_TERM o GRACE_EXPIRED), commercial_expiration_date, revocation_date, in_grace_period, quote y pending_order.
  • Genera la orden con POST /api/v1/certificate-request/{id}/issuance/renew (sin cuerpo). Si ya hay una orden de renovación pendiente, se devuelve esa misma; si el certificado no se puede renovar, responde 422 con reason.
  • Esta llamada no cobra y la orden no trae los datos de pago. Pídelos con POST /api/v1/orders/{uuid}/retry (devuelve acceptance_token e integrity_hash) y paga con el widget de WOMPI, o envía ese acceptance_token junto con payment_source_id (fuente de pago tokenizada) y payment_method a POST /api/v1/orders/{uuid}/pay. El uuid de la orden viene en data.uuid de la respuesta de renew, no en dataRecords.

Si no se renueva dentro del periodo de gracia, el certificado se revoca automáticamente.

Revocación de un certificado emitido (irreversible)​

Usa POST /api/v1/certificate-request/{uuid}/revoke, con el UUID de la solicitud. El cuerpo lleva:

  • revoking_code: el código de dataRecords.data.revocation_code en GET /api/v1/certificate-request/{id}/issuance.
  • revocation_reason: 0, 1, 2, 3, 4, 5, 9 o 10, según RFC 5280.

Solo funciona con solicitudes BIOMETRIC en estado PROCESSED.

Rechazo de una solicitud en curso (irreversible)​

Si los datos quedaron mal y el certificado todavía no se ha emitido, usa POST /api/v1/certificate-request/{id}/issuance/reject (con el ID numérico) y un reason de 5 a 400 caracteres. La solicitud BIOMETRIC debe estar en PROCESSING y sin certificado. El rechazo la cancela y te devuelve el cupo (dataRecords.quota_released); después, crea una solicitud nueva.

Antes de rechazar, revisa can_reject en GET /api/v1/certificate-request/{id}/issuance: es solo orientativo, porque la decisión final la toma Viafirma (409 si no lo admite). Para anular un certificado ya emitido, usa la revocación.

Más detalle en Emisión de Certificados y Viafirma.


🔔 Recomendado: Webhooks en lugar de Polling​

Registra una URL con POST /api/v1/webhooks (name, url, events; description es opcional; máximo 5 webhooks por empresa). El secret de firma no se devuelve al crear el webhook: obtenlo con POST /api/v1/webhooks/{id}/rotate-secret y guárdalo. Es la única respuesta que lo expone, y al rotarlo el anterior deja de valer de inmediato.

Generan entregas los eventos certificate_request.created, certificate_request.status_changed, certificate_request.ai_processed, certificate_request.file_uploaded y certificate_request.deleted y, desde la 1.21.1, payment.approved (la orden pasa a PAID) y payment.failed (la orden pasa a FAILED porque se rechazó el pago). El catálogo está en GET /api/v1/webhooks/events. certificate.expiring se acepta en la suscripción, pero todavía no genera entregas.

Los eventos de pago llegan como máximo una vez por cada cambio de estado de la orden (una orden rechazada puede recibir después payment.approved si otro intento de pago de esa orden se aprueba) y traen en data el order_id (el UUID de la orden, el que acepta GET /api/v1/orders/{uuid}), el order_type (QUOTA_PURCHASE = compra de certificados; CERTIFICATE_RENEWAL = renovación, que no agrega cupos), issuance_flow, quantity y vigencia de la compra, los datos de la transacción y paid_at (aprobado) o reason (rechazado). Son una alternativa a consultar GET /api/v1/orders/{uuid} una y otra vez, pero mantén esa consulta como respaldo: las entregas no se reintentan. Ejemplo completo en Webhooks de pagos.

certificate_request.created y certificate_request.status_changed traen en data.issuance_flow el tipo de emisión de la solicitud (BIOMETRIC o DOCUMENTARY).

Cada entrega es un POST JSON con las cabeceras X-Webhook-Event, X-Webhook-Source y X-Webhook-Sig: t=<timestamp>,v1=<firma>, donde v1 es el HMAC-SHA256 (hexadecimal) del cuerpo crudo calculado con tu secret. Verifica la firma con el cuerpo tal como lo recibiste, sin volver a serializarlo.

Entrega sin reintentos

Cada evento se envía una sola vez y el receptor tiene 10 segundos para responder. Solo cuentan como fallo los errores de conexión y los timeouts; las respuestas HTTP 4xx/5xx se registran como entregadas, con su código de estado, y no se reenvían. Tras 10 fallos consecutivos el webhook se desactiva (is_active = false); puedes reactivarlo con PUT /api/v1/webhooks/{id} enviando {"is_active": true}. Consulta el historial con GET /api/v1/webhooks/{id}/deliveries. Como complemento, usa polling (Paso 5) para conciliar los eventos que se puedan perder.

Más detalle en la referencia de Webhooks.


🆘 Solución de Problemas​

401 — "No autenticado."​

  • Verifica que el token no haya expirado (los PAT vencen a los 90 días de su creación, aunque expires_at indique otra fecha) ni haya sido revocado
  • Comprueba que envías la cabecera Authorization: Bearer <token>
  • Crea un PAT nuevo, o renueva el anterior con POST /api/v1/tokens/{id}/renew autenticándote con otra credencial válida (un token vencido no sirve para renovarse a sí mismo)

403​

  • La empresa de tu usuario está inactiva: contacta a soporte

La solicitud no aparece (dataRecords.data = [null]) o recibes 404 al descargar​

  • GET /api/v1/certificate-request/{id} usa el id numérico; las descargas usan el uuid de la solicitud
  • Verifica que la solicitud pertenece a la empresa de tu usuario
  • 404 "Archivo de certificado no encontrado.": la solicitud está en PROCESSED, pero no hay un archivo de certificado disponible para descargar por la API (si aún no está en PROCESSED recibes 409, no 404). Contacta a soporte con el UUID de la solicitud

409 al descargar​

  • El certificado solo se entrega cuando request_status = PROCESSED; si está revocado, vencido, cancelado, rechazado o aún en trámite, no se puede descargar. El campo status de la respuesta trae el estado actual; si es DRAFT, la solicitud aún no se ha enviado a emisión: envíala con POST /api/v1/certificate-request/{id}/issue

400 — "Los datos de la solicitud son inválidos." / 422 — "Los datos proporcionados no son válidos."​

  • El 400 corresponde a la creación de solicitudes y a la carga de archivos (POST /api/v1/certificate-request/{id}/files); los demás endpoints responden 422 ante datos inválidos
  • Revisa el detalle en errors: campos obligatorios, valores de catálogo, terms_version_id vigente y adjuntos (tamaño y tipo)

402 — sin cupo / 429 — demasiadas peticiones​

  • 402 (QUOTA_EXHAUSTED): no tienes cupo para ese tipo de emisión y esa vigencia. Compra certificados del mismo tipo y la misma vigencia con POST /api/v1/orders (issuance_flow, vigencia y quantity), o, si available_in_other_flows trae otro tipo habilitado con cupo, crea la solicitud con ese tipo
  • 429: espera un minuto. Límites: 60 peticiones por minuto en general, 10 creaciones de solicitud por minuto, 20 cargas de archivos por minuto y 5 emisiones por minuto

Para más ayuda, consulta la guía de Solución de Problemas y la referencia de la API: Solicitudes de Certificado, Emisión de Certificados, Viafirma, Webhooks y Tokens.