🔧 Solución de Problemas
Aquí encontrarás soluciones a los problemas más comunes al usar MATICERTS.
Los ejemplos usan la URL de Sandbox (https://sandbox-api.maticerts.com/api/v1). En Producción usa https://api.maticerts.com/api/v1.
🔐 Problemas de Autenticación
Error 401: "No autenticado."
Síntomas (HTTP 401):
{
"success": false,
"message": "No autenticado."
}
La API devuelve la misma respuesta si el token Bearer falta, está mal formado, expiró o fue revocado.
Causas posibles:
- Falta la cabecera
Authorizationo el prefijoBearer - Token mal copiado (incompleto o con espacios)
- Token expirado: los tokens emitidos por
POST /api/v1/auth/loginy los Personal Access Tokens (PAT) vencen a los 90 días de su emisión. En el token de login, el campoexpires_atde la respuesta indica la fecha exacta; en un PAT creado conexpires_in_daysdistinto de 90, no (ver la advertencia al final de esta sección). - Token revocado con
GET /api/v1/auth/logout,DELETE /api/v1/tokens/{id}oPOST /api/v1/tokens/revoke-all
Soluciones:
-
Verifica el formato del token:
# ❌ INCORRECTOcurl -H "Authorization: eyJhbGciOiJIUzI1NiIs..."# ✅ CORRECTOcurl -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..." -
Comprueba si el token sigue vigente:
curl -X GET "https://sandbox-api.maticerts.com/api/v1/auth/user" \-H "Authorization: Bearer $TOKEN"# 200 = vigente · 401 = expirado, revocado o inválido -
Obtén un token nuevo con login (dura 90 días por defecto):
curl -X POST "https://sandbox-api.maticerts.com/api/v1/auth/login" \-H "Content-Type: application/json" \-d '{"email": "tu-email@empresa.com","password": "tu-contraseña"}' -
Si usas un Personal Access Token (PAT):
- Un token expirado o revocado no sirve para consultarse ni renovarse a sí mismo. Autentícate primero con un token válido: el de sesión que obtienes con login (paso 3) u otro PAT vigente.
- Lista tus tokens vigentes con
GET /api/v1/tokens(o en la aplicación web: Ajustes → Tokens de API). Los tokens expirados o revocados no aparecen en ese listado, así que guarda elidque devuelve la creación. ConGET /api/v1/tokens/{id}consultas el estado de un token (revoked,expires_at). - Si expiró o fue revocado, renuévalo con
POST /api/v1/tokens/{id}/renew. Se emite un token nuevo de 90 días con los mismos alcances y el anterior queda revocado. Esta llamada no cuenta para el límite de creación de tokens. - El valor del token (
data.token) solo se muestra en la respuesta de creación o de renovación. Guárdalo en ese momento. - Crear tokens con
POST /api/v1/tokensestá limitado a 10 intentos por usuario en 24 horas, contando también los rechazados por validación. Al superarlo, la API responde 429.
Todo PAT es válido 90 días desde su creación, sin importar el expires_in_days con que se creó: ese valor define la fecha expires_at que muestran la respuesta y GET /api/v1/tokens. Si el PAT tiene más de 90 días, responde 401 aunque su expires_at sea posterior. Si se creó con menos de 90 días, desaparece de GET /api/v1/tokens al pasar su expires_at, pero sigue autenticando hasta el día 90. Planifica la rotación a los 90 días de la creación.
Error: "Credenciales inválidas." y otros rechazos del login
Síntomas (POST /api/v1/auth/login). Las respuestas de error del login incluyen "success": false y un message, salvo el 429 del límite general de peticiones, que responde {"message": "Too Many Attempts."} (ver la sección Error 429 más abajo), y el 500 excepcional {"message": "Server Error"} cuando la cuenta no tiene una empresa asociada válida (en ese caso, contacta al soporte):
| HTTP | message | Qué hacer |
|---|---|---|
| 404 | Credenciales inválidas. | Revisa el email y la contraseña: mayúsculas y espacios al inicio o al final. Si no la recuerdas, restablécela (ver abajo). |
| 404 | El usuario se encuentra inactivo. Comuníquese con el administrador. | Contacta al soporte. |
| 400 | El correo electrónico no ha sido verificado. Por favor, revise su bandeja de entrada y siga las instrucciones para verificar su cuenta. | Abre el enlace del correo de verificación. Para pedir uno nuevo usa POST /api/v1/email/verification-notification con {"email": "tu-email"}. |
| 404 | La empresa se encuentra inactiva. Comuníquese con el administrador. | Contacta al soporte. |
| 422 | Los datos proporcionados no son válidos. (con errors) | Envía email con un formato válido y password. remember_me, si lo envías, debe ser booleano. |
{
"success": false,
"message": "Credenciales inválidas."
}
Para restablecer tu contraseña:
- Abre la pantalla de recuperación de la aplicación web: Producción · Sandbox. También puedes entrar desde el inicio de sesión con «¿Olvidaste tu contraseña?». Por API, envía
POST /api/v1/forgot-passwordcon{"email": "tu-email@empresa.com"}; este endpoint no requiere autenticación. - Recibirás un correo con un enlace válido durante 60 minutos. Solo se permite una solicitud cada 60 segundos; si repites antes, la respuesta es 422 con el mensaje en
errors.email. - Define la nueva contraseña desde el enlace del correo (o con
POST /api/v1/reset-password) e inicia sesión de nuevo.
📝 Problemas con Solicitudes
Error: "Solicitud no encontrada"
Síntomas:
GET /api/v1/certificate-request/{id}responde 200, pero con"dataRecords": { "data": [null] }- Los endpoints de emisión responden 404
La solicitud de certificado {id} no existe. - Los de descarga o revocación responden 404
Solicitud de certificado no encontrada.
Soluciones:
-
Usa el identificador correcto: los endpoints con
{id}esperan el ID numérico (p. ej.636). Los de descarga y revocación (/{uuid}/issuance/download,/{uuid}/issuance/download/base64,/{uuid}/revoke) esperan eluuidde la solicitud. La respuesta de creación no incluye eluuid: obténlo conGET /api/v1/certificate-request/{id}.curl -X GET "https://sandbox-api.maticerts.com/api/v1/certificate-request/636" \-H "Authorization: Bearer $TOKEN" -
Comprueba que la solicitud pertenece a tu empresa: solo ves las solicitudes de la empresa del usuario del token. Puedes buscarlas por NIT:
curl -X GET "https://sandbox-api.maticerts.com/api/v1/certificate-request?query=900455420" \-H "Authorization: Bearer $TOKEN" -
Verifica que no haya sido eliminada:
- Las solicitudes no caducan ni se archivan automáticamente.
- Una solicitud solo desaparece si alguien de tu empresa la elimina con
DELETE /api/v1/certificate-request/{id}. Ese borrado es definitivo, devuelve el cupo y solo se permite en estadoDRAFToREJECTED; en cualquier otro estado la API responde 409.
Error: "Los datos de la solicitud son inválidos."
Síntomas (HTTP 400 en POST /api/v1/certificate-request):
{
"message": "Los datos de la solicitud son inválidos.",
"errors": {
"legal_rep_email": ["El correo del representante legal es requerido para la emisión con verificación biométrica"]
}
}
En los demás endpoints los errores de validación responden 422 con
{"success": false, "message": "Los datos proporcionados no son válidos.", "errors": {...}}. En ambos casoserrorses un mapa de campo a lista de mensajes.
Soluciones:
-
Envía todos los campos obligatorios:
const solicitud = {country_id: 45, // ✅ Requerido (GET /api/v1/countries)city_id: 149, // ✅ Requerido (GET /api/v1/cities)identity_document_id: 1, // ✅ Requerido (GET /api/v1/identity-documents)type_organization_id: 1, // ✅ Requerido (GET /api/v1/organization-type)entity_document_type_id: 1, // ✅ Requerido si type_organization_id = 1 (GET /api/v1/entity-document-types)document_number: "1234567890", // ✅ Requerido: documento del representante legaladdress: "Calle 123 # 45-67", // ✅ Requeridocompany_name: "MI EMPRESA S.A.S.", // ✅ Requeridodni: "900455420", // ✅ Requerido: NIT sin dígito de verificaciónlegal_rep_first_name: "JUAN PABLO", // Requerido con issuance_flow BIOMETRIC (en DOCUMENTARY: estos dos o legal_representative)legal_rep_last_name: "PÉREZ GÓMEZ", // Requerido con issuance_flow BIOMETRIClegal_rep_email: "juan.perez@empresa.com", // Requerido con issuance_flow BIOMETRIC (dominio con DNS válido)life: 1, // ✅ Requerido: 1 o 2 añosissuance_flow: "BIOMETRIC", // Opcional: BIOMETRIC o DOCUMENTARY (sin él, el tipo por defecto de tu empresa)accept_terms: true, // ✅ Requeridoterms_version_id: 3 // ✅ Requerido: id de GET /api/v1/terms/current};Los IDs del ejemplo son ilustrativos: tómalos siempre de los catálogos.
-
Usa los catálogos, no textos libres: los tipos de documento, organización, país y ciudad son IDs numéricos de los endpoints de Datos Maestros.
terms_version_iddebe ser la versión vigente que devuelveGET /api/v1/terms/current. -
Adjuntos: viajan en Base64 dentro del JSON (
attachments[].base64), no como multipart. Dependen del tipo de emisión de la solicitud (issuance_flow): enDOCUMENTARYson obligatorios, de 2 a 3 archivos, máximo 7 MB por archivo y 10 MB en total (se recomiendan PDF, JPG o PNG; el servidor no valida el tipo de archivo); enBIOMETRICson opcionales. -
Te piden adjuntos y querías una emisión biométrica: si tu empresa es
dualy no envíasissuance_flow, la solicitud se creaDOCUMENTARY. En ese casoerrors.attachmentsdiceLos adjuntos son requeridos para la emisión documental. La empresa admite ambos flujos; envíe issuance_flow=BIOMETRIC para emisión biométrica.Envía"issuance_flow": "BIOMETRIC"con el correo y los nombres del representante legal.
Error al crear una solicitud: sin cupo, duplicada o empresa inactiva
Sin cupo disponible (HTTP 402):
{
"success": false,
"error_code": "QUOTA_EXHAUSTED",
"message": "No tiene certificados disponibles de tipo Biométrica (con verificación de identidad) para vigencia de 1 año(s). Debe adquirir un paquete de certificados para continuar.",
"issuance_flow": "BIOMETRIC",
"vigencia": 1,
"available_in_other_flows": [{ "issuance_flow": "DOCUMENTARY", "available": 3 }]
}
- Consulta tu disponibilidad por tipo de emisión con
GET /api/v1/quota/status(by_flow.<TIPO>.available_1_yearyavailable_2_year). available_in_other_flowslista los otros tipos habilitados para tu empresa que tienen cupo para esa vigencia (puede venir vacío). Si te sirve, crea la solicitud con eseissuance_flow.- Revisa los precios del tipo que necesitas en
GET /api/v1/pricing?issuance_flow=<TIPO>, compra conPOST /api/v1/orders(con el mismoissuance_flow) y paga conPOST /api/v1/orders/{uuid}/pay. - Los certificados prepago son por vigencia y por tipo de emisión: un paquete de 2 años no habilita solicitudes con
life: 1, y uno documental no habilita solicitudes biométricas. Compra el tipo y la vigencia que vas a solicitar. Un cupo pospago vigente sirve para cualquier vigencia, pero solo para su tipo de emisión.
Solicitud duplicada (HTTP 400):
{
"success": false,
"message": "Ya existe una solicitud de certificado, en proceso, con el mismo NIT y DV. Por favor verifique el estado de la solicitud."
}
- Tu empresa no puede tener dos solicitudes con el mismo NIT y DV en estado
DRAFT,SENT,PENDING,ACCEPTEDoPROCESSING. - Búscala con
GET /api/v1/certificate-request?query=900455420(tu NIT) y continúa con la existente. - Si está en
DRAFTy no la vas a usar, elimínala conDELETE /api/v1/certificate-request/{id}(se devuelve el cupo) y después crea la nueva. Si es una solicitudBIOMETRICya en trámite y sus datos son incorrectos, recházala (ver «Quiero anular una solicitud con datos erróneos» más abajo).
Empresa inactiva (HTTP 403): la empresa asociada a tu usuario está inactiva y no puede operar. Contacta al soporte.
Errores del tipo de emisión (issuance_flow)
| HTTP | error_code | Dónde | Qué hacer |
|---|---|---|---|
| 400 | INVALID_ISSUANCE_FLOW | POST /api/v1/certificate-request | Envía BIOMETRIC o DOCUMENTARY, o no envíes el campo para usar el tipo por defecto de tu empresa. |
| 400 | ISSUANCE_FLOW_NOT_ALLOWED | POST /api/v1/certificate-request | Tu empresa no tiene habilitado ese tipo. Revisa issuance_provider en GET /api/v1/quota/status: viafirma solo admite BIOMETRIC, mail solo DOCUMENTARY y dual los dos. |
| 400 | ISSUANCE_FLOW_UNAVAILABLE | POST /api/v1/certificate-request | 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. |
| 400 | ISSUANCE_FLOW_IMMUTABLE | PUT /api/v1/certificate-request/{id} | El tipo de una solicitud no se puede cambiar. No envíes issuance_flow al editar; para usar otro tipo, elimina la solicitud (solo en DRAFT o REJECTED) y crea una nueva. |
| 402 | QUOTA_EXHAUSTED | POST /api/v1/certificate-request | Sin cupo para ese tipo y esa vigencia (ver «Sin cupo disponible» arriba). |
| 409 | ISSUANCE_FLOW_ACTION_NOT_ALLOWED | POST /api/v1/certificate-request/{id}/issue | Enviaste request_status o user_of_change en el cuerpo de /issue sobre una solicitud BIOMETRIC. No envíes esos campos: las solicitudes biométricas avanzan solas con la verificación de identidad del titular. |
| 422 | ISSUANCE_PROVIDER_MISMATCH | POST /api/v1/certificate-request/{id}/issue | El provider del cuerpo no corresponde al tipo de la solicitud. No envíes provider. |
| 422 | INVALID_ISSUANCE_FLOW | GET /api/v1/pricing, POST /api/v1/orders, filtros ?issuance_flow= de listados y estadísticas | Usa BIOMETRIC o DOCUMENTARY. En los filtros, un valor vacío cuenta como si no lo enviaras. |
| 422 | ISSUANCE_FLOW_NOT_ALLOWED | GET /api/v1/pricing, POST /api/v1/orders | Tu empresa no tiene habilitado ese tipo: no puedes cotizarlo ni comprarlo. |
| 422 | PRICING_TIER_NOT_FOUND | GET /api/v1/pricing (cotización), POST /api/v1/orders | No hay un rango de precios para esa cantidad en ese tipo de emisión. Revisa los rangos con GET /api/v1/pricing?issuance_flow=<TIPO>. |
GET /api/v1/pricing devuelve una lista vacía ([]): no es un error. Ese tipo de emisión no está disponible para compra en tu plan; no ofrezcas esa compra.
En todos los casos, muestra el message de la respuesta: describe el motivo en lenguaje de usuario.
⏳ Problemas de Estado y Progreso
La solicitud no avanza
Síntomas:
request_statussigue enDRAFT,SENT,PENDING,ACCEPTEDoPROCESSINGdurante horasGET /api/v1/certificate-request/{id}/issuancedevuelvedataRecords.status=submittedoprocessing
Soluciones:
-
Consulta el estado de la emisión:
curl -X GET "https://sandbox-api.maticerts.com/api/v1/certificate-request/636/issuance" \-H "Authorization: Bearer $TOKEN"dataRecords.statuspuede valersubmitted,processing,readyofailed. En una solicitudBIOMETRIC,dataRecords.dataincluye estos campos:internal_stateyremote_statusaccredited_status: resultado de la verificación de identidadlast_public_note: motivo concreto que informa Viafirmalast_error_messageexpires_at: fecha límite para completar la verificación de identidadcan_reject: si hoy se puede rechazar el trámite
-
Acciones según el caso (solicitud
BIOMETRIC):remote_status=accreditation: el suscriptor debe completar la verificación de identidad (KYC). Obtén el enlace conGET /api/v1/certificate-request/{id}/kyc-linky envíaselo. Si responde 422, el enlace no se puede obtener ahora: leemessage. Si el estado remoto todavía es anterior aaccreditation, vuelve a consultar más tarde; si ya es posterior (por ejemplofailoaccreditation_rejected) o Viafirma respondió con un error, reintentar no lo resolverá: revisa el trámite conGET /api/v1/certificate-request/{id}/issuance. Justo después de crear la solicitud, mientras el trámite de Viafirma aún no existe, responde404(Recurso no encontrado.): vuelve a consultar más tarde. Si el404diceSolicitud de certificado no encontrada., elidno existe o pertenece a otra empresa.kyc_flow_completed_atinformado pero la solicitud no avanza: el suscriptor terminó el flujo, pero la aprobación la confirma Viafirma. El estado se actualiza solo: el sistema consulta a Viafirma cada 60 segundos.accredited_status=NOT_MATCHINGoREVIEW, olast_error_message/last_public_noteinformados: contienen el motivo reportado por el proveedor.NOT_MATCHINGsignifica que los datos del documento no coinciden;REVIEW, que un operador de la autoridad de registro la está revisando.- Datos incorrectos: si
can_rejectestrue, rechaza el trámite conPOST /api/v1/certificate-request/{id}/issuance/reject(es definitivo) y crea una solicitud nueva. - Se acerca
expires_at: si el suscriptor no completa el KYC dentro del plazo, la solicitud se cancela (ver la sección siguiente).
-
Acciones según el caso (solicitud
DOCUMENTARY):- Solicitud en
DRAFT: envíala conPOST /api/v1/certificate-request/{id}/issue, que necesita al menos un adjunto de tipoATTACHED. Es la llamada que remite los soportes para la emisión y pasa la solicitud aPROCESSING.PUT /api/v1/certificate-request/{id}/statusconSENTsolo cambia el estado: la solicitud queda enSENTa la espera de revisión por MATICERTS, sin enviar los soportes a la emisión (en Sandbox tampoco se genera el certificado de prueba). - Solicitud en
SENT,PENDING,ACCEPTEDoPROCESSING: está en revisión. Si nunca llamaste aPOST /api/v1/certificate-request/{id}/issue(por ejemplo, la pasaste aSENTconPUT /status), los soportes no se han enviado: llama a/issue. Si ya lo hiciste, solo queda esperar.
- Solicitud en
-
POST /issueresponde 422 porprovideroemail_certificate: no envíesprovideren el cuerpo: el proveedor lo decide el tipo de emisión de la solicitud, y unproviderque no corresponde a ese tipo responde422conerror_code=ISSUANCE_PROVIDER_MISMATCH. Si envías"provider": "viafirma",email_certificatees obligatorio. En una solicitudBIOMETRICno necesitas llamar a/issue: la emisión arranca sola al crear la solicitud (solo se usa para reintentar una emisión fallida).
La solicitud pasó a CANCELLED sin que la cancelaras
Síntomas:
request_status=CANCELLED, y enGET /api/v1/certificate-request/{id}/issuanceel trámite aparece coninternal_state=EXPIRED,last_error_code=POLL_EXPIREDydataRecords.status=failed.
Causa: en una solicitud BIOMETRIC, el suscriptor tiene 96 horas (4 días) desde el envío del trámite para completar la verificación de identidad (KYC). La fecha límite está en dataRecords.data.expires_at. 24 horas antes del vencimiento se envía un aviso de último llamado al correo de la empresa. Si el plazo vence sin que el suscriptor haya completado el KYC, un proceso automático que corre cada hora rechaza el trámite en Viafirma, lo pasa a EXPIRED, cancela la solicitud y reintegra a tu empresa el cupo consumido. Si el suscriptor alcanzó a avanzar en Viafirma antes del vencimiento, el trámite no se cancela y sigue su curso.
Soluciones:
- Crea una solicitud nueva. El cupo ya está disponible de nuevo.
- Cuando el enlace de verificación esté disponible, compártelo de inmediato con el suscriptor (
GET /api/v1/certificate-request/{id}/kyc-link) y pídele completarlo antes deexpires_at.
No confundas
CANCELLEDconEXPIREDenrequest_status:EXPIREDindica un certificado ya emitido cuya vigencia terminó.
📥 Problemas de Descarga
Error: el certificado no se puede descargar
Síntomas (GET /api/v1/certificate-request/{uuid}/issuance/download o /issuance/download/base64; el parámetro es el UUID de la solicitud, no su ID numérico):
| HTTP | message | Significado |
|---|---|---|
| 409 | El certificado no está vigente y no se puede descargar. | La solicitud no está en PROCESSED. El campo status trae su estado actual: DRAFT, SENT, PENDING, ACCEPTED, PROCESSING (aún en trámite; DRAFT en una solicitud DOCUMENTARY significa que falta llamar a POST /api/v1/certificate-request/{id}/issue) o REVOKED, EXPIRED, CANCELLED, REJECTED (ya no se entrega). |
| 404 | Solicitud de certificado no encontrada. | El UUID no existe o pertenece a otra empresa. |
| 404 | Archivo de certificado no encontrado. | La solicitud está en PROCESSED, pero no hay un archivo de certificado disponible para descargar por la API. |
| 404 | Archivo no disponible en almacenamiento. | El archivo ya no existe en el almacenamiento (p. ej., se purgó tras el vencimiento). |
| 410 | No se pudo leer el archivo del almacenamiento. | Solo en /download/base64: el archivo está vacío o no se pudo leer. |
{
"success": false,
"message": "El certificado no está vigente y no se puede descargar.",
"status": "PROCESSING"
}
Soluciones:
- Comprueba el estado de la emisión:
GET /api/v1/certificate-request/{id}/issuance(ID numérico). Puedes descargar cuandodataRecords.statusseareadyy la solicitud esté enPROCESSED. - Si
statusesDRAFT: la solicitud aún no se ha enviado a emisión. Envíala conPOST /api/v1/certificate-request/{id}/issue(ver «Solicitud enDRAFT» más arriba). - Si
statusesSENT,PENDING,ACCEPTEDoPROCESSING: espera. El tiempo no es fijo: depende de que el suscriptor complete la verificación de identidad (el trámite vence a las 96 horas) y de la autoridad certificadora. Con Viafirma, siremote_statusesaccreditation, reenvía al suscriptor el enlace deGET /api/v1/certificate-request/{id}/kyc-link. Suscríbete al webhookcertificate_request.status_changedpara no tener que consultar periódicamente. - Si
statusesREVOKED,EXPIRED,CANCELLEDoREJECTED: el certificado ya no se entrega. Si se revocó por no renovarse a tiempo, revisa la sección «Renovación, revocación y rechazo» más abajo. - Si recibes un 404 de archivo o un 410 con la solicitud en
PROCESSED: contacta al soporte e indica el UUID de la solicitud.
Error: "Archivo corrupto" al abrir el certificado
Síntomas:
- El archivo descargado no se puede abrir o importar
filemuestraZip archive dataoJSON dataen lugar de un certificado
Causas frecuentes:
- Guardar la respuesta JSON del endpoint como si fuera el
.p12: ninguno de los dos endpoints de descarga devuelve el binario; ambos responden con JSON. - Abrir el
.zipcomo.p12: el archivo entregado es un .zip que contiene el .p12 (p. ej.empresa-sas_636.zip→empresa-sas_636.p12).
Soluciones:
-
Obtén la URL y el PIN (usa el
uuidde la solicitud, no su ID numérico):curl -X GET "https://sandbox-api.maticerts.com/api/v1/certificate-request/$UUID/issuance/download" \-H "Authorization: Bearer $TOKEN"# dataRecords.data: download_url (válida 24 h), p12_pin, p12_filename, expires_at -
Descarga el .zip desde
download_url, descomprímelo y abre el .p12:curl -L "$DOWNLOAD_URL" -o certificado.zipfile certificado.zip # Debería mostrar: "Zip archive data"unzip certificado.zip # Extrae el .p12openssl pkcs12 -in empresa-sas_636.p12 -info -noout -passin pass:$P12_PINUsa como contraseña del .p12 el valor de
p12_pin. Sip12_pinvaleNO_PIN, contacta al soporte. En una solicitudDOCUMENTARY, el .zip puede venir protegido con contraseña: el NIT del titular, o el NIT seguido del DV si es persona jurídica. -
Integraciones desatendidas (ERP, scripts) o
download_urlennull: usaGET /api/v1/certificate-request/{uuid}/issuance/download/base64.p12_base64es el Base64 del .zip:curl -s -H "Authorization: Bearer $TOKEN" \"https://sandbox-api.maticerts.com/api/v1/certificate-request/$UUID/issuance/download/base64" > resp.jsonjq -r .dataRecords.data.p12_base64 resp.json | base64 -d > certificado.zipunzip certificado.zip -
Si el problema persiste: contacta al soporte indicando el UUID de la solicitud.
🔁 Renovación, revocación y rechazo
No puedo renovar un certificado
Consulta primero GET /api/v1/certificate-request/{id}/issuance/renewal. Si eligible es false, reason explica el motivo:
reason | Significado | Qué hacer |
|---|---|---|
NOT_RENEWABLE_PROVIDER | El certificado no se emitió en una solicitud BIOMETRIC o aún no está emitido. | Solicita un certificado nuevo. |
NOT_ACTIVE | La solicitud no está en PROCESSED (revocada, vencida…). | Solicita un certificado nuevo. |
MAX_TERM | Ya tiene la vigencia máxima de 2 años. | No requiere renovación. |
GRACE_EXPIRED | Pasó revocation_date (vencimiento + 15 días de gracia). | Solicita un certificado nuevo. |
Si es renovable, POST /api/v1/certificate-request/{id}/issuance/renew genera la orden de pago (o devuelve la orden pendiente que ya existía, sin duplicarla); si no lo es, responde 422 con reason. La renovación se aplica cuando pagas esa orden con los endpoints de Órdenes.
Una orden de renovación pendiente pasa a FAILED si el certificado deja de ser renovable (al intentar pagarla, al revocarse o al vencer). Una orden FAILED no se puede reintentar: si el certificado sigue siendo renovable, genera otra con POST /api/v1/certificate-request/{id}/issuance/renew.
Mi certificado aparece como REVOKED sin haberlo revocado
Los certificados de 1 año que no se renuevan antes de revocation_date se revocan automáticamente; desde ese momento la descarga responde 409 con status: "REVOKED". Para evitarlo, renueva y paga antes de que termine el periodo de gracia.
Si hay una orden de renovación pendiente con actividad en las últimas 48 horas (creada, reutilizada o con un pago iniciado), la revocación automática se aplaza, pero como máximo hasta 3 días después de revocation_date.
Error al revocar (POST /api/v1/certificate-request/{uuid}/revoke)
- Usa el uuid de la solicitud, no el ID numérico.
revoking_codese obtiene deGET /api/v1/certificate-request/{id}/issuance→dataRecords.data.revocation_code.revocation_reasondebe ser uno de: 0, 1, 2, 3, 4, 5, 9 o 10.- 422
No se puede revocar un certificado que no esté en estado PROCESSED...: solo se revocan certificados emitidos. La revocación es irreversible.
Quiero anular una solicitud con datos erróneos que aún no se emitió
Usa POST /api/v1/certificate-request/{id}/issuance/reject con un reason de 5 a 400 caracteres. La solicitud queda CANCELLED y se reintegra el cupo. Es definitivo, así que deberás crear una solicitud nueva. can_reject en GET /api/v1/certificate-request/{id}/issuance anticipa si el rechazo es posible.
- 409: la solicitud ya no está en
PROCESSINGo el trámite ya terminó, el certificado ya fue emitido (revócalo conPOST /api/v1/certificate-request/{uuid}/revoke), Viafirma no admite el rechazo en el estado actual del trámite (contacta al soporte) o la solicitud se está actualizando en ese momento (reintenta en unos segundos). - 502: error de comunicación con Viafirma; no se modificó nada. Reintenta más tarde.
- La respuesta incluye
dataRecords.quota_released: si esfalse, el cupo no se reintegró, pero el rechazo se mantiene.
🔄 Problemas de Integración
Error: "CORS - Cross-Origin Request Blocked"
Síntomas:
Access to fetch at 'https://sandbox-api.maticerts.com/...'
from origin 'http://localhost:3000' has been blocked by CORS policy
Causa: la política CORS la decide el servidor de la API de MATICERTS, que puede restringir los orígenes de navegador permitidos según el entorno. Configurar CORS en tu propio servidor no cambia esa política.
Solución recomendada: llama a la API desde tu backend. Así evitas CORS y el token Bearer (que dura 90 días) nunca queda expuesto en el navegador.
// Backend (Node.js 18+ con Express): tu frontend llama a /api/solicitudes/:id/emision
app.get('/api/solicitudes/:id/emision', async (req, res) => {
const r = await fetch(
`${process.env.MATICERTS_API_URL}/certificate-request/${req.params.id}/issuance`,
{ headers: { Authorization: `Bearer ${process.env.MATICERTS_TOKEN}`, Accept: 'application/json' } }
);
res.status(r.status).json(await r.json());
});
No uses proxies de desarrollo (por ejemplo "proxy" en package.json) como solución de producción: solo funcionan en local y siguen dejando el token en el cliente. Si necesitas llamar a la API directamente desde un navegador, contacta al soporte para solicitar la autorización de tu dominio.
Error: "Timeout - La petición tardó demasiado"
Síntomas:
Error: Request timeout after 30000ms
Soluciones:
-
Aumenta el timeout en tu cliente:
// Node.js 18+ (fetch nativo no admite la opción `timeout`)const response = await fetch(url, { signal: AbortSignal.timeout(60000) }); // 60 s# Pythonresponse = requests.get(url, timeout=60) -
No esperes la emisión en la misma petición; consulta el estado periódicamente (o usa webhooks):
const API = 'https://sandbox-api.maticerts.com/api/v1';const headers = { Authorization: `Bearer ${process.env.MATICERTS_TOKEN}`, Accept: 'application/json' };async function esperarCertificado(id, { intervaloMs = 60000, plazoMs = 96 * 3600 * 1000 } = {}) {const limite = Date.now() + plazoMs;while (Date.now() < limite) {const res = await fetch(`${API}/certificate-request/${id}/issuance`, { headers });if (res.status === 429) { await new Promise(r => setTimeout(r, 60000)); continue; }const body = await res.json();const { status, data } = body.dataRecords;if (status === 'ready' || status === 'completed') return body;if (status === 'failed' || status === 'unsupported') {throw new Error(data?.last_error_message || body.message || 'Emisión fallida');}// submitted | processing | sent: seguir esperandoawait new Promise(r => setTimeout(r, intervaloMs));}throw new Error('La emisión no finalizó dentro del plazo');}El estado se consulta con
GET /api/v1/certificate-request/{id}/issuance(dataRecords.status:submitted,processing,readyofaileden solicitudesBIOMETRIC;processing,readyofaileden solicitudesDOCUMENTARY). El estado de Viafirma se actualiza cada 60 segundos, así que consulta como máximo una vez por minuto: la API admite 60 peticiones por minuto y responde 429 si se superan. En una solicitudBIOMETRICla emisión depende de que el suscriptor complete la verificación de identidad, lo que puede tardar horas o días; el trámite vence a las 96 horas y termina enfailed.Para evitar el polling, suscríbete al webhook
certificate_request.status_changed. -
Contacta al soporte si el servidor es lento:
- Podemos investigar problemas de rendimiento
- Proporciona el ID de la solicitud
Error 429: "Demasiadas solicitudes"
Síntomas: la API responde HTTP 429. El cuerpo depende del límite que se superó:
- Límite general (cualquier endpoint):
{ "message": "Too Many Attempts." }
- Límites específicos por operación:
{
"success": false,
"message": "Demasiadas solicitudes de certificado. Intente nuevamente en un minuto."
}
Límites vigentes:
| Operación | Límite | Mensaje |
|---|---|---|
| Cualquier endpoint de la API (límite general) | 60 peticiones por minuto por usuario (por IP si no hay sesión) | Too Many Attempts. |
POST /certificate-request | 10 por minuto por usuario | Demasiadas solicitudes de certificado. Intente nuevamente en un minuto. |
POST /certificate-request/{id}/files | 20 por minuto por usuario | Demasiadas solicitudes de carga de archivos. Intente nuevamente en un minuto. |
POST /certificate-request/{id}/issue | 5 por minuto por usuario | Demasiadas solicitudes de emisión. Intente nuevamente en un minuto. |
POST /tokens | 10 intentos en 24 horas por usuario (cuentan también los fallidos) | Límite de creación de tokens alcanzado. Máximo 10 por día. |
Las peticiones a estos endpoints también cuentan para el límite general de 60 por minuto.
Soluciones:
- Si superas el límite general, la respuesta trae las cabeceras
Retry-After(segundos que debes esperar),X-RateLimit-LimityX-RateLimit-Remaining. Espera lo que indiqueRetry-Afterantes de reintentar. Las respuestas exitosas también traenX-RateLimit-Remaining, así que puedes vigilar cuántas peticiones te quedan. - Las respuestas 429 de los límites específicos (solicitudes, archivos, emisión, tokens) no traen
Retry-After. Espera al menos 60 segundos antes de reintentar; enPOST /tokens, espera a que se cumplan 24 horas desde el primer intento (el contador no se reinicia a medianoche). - Reduce el polling: consulta el estado de la emisión como máximo cada 60 segundos.
- Reutiliza tus tokens de acceso en lugar de crear uno nuevo en cada proceso.
No recibo webhooks o la firma no coincide
Soluciones:
- Revisa que el webhook esté activo:
GET /api/v1/webhooks/{id}. Tras 10 fallos consecutivos de conexión o timeout se desactiva automáticamente (is_active: false). Reactívalo conPUT /api/v1/webhooks/{id}enviando{"is_active": true}. Corrige antes la causa: el contador de fallos solo se reinicia con una entrega exitosa, así que si la siguiente entrega vuelve a fallar se desactiva otra vez. - Consulta el historial de entregas:
GET /api/v1/webhooks/{id}/deliveries.statuspuede serdelivered(tu servidor respondió; se guardanhttp_statusy la respuesta),failed(error de conexión o timeout; el error queda enresponse_body) opending(el envío está en curso o se interrumpió antes de completarse; también queda así una entrega marcada por soporte para reintento manual). Una entrega no se reenvía automáticamente. Ojo: si tu servidor responde 4xx o 5xx, la entrega queda comodeliveredy el evento no se reintenta. - Responde rápido y con 2xx: tu endpoint debe contestar en menos de 10 segundos. Procesa el evento en segundo plano.
- Obtén el secret: al crear el webhook la respuesta no incluye el
secret. Para obtenerlo llama aPOST /api/v1/webhooks/{id}/rotate-secret; es el único momento en que se muestra. Cada rotación invalida el secret anterior de inmediato. - Verifica la firma correctamente: la cabecera
X-Webhook-Sigtiene el formatot=1735689600,v1=<hmac>. Calcula el HMAC-SHA256 (hexadecimal) del cuerpo crudo recibido, sin volver a serializar el JSON, con elsecret, y compáralo conv1. Opcionalmente, rechaza firmas cuyottenga más de 5 minutos de antigüedad.const crypto = require('crypto');app.post('/webhooks/maticerts', express.raw({ type: 'application/json' }), (req, res) => {const firma = Object.fromEntries((req.get('X-Webhook-Sig') || '').split(',').map(p => p.split('=')));const esperado = crypto.createHmac('sha256', process.env.WEBHOOK_SECRET).update(req.body).digest('hex');const a = Buffer.from(esperado);const b = Buffer.from(firma.v1 || '');if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) return res.sendStatus(401);res.sendStatus(200);// procesa JSON.parse(req.body) en segundo plano}); - Revisa los eventos suscritos: generan entregas
certificate_request.created,certificate_request.status_changed,certificate_request.ai_processed,certificate_request.file_uploadedycertificate_request.deletedy, desde la 1.21.1,payment.approvedypayment.failed.certificate.expiringse puede suscribir, pero todavía no envía notificaciones. La cabeceraX-Webhook-Eventindica el tipo de cada entrega. - No me llegó el evento de un pago:
payment.approvedllega solo cuando la orden pasa aPAID, ypayment.failedsolo cuando pasa aFAILEDporque se rechazó el pago; cada uno, como máximo una vez por orden (una orden que recibiópayment.failedpuede recibir despuéspayment.approvedsi otro intento de pago de esa orden se aprueba). No llega ninguno si la transacción quedó enVOIDEDoERROR(la orden sigue enPENDING), si la pasarela reenvía un pago ya notificado ni si una orden de renovación se anuló porque el certificado dejó de ser renovable. En cualquier caso, el estado de la orden enGET /api/v1/orders/{uuid}es la fuente de verdad: consúltalo como respaldo, sobre todo para las órdenes que lleven tiempo enPENDING.
📞 Contacto y Soporte
Si ninguna solución funciona:
-
Reúne información:
- ID (y UUID, si aplica) de la solicitud
- Timestamp del error
- Pasos que realizaste
- Código HTTP y mensaje de error exactos
-
Contacta al soporte:
- Email: soporte@matias.com.co
- Teléfono: (+57) 310-843-5431
-
Proporciona logs:
- Incluye las peticiones y respuestas relevantes de tu aplicación; ayudan a diagnosticar el problema más rápido.
- Nunca compartas tokens, contraseñas, secrets de webhooks ni el PIN del certificado.