# Firmame — documentación para desarrolladores > Firmame es un servicio de firma electrónica. Esta es la documentación de su API, cuya URL base es https://app.firmame.com. - Cada página se sirve también en markdown: añade `.md` a su URL, por ejemplo https://devs.firmame.com/docs/autenticacion.md - Especificación OpenAPI: https://devs.firmame.com/openapi.yaml - Índice de páginas: https://devs.firmame.com/llms.txt - Versión en inglés de este archivo: https://devs.firmame.com/en/llms-full.txt - Toda petición se autentica con una firma HMAC. Las reglas van más abajo; si no coinciden, la respuesta es `401` con cuerpo vacío. Cada petición lleva `Authorization: signmage {webId}:{firma}`, donde `firma` es el Base64 de `HMAC-SHA256(apiKey, cadenaFirmada)`. Qué va dentro de `cadenaFirmada` depende de la petición: | Petición | `cadenaFirmada` | |---|---| | `GET` | La URI completa **incluyendo el query string**, exactamente como se envía | | `POST` con JSON | El cuerpo JSON, re-serializado compacto (sin espacios ni salto de línea final) | | `POST` multipart | El SHA-256 en Base64 del archivo, que además se manda en el header `Content-SHA256` | Dos detalles que conviene resolverlos desde el principio: 1. En `GET`, la URI se firma tal como se envía, así que **el orden y el encoding de los parámetros importan**. 2. En `POST`, la comparación se hace contra tu JSON re-serializado en forma compacta. Manda JSON compacto y canónico para que las firmas coincidan. Una firma que no coincide devuelve `401` con el cuerpo vacío. Como la misma respuesta cubre un web ID desconocido, una llave inactiva y una firma que no coincide, revisa las tres cosas al diagnosticar. # Conceptos (https://devs.firmame.com/docs/conceptos) > El vocabulario de Firmame. Antes de escribir código conviene fijar ocho términos. ## Documento [#documento] El PDF que se firma. Al crearlo, Firmame le asigna un identificador único que vas a usar en todas las llamadas posteriores. ```js const { document_ticket } = await services.request({ /* … */ }); // más tarde, el mismo valor: await services.docs({ ticket: document_ticket, action: 'delete' }); // y en el webhook que te llega: // { "firmamex_id": "04337396-e929-43ca-9b8a-ec958a44079d", … } ``` ## Sticker [#sticker] Un recuadro de firma: **una persona, en un lugar, en una página**. Un documento sin stickers se guarda, pero nadie puede firmarlo. Cada sticker define quién firma (`data`), cómo se verifica esa persona (`authority`), en qué página va (`page`) y en qué coordenadas (`rect`). Hay dos formas de colocarlos: mandándolos en el arreglo `stickers` con coordenadas explícitas, o [dibujándolos dentro del PDF](/docs/guias/recuadros-de-firma) para que Firmame los encuentre. ## Authority [#authority] El método con el que el firmante comprueba que es quien dice ser: su certificado del SAT, una liga que recibe por correo, un PIN por WhatsApp, un SMS. Es la decisión de más peso de cada sticker: define **qué queda comprobado** de la persona que firmó, y por lo tanto qué evidencia respalda esa firma después. No comprueban lo mismo un certificado del SAT y una liga enviada por correo. También determina quién manda la invitación. Con las autoridades **por liga**, Firmame la envía automáticamente y solo el firmante tiene la URL que autoriza firmar a su nombre. Con `SAT`, `EMAIL` y `SMS`, si no configuras un flujo, repartir la URL de firma es responsabilidad tuya. El catálogo completo está en el [esquema `Authority`](/docs/guias/firmantes) de la referencia. ## Verificación de identidad [#verificación-de-identidad] Una capa adicional sobre la `authority`: además de verificar el canal, el firmante comprueba con una identificación oficial y una **prueba de vida** —una selfie que confirma que es una persona real, no una foto ni una pantalla— que es quien dice ser. Se activa por sticker con `requireIdentityVerification`, y consume créditos adicionales de la cuenta. Ver [Verificación de identidad](/docs/guias/verificacion-de-identidad). ## Flujo de firma [#flujo-de-firma] La configuración de **cómo** se recolectan las firmas: en orden o en paralelo, cada cuánto se recuerda a quien falta, cuándo expira, quién recibe copia sin firmar. Sin flujo, tienes un documento firmable y tú decides cómo hacérselo llegar a cada persona. Con flujo, Firmame manda las invitaciones, los recordatorios y avisa cuando se completa. Se puede adjuntar al crear el documento o agregarse después. Ver [Flujos de firma](/docs/guias/flujos). ## Expediente [#expediente] Un paquete de varios documentos que se firman juntos: una invitación, un orden de firma, una conclusión. El ciclo es crear el expediente, agregarle documentos, y cerrarlo para arrancar el flujo. Ver [Expedientes](/docs/guias/expedientes). ## Original y universal [#original-y-universal] Dos representaciones firmadas **del mismo documento**, no dos documentos: | | Qué es | Cuándo la quieres | | ------------- | -------------------------------------------------------------------- | ----------------------------------------------------------------------------------- | | **original** | El PDF con las firmas tal como se aplicaron | Para mostrarlo o descargarlo en el momento | | **universal** | Esas mismas firmas envueltas en un sobre de validación a largo plazo | Para archivar, porque sigue siendo verificable cuando los certificados ya expiraron | Ambas se descargan con [`GET /api/document/{type}/{ticket}`](/docs/api/descargas/download-document), y cada una tiene su propio webhook: `original_signed` y `universal_signed`. ## NOM-151 [#nom-151] La norma mexicana de constancia de conservación. Un sello NOM-151 certifica ante un tercero que un documento existía, con ese contenido exacto, en un momento dado. Firmame lo aplica automáticamente a los documentos que firma, y también lo expone por separado para sellar archivos o hashes que ni siquiera pasaron por la plataforma. Ver [NOM-151](/docs/guias/nom151). # Introducción (https://devs.firmame.com/docs) > Integra la firma electrónica de Firmame en tu aplicación. Firmame es un servicio de firma electrónica. Su API te permite crear documentos, colocar recuadros de firma, invitar a los firmantes, seguir el avance y descargar el documento firmado con su evidencia. La mayoría de las integraciones se resuelven con tres llamadas y un webhook. ## El flujo de integración [#el-flujo-de-integración] 1. **Crea el documento.** Envías el PDF —por URL, base64 o desde un template— junto con los *stickers*: los recuadros donde cada persona debe firmar. 2. **Invita a los firmantes.** Con un *flujo de firma* controlas si es en orden o en paralelo, los recordatorios y la fecha de expiración. Si prefieres repartir tú las ligas, puedes omitirlo. 3. **Escucha los webhooks.** Firmame notifica cada firma y el cierre del documento a la URL que registres. 4. **Descarga el resultado.** [El documento firmado](/docs/api/descargas/download-document) y su historial de evidencia, en [JSON](/docs/api/consultas/get-document-history) o en [PDF](/docs/api/consultas/get-document-history-pdf). ## Autenticación en una línea [#autenticación-en-una-línea] Cada solicitud lleva un HMAC-SHA256 firmado con tu API key: ``` Authorization: signmage TU_WEB_ID: ``` Los SDKs la construyen por ti. Si llamas a la API directamente, la [guía de autenticación](/docs/autenticacion) explica qué entra en `cadenaFirmada` según el tipo de petición. ## Por dónde empezar [#por-dónde-empezar] # Inicio rápido con IA (https://devs.firmame.com/docs/inicio-rapido-ai) > Dale esta documentación a tu asistente y pídele la integración. Prompt de arranque y URLs legibles por máquina. Toda esta documentación está publicada también en formatos que lee una máquina: markdown crudo por página, un archivo con el portal completo, y la especificación OpenAPI. Si integras con ayuda de un asistente, dale eso como contexto en lugar de dejarlo adivinar. ## El prompt de arranque [#el-prompt-de-arranque] Cópialo, pégalo en tu asistente y describe tu caso en la última línea. Lleva las tres cosas que necesita saber antes de escribir la primera línea: dónde está la documentación, el flujo de una integración típica, y **cómo se firma cada petición**. Esa última parte es la que conviene no dejar al azar: una firma que no coincide devuelve `401` con cuerpo vacío, sin nada en la respuesta de dónde agarrarse. Si quieres el detalle, está en [Errores 401](/docs/autenticacion/errores-401). ## Las URLs legibles por máquina [#las-urls-legibles-por-máquina] Todas existen en los dos idiomas: el inglés vive bajo `/en` — `/en/llms-full.txt`, `/en/openapi.yaml`, `/en/docs/autenticacion.md`. ## Cargar la documentación como contexto [#cargar-la-documentación-como-contexto] Guarda el archivo en el repositorio donde estás trabajando y menciónalo en tu petición: ```bash curl -o docs/firmame.md https://devs.firmame.com/llms-full.txt ``` Así queda disponible en cada sesión sin volver a descargarlo. También puedes pegar la URL en el chat y pedirle que la lea. Añade `https://devs.firmame.com/llms-full.txt` como documentación indexada en los ajustes, y después referénciala con `@` en tus peticiones. La alternativa es la misma que en cualquier repositorio: guardar el archivo dentro del proyecto. Descarga `/llms-full.txt` y pégalo, o pasa la URL si tu asistente puede navegar. Si el archivo completo no cabe en la ventana de contexto, empieza por `/llms.txt` y baja solo las páginas que haga falta con `.md`. ## Dos cosas que vale la pena revisar del código generado [#dos-cosas-que-vale-la-pena-revisar-del-código-generado] **Que la cadena firmada se arme una sola vez.** En `GET` se firma la URI completa tal como se envía, y en `POST` con JSON se firma el cuerpo compacto. Si el código construye la URL o serializa el JSON dos veces —una para firmar y otra para enviar— cualquier diferencia mínima entre las dos versiones produce un `401`. **Que los endpoints y los campos existan.** La [Referencia de API](/docs/api) es la lista completa: 35 operaciones y 12 eventos de webhook. Si el código llama a algo que no está ahí, viene de una suposición. # SDKs (https://devs.firmame.com/docs/sdks) > Instala o descarga el SDK de tu lenguaje, como firmame.ts para TypeScript. Se encargan de firmar cada petición por ti. Los SDKs construyen el HMAC de cada petición y te dejan trabajar con objetos en vez de con cadenas firmadas. | Lenguaje | Cómo se obtiene | | ---------- | ----------------------------------------------------------------------------------- | | Node | [firmamex.js](https://assets.firmame.com/sdks/javascript/firmamex.js) | | TypeScript | [firmame.ts](https://www.npmjs.com/package/firmame.ts) — `npm install firmame.ts` | | PHP | [firmamex.php](https://assets.firmame.com/sdks/php/firmamex.php) | | Python | [firmamex.py](https://assets.firmame.com/sdks/python/firmamex.py) | | .NET | [SignmageSDKCore.dll](https://assets.firmame.com/sdks/net/SignmageSDKCore.dll) | | Java 8 | [firmamex-services.zip](https://assets.firmame.com/sdks/java/firmamex-services.zip) | `firmame.ts` requiere Node 20 o superior. ## Inicializar [#inicializar] Todos reciben lo mismo: tu Web ID y tu API key. Cárgalos desde variables de ambiente, no escritos en el código. ```js const FirmamexServices = require('./firmamex'); const services = FirmamexServices( process.env.FIRMAME_WEB_ID, process.env.FIRMAME_API_KEY, ); ``` ```ts import { Firmame } from 'firmame.ts'; const firmame = new Firmame({ webId: process.env.FIRMAME_WEB_ID, apiKey: process.env.FIRMAME_API_KEY, }); ``` ```python import os from firmamex import SignmageServices services = SignmageServices( os.environ['FIRMAME_WEB_ID'], os.environ['FIRMAME_API_KEY'], ) ``` ```php require_once 'firmamex.php'; $services = new FirmamexServices( getenv('FIRMAME_WEB_ID'), getenv('FIRMAME_API_KEY') ); ``` ```java SignmageServices services = new SignmageServices( System.getenv("FIRMAME_WEB_ID"), System.getenv("FIRMAME_API_KEY") ); ``` A partir de ahí, cada endpoint de la [referencia](/docs/api/documentos/create-document) tiene su método. Por ejemplo, crear un documento: ```js const response = await services.request({ url_doc: { url: 'https://ejemplo.com/contrato.pdf', name: 'contrato.pdf' }, stickers: [ { authority: 'EMAIL_LINK', data: 'firmante@dominio.com', page: 0, rect: { lx: 330, ly: 300, tx: 530, ty: 400 }, }, ], }); ``` ## Si tu lenguaje no está en la lista [#si-tu-lenguaje-no-está-en-la-lista] La API es HTTP con JSON, así que se puede usar desde cualquier parte. Lo único que hay que implementar es el encabezado de autenticación, [Credenciales y firma](/docs/autenticacion) lo explica con un ejemplo completo. # Zapier (https://devs.firmame.com/docs/zapier) > Conecta Firmame con miles de apps sin escribir código, con triggers y acciones listos para usar. Firmame tiene una integración publicada en Zapier: conecta tus documentos y firmas con miles de otras apps sin escribir código, en [zapier.com/apps/firmamex/integrations](https://zapier.com/apps/firmamex/integrations). ## Conectar tu cuenta [#conectar-tu-cuenta] Al agregar la app de Firmame a un Zap, Zapier te pide tu **Web ID** y tu **API key** — las mismas credenciales que usas para firmar peticiones a la API. Ver [Credenciales](/docs/autenticacion) si todavía no las tienes a la mano. ## Triggers [#triggers] Arrancan un Zap cuando algo pasa en Firmame. Los cinco son **instantáneos**: por debajo usan el mismo [esquema de suscripciones por evento](/docs/webhooks/suscripciones) (beta) que documenta la sección de Webhooks — Zapier registra la suscripción por ti, sin que tengas que llamar a `addWebhook` a mano. | Trigger | Cuándo se dispara | | -------------------------------- | --------------------------------------------- | | **Document Completed** | El documento quedó firmado por todos | | **Document Signed - Original** | Alguien firmó el documento | | **Document Signed - Universal** | La versión universal de una firma quedó lista | | **Document Rejected** | Un firmante rechazó el documento | | **Document Stamped With NOM151** | El documento recibió un sello NOM-151 | Corresponden al mismo [catálogo de eventos](/docs/api/webhooks-y-cuenta/document-completed) que documenta la sección de Webhooks. ## Acciones [#acciones] Ejecutan algo en Firmame desde un Zap. | Acción | Qué hace | Equivalente en la API | | -------------------------------- | -------------------------------------------------------------------- | ----------------------------------------------------------------------- | | **Upload Document With Signers** | Sube un documento (Base64 o URL) y opcionalmente le agrega firmantes | [Crear y enviar un documento](/docs/guias/crear-documento) | | **Configure Workflow** | Configura el flujo de firma de un documento | [Flujos de firma](/docs/guias/flujos) | | **Download Document (Base64)** | Descarga la versión original o universal de un documento, en Base64 | [Descargar un documento firmado](/docs/api/descargas/download-document) | | **Upload Template** | Sube un template en HTML, PDFMake, PDF o DOCX | [Templates](/docs/guias/templates) | | **Fill Template** | Llena un template con datos dinámicos y devuelve el resultado | [Templates](/docs/guias/templates) | ## Referencia [#referencia] # Referencia de API (https://devs.firmame.com/docs/api) {/* Generated from openapi/firmame.yaml by `npm run openapi`. Do not edit — your changes will be overwritten. */} # Errores 401 (https://devs.firmame.com/docs/autenticacion/errores-401) > Cómo diagnosticar una petición que devuelve 401, paso por paso. Un `401` llega con el **cuerpo vacío** y cubre tres situaciones distintas: el Web ID no se reconoce, la llave no está activa, o la firma no coincide. ## 1. Descarta lo simple [#1-descarta-lo-simple] Antes de revisar la firma, confirma que el encabezado llegó completo y bien formado: * Empieza con `signmage` seguido de un espacio. * Entre el Web ID y la firma va un `:`, sin espacios alrededor. * La firma va en Base64, no en hexadecimal. ``` Authorization: signmage TU_WEB_ID:PWJhc2U2NCBkZWwgSE1BQz4= ``` Si el Web ID o la llave no son los que te asignaron, o la llave ya no está activa, escribe a **[soporte@firmamex.com](mailto:soporte@firmamex.com)** ## 2. En `GET`, la URI se firma tal cual [#2-en-get-la-uri-se-firma-tal-cual] La cadena firmada es la ruta **con su query string**, exactamente como sale en la petición. Eso significa que el orden de los parámetros y su codificación forman parte de la firma. ```js // Construye la ruta una vez y usa esa misma cadena para firmar y para pedir. const path = '/api/document?from=1640995200000&to=1641945600000'; const auth = authorization(path); await fetch(`https://app.firmame.com${path}`, { headers: { Authorization: auth } }); ``` Lo que suele descuadrar: * Armar la URL con un objeto y dejar que la librería reordene los parámetros. * Firmar la ruta sin query string y luego enviarla con parámetros. * Codificar un valor al firmar y no al enviar, o al revés. ## 3. En `POST` con JSON, serializa una sola vez [#3-en-post-con-json-serializa-una-sola-vez] La comparación se hace contra tu JSON **compacto**: sin espacios ni saltos de línea. Serializa una vez, firma esa cadena y manda exactamente esos bytes. ```js const body = JSON.stringify(payload); // una sola serialización await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: authorization(body) }, body, // los mismos bytes que se firmaron }); ``` Lo que suele descuadrar: * Firmar `payload` y dejar que el cliente HTTP serialice por su cuenta al enviar. Dos serializaciones distintas producen dos cadenas distintas. * Usar un formateador con indentación al firmar. * Un cliente que agrega un salto de línea final al cuerpo. Nota sobre los números: `1.0` y `1` son el mismo valor en JSON pero no la misma cadena. Si tu payload lleva decimales, revisa que sobrevivan igual a la serialización que firmas y a la que envías. ## 4. En `POST` multipart, se firma el hash del archivo [#4-en-post-multipart-se-firma-el-hash-del-archivo] La cadena firmada es el SHA-256 del archivo en Base64, y ese mismo valor va en el encabezado `Content-SHA256`. El archivo viaja en el campo `file`. ```js const hash = createHash('sha256').update(fileBytes).digest('base64'); const headers = { Authorization: authorization(hash), 'Content-SHA256': hash, }; ``` ## 5. Si aún falla, compara la cadena [#5-si-aún-falla-compara-la-cadena] Cuando los cuatro puntos anteriores están bien y el `401` sigue, imprime la cadena que estás firmando y revísala carácter por carácter contra lo que realmente sale por la red. Los [SDKs](/docs/sdks) construyen la firma por ti en los cuatro casos. Si tu lenguaje está en la lista, es la vía más directa. # Credenciales y firma (https://devs.firmame.com/docs/autenticacion) > Cómo obtener tus credenciales y cómo se arma el encabezado de autenticación de cada petición. ## Credenciales [#credenciales] Tu cuenta tiene un par de credenciales de desarrollador. Si todavía no las tienes a la mano, debes poder generarlas dando click en tu usuario en el portal de Administración de usuarios de la cuenta. Si no tienes acceso a este portal, solicita las credenciales a tu administrador de cuetna **Si no tienes cuenta, puedes obtener credenciales de prueba gratuitas**. Sólo envía un correo a [contacto@firmamex.com](mailto:contacto@firmamex.com). | | Qué es | Cómo tratarla | | ----------- | -------------------------------------------- | ------------------------------------------------------------------------------ | | **Web ID** | Tu identificador público | Puede ir en configuración, en el repositorio, donde sea | | **API key** | El secreto con el que se firma cada petición | Trátala como una contraseña: autoriza a solicitar firmas a nombre de tu cuenta | Carga las dos desde variables de ambiente o desde tu gestor de secretos, nunca escritas en el código: ```js const services = FirmamexServices( process.env.FIRMAME_WEB_ID, process.env.FIRMAME_API_KEY, ); ``` ## Con un SDK [#con-un-sdk] Es el camino corto: le pasas las credenciales al SDK y él arma la firma de cada petición. Hay SDK para [Node, TypeScript, PHP, Python, .NET y Java](/docs/sdks). Si tu lenguaje no está en la lista puedes llamar a la API directamente: ## Directo contra la API [#directo-contra-la-api] Cada petición lleva un encabezado `Authorization` con esta forma: B["TU_WEB_ID"] B --> C[":"] C --> D["base64 del HMAC-SHA256"] style A fill:#0b66c9,color:#fff style B fill:#0b66c9,color:#fff style C fill:#64748b,color:#fff style D fill:#0b66c9,color:#fff" /> ``` Authorization: signmage TU_WEB_ID:PWJhc2U2NCBkZWwgSE1BQz4= Content-Type: application/json ``` La firma es `base64(HMAC-SHA256(TU_API_KEY, cadenaFirmada))`. Lo único que cambia entre peticiones es qué va dentro de `cadenaFirmada`: | Petición | `cadenaFirmada` | | ---------------- | ----------------------------------------------------------------------------------- | | `GET` | La URI completa **incluyendo el query string**, exactamente como se envía | | `POST` con JSON | El cuerpo JSON, re-serializado compacto (sin espacios ni salto de línea final) | | `POST` multipart | El SHA-256 en Base64 del archivo, que además se manda en el header `Content-SHA256` | Dos temas importantes: 1. En `GET`, la URI se firma tal como se envía, así que **el orden y el encoding de los parámetros importan**. Construye la URL una sola vez y firma esa misma cadena. 2. En `POST`, la comparación se hace contra tu JSON re-serializado en forma compacta. Serializa una vez, firma esa cadena, y **manda exactamente esos bytes**. ### Ejemplo completo [#ejemplo-completo] ```js import { createHmac } from 'node:crypto'; const WEB_ID = process.env.FIRMAME_WEB_ID; const API_KEY = process.env.FIRMAME_API_KEY; function authorization(signedString) { const hmac = createHmac('sha256', API_KEY).update(signedString, 'utf8').digest('base64'); return `signmage ${WEB_ID}:${hmac}`; } // POST: se firma el cuerpo compacto, y se manda ese mismo cuerpo. const body = JSON.stringify({ url_doc: { url: 'https://ejemplo.com/contrato.pdf', name: 'contrato.pdf' }, stickers: [ { authority: 'EMAIL_LINK', data: 'firmante@dominio.com', page: 0, rect: { lx: 330, ly: 300, tx: 530, ty: 400 }, }, ], }); await fetch('https://app.firmame.com/developers/json', { method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: authorization(body) }, body, }); // GET: se firma la ruta con su query string, tal cual. const path = '/api/document?from=1640995200000&to=1641945600000'; await fetch(`https://app.firmame.com${path}`, { headers: { Authorization: authorization(path) }, }); ``` Si recibes un `401`, la guía de [errores 401](/docs/autenticacion/errores-401) recorre las causas posibles en orden. # Código QR (https://devs.firmame.com/docs/guias/codigo-qr) > Estampa un QR de verificación en el documento, en el mismo payload de creación. Un QR estampado en el documento lleva a la página donde cualquiera puede verificar sus firmas. Viaja en el mismo payload de `POST /developers/json` y no necesita un endpoint propio. Sirve cuando el documento se va a imprimir: quien lo tenga en papel puede comprobar que es auténtico sin que le mandes nada. ```js await services.request({ url_doc: { url: 'https://ejemplo.com/contrato.pdf', name: 'contrato.pdf' }, stickers: [ /* … */ ], qr: [ { width: 150, height: 150, x: 0, y: 0, page: 1 }, ], }); ``` Las coordenadas siguen la misma convención que los stickers: **`x` e `y` se miden desde la esquina inferior izquierda** de la página. ## Repetirlo en varias páginas [#repetirlo-en-varias-páginas] Cada entrada de `qr` puede fijar una página o varias: ```js qr: [ { width: 150, height: 150, x: 0, y: 0, page: 1 }, // una página { width: 120, height: 120, x: 200, y: 400, pages: [2, 3, 4] }, // una lista { width: 120, height: 120, x: 400, y: 600, pages: 'all' }, // todas ] ``` Para un contrato largo que se archiva impreso, `pages: 'all'` deja cada hoja verificable por separado. También puedes pedir el QR desde un [recuadro dibujado en el PDF](/docs/guias/recuadros-de-firma), escribiendo `Firmamex QR` dentro del rectángulo. Es la vía cómoda cuando quien arma el documento decide dónde va. # Correos certificados (https://devs.firmame.com/docs/guias/correos-certificados) > Envía documentación con constancia de entrega, apertura y lectura. Un correo certificado deja evidencia de que mandaste algo, a quién, cuándo llegó y cuándo lo abrieron. Sirve para notificaciones donde después vas a tener que demostrar que se entregaron: avisos de cobranza, comunicaciones laborales, notificaciones contractuales. ## Cómo funciona [#cómo-funciona] Firmame manda la documentación por ti, se queda con una copia de lo enviado, y va registrando lo que pasa con ese correo: si el servidor del destinatario lo aceptó o lo rechazó, si lo abrió y si dio clic en el acuse. El proceso se cierra cuando el destinatario firma el acuse, o a las **24 horas** del envío si no lo firmó — así puedes demostrar que realizaste la entrega. Al cerrar, Firmame te manda por correo a ti y al destinatario un paquete con: una copia del correo enviado (en EML), el historial completo en PDF y JSON, el acuse (firmado o no), y una **Constancia de Conservación NOM-151** que sella con fecha y hora que esa información existía tal cual. ## Pedirlo [#pedirlo] Se pide en el mismo `POST /developers/json`, agregando el objeto `certifiedEmail`. ```js await services.request({ template: { title: 'notificacion', name: 'Envío de documentación', }, fields: { email: 'destinatario@dominio.com', name: 'Ana Ruiz', }, certifiedEmail: { emailParams: [{ name: 'nombre', value: 'Ana Ruiz' }], }, attachments: [ { data: fs.readFileSync('anexo.pdf').toString('base64'), name: 'anexo.pdf', mimeType: 'application/pdf', password: 'una-contrasena', }, ], stickers: [ /* opcional: si además quieres que firmen */ ], }); ``` El documento puede venir de donde sea —`url_doc`, `b64_doc` o un template—, igual que en [crear un documento](/docs/guias/crear-documento). ## Adjuntos [#adjuntos] Cada entrada de `attachments` lleva el archivo en Base64, su nombre y su `mimeType`. El campo `password` es opcional y protege ese adjunto; si lo usas, puedes recuperar la contraseña después: ```js const { password } = await services.getAttachmentsPassword(firmamexId); ``` ## La evidencia [#la-evidencia] Cada paso de arriba queda registrado como un evento. Estos son los tipos que vas a ver en `documentEventType`: | Evento | Qué significa | | ---------------------------------- | --------------------------------------------- | | `DOCUMENT_CREATED` | Documento generado | | `FLOW_STARTED` | Arrancó el flujo | | `SIGNATURE_INVITATION` | Se envió la invitación a firma | | `NON_SIGNER_RECIPENT_FLOW_STARTED` | Se notificó a un destinatario que no firma | | `EMAIL_DELIVERED` | El servidor del destinatario aceptó el correo | | `EMAIL_OPENED` | El destinatario abrió el correo | | `EMAIL_CLICKED` | El destinatario dio clic en el correo | | `EMAIL_FAILED` | El correo no se pudo entregar | | `EMAIL_COMPLAINED` | El destinatario lo marcó como spam | | `DOCUMENT_SIGNED` | Se firmó el documento | | `DOCUMENT_COMPLETED` | Se completó el documento | | `DOCUMENT_STAMPED` | Se selló el documento | | `DOCUMENT_FLATTENED` | Se aplanó el documento (solo AcroForms) | | `REJECTED_DOCUMENT` | El destinatario rechazó el documento | | `FLOW_EXPIRED` | El flujo expiró | `EMAIL_DELIVERED` significa que el servidor del destinatario aceptó el mensaje, no que la persona lo haya visto. La lectura es `EMAIL_OPENED`. ## Recuperar los datos [#recuperar-los-datos] ```js const data = await services.getCertifiedEmailData(firmamexId); ``` Trae los EML de los correos enviados y el JSON con los eventos generados. Lee el `Content-Type` de la respuesta en lugar de dar por hecho que es JSON: refleja el formato en el que está guardada la evidencia. ## Verificar la Constancia de NOM-151 [#verificar-la-constancia-de-nom-151] La Constancia de Conservación se puede verificar de forma independiente, sin depender de Firmame, en el portal del PSC: [serviciospsc.cecoban.mx](https://serviciospsc.cecoban.mx:8004). Ese portal pide dos archivos: el **Original** —[el historial en PDF](/docs/api/consultas/get-document-history-pdf), el historial en JSON, el acuse y los correos enviados, unidos en orden alfabético en un solo archivo— y la **Constancia de Conservación**, que es el archivo `.tsr` que llegó adjunto en el correo. Armar el Original a mano es posible, pero es más simple hacerlo en [verificar-correo-certificado.firmamex.com](https://verificar-correo-certificado.firmamex.com): subes ahí los mismos archivos que recibiste por correo y genera un `Sellado.bin` listo para el portal del PSC. Ese proceso solo une los archivos en orden alfabético — no modifica su contenido. ## Historial en cualquier momento [#historial-en-cualquier-momento] Si solo quieres el avance sin la evidencia completa: ```js const historial = await services.getHistory(firmamexId); const pdf = await services.getHistoryAsPdf(firmamexId); ``` Ver [`GET /api/v1/document/{frmxId}/history/json`](/docs/api/consultas/get-document-history) para los eventos y [`GET /api/v1/document/{frmxId}/history/pdf`](/docs/api/consultas/get-document-history-pdf) para el mismo historial como PDF. # Crear y enviar un documento (https://devs.firmame.com/docs/guias/crear-documento) > De un PDF a un documento firmado, con Firmame llevando las invitaciones. Esta guía cubre la integración más común: subes un contrato, defines quién firma y dónde, y Firmame se encarga de invitar a la gente y avisarte cuando terminen. ## Lo mínimo que funciona [#lo-mínimo-que-funciona] ```js const response = await services.request({ url_doc: { url: 'https://ejemplo.com/contrato.pdf', name: 'contrato.pdf', }, stickers: [ { authority: 'EMAIL_LINK', data: 'firmante@dominio.com', page: 0, rect: { lx: 330, ly: 300, tx: 530, ty: 400 }, }, ], }); ``` La respuesta trae lo que necesitas guardar: ```json { "document_ticket": "c6279a7c-b3d5-4c71-b417-4687a4f38914", "sticker_count": 1, "stickers": [{ "sticker_id": "qv3ZzAL3w", "sticker_index": 0, "…": "…" }], "document_url": "https://app.firmame.com/pdf/c6279a7c-b3d5-4c71-b417-4687a4f38914/" } ``` Guarda **`document_ticket`**: es el identificador del documento en todo lo demás, y también llega como `firmamex_id` en los webhooks. Guarda también los `sticker_id` si vas a querer [corregir un firmante](/docs/guias/firmantes) más adelante. ## De dónde puede venir el PDF [#de-dónde-puede-venir-el-pdf] Manda exactamente una de estas tres: Firmame lo descarga. Útil cuando el PDF ya vive en tu almacenamiento. ```js url_doc: { url: 'https://ejemplo.com/contrato.pdf', name: 'contrato.pdf', } ``` El archivo viaja en la petición. Útil cuando lo acabas de generar y no está publicado. ```js b64_doc: { data: fs.readFileSync('contrato.pdf').toString('base64'), name: 'contrato.pdf', } ``` El documento se genera al vuelo a partir de una plantilla registrada. Ver [Templates](/docs/guias/templates). ```js template: { title: 'contrato-arrendamiento', name: 'Contrato — marzo', }, fields: [ { id: 'arrendatario', value: 'Ana Ruiz' }, ] ``` ## Dónde va cada firma [#dónde-va-cada-firma] Hay dos formas de decirle a Firmame dónde va cada firma: dibujando el recuadro dentro del documento, o mandando coordenadas explícitas en `stickers`. ### Recuadros automáticos [#recuadros-automáticos] Si quien redacta o genera el documento puede marcar dónde va la firma, esta es la forma más simple: [dibuja el recuadro dentro del PDF](/docs/guias/recuadros-de-firma) con la palabra `Firmamex` seguida de los datos del firmante. Firmame lo encuentra, lo borra y coloca ahí el área de firma — tu código deja de necesitar coordenadas. Así se ve en la práctica: [PDF de ejemplo](https://storage.googleapis.com/static.firmamex.com/documentBoxes2.pdf). ### Con coordenadas [#con-coordenadas] Si prefieres fijar la posición desde tu código, cada entrada de `stickers` es una persona firmando en un lugar. Dos convenciones que conviene tener presentes al calcularla: **`page` empieza en cero.** La primera página es `0`. **`rect` va en coordenadas de PDF, con el origen abajo a la izquierda.** La mayoría de los editores gráficos lo pone arriba, así que si tomas las coordenadas de una herramienta de diseño, invierte el eje vertical. ``` ty ┌───────────────┐ │ │ │ la firma │ │ │ ly └───────────────┘ lx tx (0,0) = esquina inferior izquierda de la página ``` Si no conoces de antemano el tamaño de la página —porque el PDF lo genera alguien más— [`POST /api/pdf/pageData`](/docs/api/nom-151-y-herramientas-pdf/get-page-data) te da sus dimensiones para calcular el rectángulo. ## Que Firmame mande las invitaciones [#que-firmame-mande-las-invitaciones] Con lo de arriba tienes un documento firmable, pero el reparto de `document_url` queda de tu lado. Para que Firmame invite, agrega un `workflow` y dale `email` a cada firmante: ```js const response = await services.request({ url_doc: { url: 'https://ejemplo.com/contrato.pdf', name: 'contrato.pdf' }, stickers: [ { authority: 'EMAIL_LINK', data: 'ana@dominio.com', email: 'ana@dominio.com', page: 0, rect: { lx: 330, ly: 300, tx: 530, ty: 400 }, }, { authority: 'EMAIL_LINK', data: 'luis@dominio.com', email: 'luis@dominio.com', page: 0, rect: { lx: 330, ly: 150, tx: 530, ty: 250 }, }, ], workflow: { subject: 'Contrato de arrendamiento', message: 'Te compartimos el contrato para tu firma.', remindEvery: '1d', ordered: true, signersConfig: [{ data: 'ana@dominio.com' }, { data: 'luis@dominio.com' }], }, }); ``` Con `ordered: true`, Luis no recibe su invitación hasta que Ana firma. Los detalles del flujo — expiración, recordatorios, copias a terceros — están en [Flujos de firma](/docs/guias/flujos). ## Enterarte de que ya firmaron [#enterarte-de-que-ya-firmaron] Registra un webhook para `document_completed` y Firmame te avisa cuando el último firmante termina: ```json { "notification_type": "document_completed", "notification_id": "2698", "firmamex_id": "c6279a7c-b3d5-4c71-b417-4687a4f38914", "document_title": "contrato.pdf" } ``` Con ese `firmamex_id` descargas el documento firmado, eligiendo `type`: `original` para el PDF con las firmas tal como se aplicaron, o `universal` para esas mismas firmas envueltas en un sobre de validación a largo plazo. ```js const pdf = await services.downloadDocument(firmamexId, 'original'); ``` Ver [Descargar un documento firmado](/docs/api/descargas/download-document) para los detalles del endpoint, y [Webhooks](/docs/webhooks) para el ciclo completo y la política de reintentos. ## Descargar el historial [#descargar-el-historial] El documento firmado es la mitad del resultado; la otra es su historial: cuándo se creó, cuándo salió cada invitación, cuándo la abrió cada firmante y cuándo firmó. Con el mismo `firmamex_id` lo bajas en dos formatos. ```js const eventos = await services.getHistory(firmamexId); const historial = await services.getHistoryAsPdf(firmamexId); ``` El **JSON** te da un arreglo de eventos, del más reciente al más viejo, para mover tu propia interfaz sin esperar los webhooks: [`GET /api/v1/document/{frmxId}/history/json`](/docs/api/consultas/get-document-history). Ahí está también el catálogo completo de tipos de evento. El **PDF** es el mismo historial armado para entregárselo a alguien, y es el que un paquete de verificación NOM-151 espera: [`GET /api/v1/document/{frmxId}/history/pdf`](/docs/api/consultas/get-document-history-pdf). ## Y después [#y-después] # Endosos (https://devs.firmame.com/docs/guias/endosos) > Transfiere un título de crédito ya firmado a un nuevo tenedor. Un endoso es la transferencia de un título de crédito —típicamente un pagaré— de quien lo tiene a otra persona. En papel se escribe al reverso y se firma; aquí es lo mismo, sobre un documento que ya está completamente firmado. ## Requisitos [#requisitos] El documento **tiene que estar completamente firmado** antes de endosarse. Si le falta una firma, la petición se rechaza. ## Cómo se pide [#cómo-se-pide] ```js await services.endorse( firmamexId, 'Se hace constar que, en esta fecha, XX, S.A.P.I. de C.V., como tenedor, cedió el presente pagaré ' + 'por la totalidad del monto amparado por el mismo al que esta anotación va adherida.', // endosante: quien transfiere { authority: 'SAT', stickerType: 'line', dataType: 'rfc', email: 'endosante@dominio.com', data: 'ARCX9012226P8', imageType: 'endorsment', page: 1, rect: { lx: 145, ly: 496, tx: 443, ty: 610 }, }, // endosatario: quien recibe (opcional) { authority: 'SAT', stickerType: 'line', dataType: 'rfc', email: 'endosatario@dominio.com', data: 'BACX9012226P8', imageType: 'stroke', page: 1, rect: { lx: 145, ly: 296, tx: 443, ty: 410 }, }, ); ``` ## Las piezas [#las-piezas] **El texto del endoso** es la anotación que queda escrita en el documento. El campo se llama `endorsment`. **`endorser`** es el endosante, quien transfiere. Es un [sticker](/docs/guias/firmantes) normal con una particularidad: dale `imageType: "endorsment"` para que el propio texto del endoso se dibuje como imagen de firma, que es como se ve en un endoso de papel. **`endorsee`** es el endosatario, quien recibe. Es **opcional**: un endoso en blanco no nombra destinatario, y el título queda al portador. Para operaciones con `SAT`, el `data` es el RFC de cada parte. ## Referencia [#referencia] Todos los campos, en [`POST /api/document/{firmamexId}/endorse`](/docs/api/firmantes-y-flujos/prepare-endorsement). # Expedientes (https://devs.firmame.com/docs/guias/expedientes) > Varios documentos, una sola invitación y un solo orden de firma. Cuando un trámite abarca más de un documento —contrato, anexo y pagaré— mandarlos por separado significa tres invitaciones, tres recordatorios y tres oportunidades de que alguien firme uno y olvide los otros. Un expediente los junta en un solo paquete. ## El ciclo [#el-ciclo] ```js // 1. Crear const { document_set } = await services.createDocumentSet('Expediente de contratación'); // 2. Agregar cada documento — es el mismo POST /developers/json de siempre, // más el campo document_set await services.request({ url_doc: { url: 'https://ejemplo.com/contrato.pdf', name: 'contrato.pdf' }, document_set, stickers: [ { authority: 'EMAIL_LINK', data: 'ana@dominio.com', email: 'ana@dominio.com', page: 0, rect: { lx: 330, ly: 300, tx: 530, ty: 400 }, }, ], }); await services.request({ url_doc: { url: 'https://ejemplo.com/anexo.pdf', name: 'anexo.pdf' }, document_set, stickers: [ /* … */ ], }); // 3. Cerrar y arrancar el flujo await services.closeDocumentSet({ documentSet: document_set, workflow: { remindEvery: '1d', ordered: ['ana@dominio.com', 'luis@dominio.com'], }, }); ``` Los documentos entran por el mismo `POST /developers/json` que usas siempre; lo único que cambia es que le pasas `document_set`. Si ya tienes código que crea documentos, agregar el campo es todo lo que hace falta. Ver [Subir un documento a un expediente](/docs/guias/subir-documento-a-expediente) para el detalle. ## El flujo abarca todo el paquete [#el-flujo-abarca-todo-el-paquete] Al cerrar, el `workflow` es el [mismo objeto](/docs/guias/flujos) de un documento suelto, con una diferencia: `ordered` lista firmantes del expediente completo, no de un documento en particular. Ana firma todo lo que le toca, y hasta entonces le llega su turno a Luis. El `workflow` es obligatorio al cerrar. ## Consultar el avance [#consultar-el-avance] ```js const set = await services.getDocumentSet(document_set); ``` ```json { "uuid": "9v3d6a47-e4e8-48ef-b9b3-7af45c8207dn", "status": "CLOSED", "documents": ["4a3d6a47-…", "5d08eabe-…"], "signers": ["ana@dominio.com", "luis@dominio.com"], "workflow": { "currentSigner": "luis@dominio.com", "lastNotification": 1665001495000, "expirationDate": 1666211094000 } } ``` `status` es `OPEN` mientras se le pueden agregar documentos y `CLOSED` una vez que arrancó el flujo. El objeto `workflow` **solo aparece mientras el expediente está en proceso**: no viene si está abierto ni cuando ya terminó, así que pregunta por él en vez de darlo por hecho. ## Precargar firmantes para todo el expediente [#precargar-firmantes-para-todo-el-expediente] Precarga a las personas que deben firmar todos los documentos del expediente, sin ligarlas todavía a una página o posición. Con el expediente en curso, arrastras el recuadro de cada firmante precargado sobre cada documento desde la interfaz de Firmame; una vez colocados todos, cierras el expediente y arranca el flujo. ```js await services.addPremadeSticker(document_set, { authority: 'EMAIL_LINK', data: 'ana@dominio.com', email: 'ana@dominio.com', imageType: 'stroke', stickerType: 'rect', line: false, width: 200, height: 100, }); ``` Todavía no lleva `page` ni `rect` — solo `width` y `height` — porque la posición se define después, al arrastrarlo en la interfaz. Todos los campos de arriba son obligatorios. Devuelve un `uuid` propio; guárdalo si vas a querer quitar a esa persona: ```js await services.deletePremadeSticker(document_set, { uuid: signerUuid }); ``` Ver [Quitar un firmante del expediente](/docs/api/expedientes/delete-premade-sticker). ## Cambiar o cancelar el flujo del expediente [#cambiar-o-cancelar-el-flujo-del-expediente] Igual que con un documento suelto, pero apuntando al UUID del expediente: ```js await services.workflowSet(document_set, { expiration_date: '25/12/2026' }); await services.workflowSet(document_set, { cancel: true }); ``` ## Referencia [#referencia] # Firmantes y autoridades (https://devs.firmame.com/docs/guias/firmantes) > Cómo elige cada persona su método de verificación, y cómo corregir un firmante a tiempo. La **autoridad** de un sticker define **cómo se comprueba la identidad del firmante**, y eso es lo que queda registrado como evidencia de la firma. Un certificado del SAT y una liga enviada por correo no comprueban lo mismo: el primero acredita a la persona ante una autoridad certificadora, el segundo acredita el control de una cuenta de correo. Es la decisión de más peso de cada sticker, porque determina dos cosas a la vez: * **Qué respalda la firma.** El nivel de comprobación que puedes demostrar después, si alguien cuestiona quién firmó. * **Qué tan fácil es firmar.** Un certificado exige que la persona lo tenga a la mano; una liga se abre desde el teléfono. Elígela según lo que el documento necesite sostener, no solo por comodidad — y si el trámite tiene requisitos legales concretos, confírmalos antes de decidir. ## Cuál elegir [#cuál-elegir] | Autoridad | Qué queda comprobado | | --------------- | ------------------------------------------------------------------------------ | | `SAT` | La identidad de la persona ante el SAT, con su certificado y su llave privada | | `OAUTH_TOKEN` | El control de una cuenta de Google o de Outlook, verificada por el proveedor | | `WHATSAPP_PIN` | El control de un número, con un PIN de un solo uso al momento de firmar | | `EMAIL_LINK` | El control de una cuenta de correo, por una liga única enviada a esa dirección | | `WHATSAPP_LINK` | El control de un número, por una liga única enviada por WhatsApp | | `SMS_LINK` | El control de un número, por una liga única enviada por SMS | | `EMAIL` | El control de una cuenta de correo, por respuesta a un mensaje | | `SMS` | El número del firmante, con la invitación enviada por SMS | Están ordenadas de mayor a menor nivel de comprobación. Una liga acredita que quien firmó tenía acceso a esa cuenta o a ese número; un certificado del SAT acredita a la persona ante la autoridad que lo emitió. Con las autoridades **por liga** (`EMAIL_LINK`, `WHATSAPP_LINK`, `SMS_LINK`), Firmame envía la invitación automáticamente y solo el firmante tiene la URL que autoriza firmar a su nombre. Con `SAT`, `EMAIL` y `SMS`, si no configuras un [flujo](/docs/guias/flujos), repartir la URL de firma es responsabilidad tuya. También se aceptan los nombres largos en español —`Vinculada a Correo Electronico por Liga` y similares—, que es lo que verás en integraciones viejas. En código nuevo usa el código corto. ## El campo `data` [#el-campo-data] Es el valor que identifica a la persona, y su forma depende de la autoridad: ```js { authority: 'EMAIL_LINK', data: 'firmante@dominio.com' } // un correo { authority: 'WHATSAPP_PIN', data: '5216621234567' } // un teléfono { authority: 'SAT', data: 'VECJ880326XXX' } // un RFC ``` `email` es un campo aparte de `data`. **Es obligatorio si quieres que Firmame lleve el flujo**, porque es a donde llega la invitación; con las autoridades por SMS y WhatsApp, omítelo — identifican por teléfono y mandar los dos se rechaza. ## Cómo se ve la firma en el papel [#cómo-se-ve-la-firma-en-el-papel] `imageType` controla el dibujo que queda estampado: | Valor | Qué dibuja | | -------- | -------------------------------------------------------------- | | `desc` | Texto: «Firma realizada con certificado emitido por … para: …» | | `name` | El nombre del certificado, en cursiva | | `hash` | Un hash del documento seguido del nombre | | `stroke` | El trazo que la persona dibuja al firmar | Se pueden combinar dos con `+`, por ejemplo `stroke+desc` para el trazo con el texto descriptivo debajo. ## Verificación de identidad [#verificación-de-identidad] Como capa extra de seguridad para confirmar la identidad de quien firma, agrega `requireIdentityVerification: true` al sticker. El proceso completo —identificación oficial, prueba de vida, créditos que consume y el webhook con el resultado— está en [Verificación de identidad](/docs/guias/verificacion-de-identidad). ## Corregir un firmante [#corregir-un-firmante] Mientras esa persona **no haya firmado**, puedes cambiarle el correo, el dato de verificación, la autoridad o el tipo de imagen. Necesitas dos ids, los dos de la respuesta al crear el documento: ```js await services.updateSigner(documentTicket, stickerId, { authority: 'EMAIL_LINK', email: 'correo-correcto@dominio.com', data: 'correo-correcto@dominio.com', imageType: 'desc', }); ``` Resuelve el caso de un correo mal escrito sin rehacer el documento. Una vez que la persona firmó, su recuadro queda fijo; si aun así necesitas cambiarlo, cancela el flujo y genera el documento de nuevo. Ver [`POST /api/{firmamexId}/signer/{publicSignerId}/update`](/docs/api/firmantes-y-flujos/update-signer). ## Otras opciones del sticker [#otras-opciones-del-sticker] | Campo | Para qué | | --------------------- | ---------------------------------------------------------------------------------------------------------------------------- | | `requireLocation` | Pide la ubicación del firmante antes de firmar | | `sendEmailInvitation` | Manda el correo de invitación de este firmante | | `customData` | Cadena tuya que se te regresa en los webhooks de esa persona, útil para amarrar la firma con un registro de tu base de datos | # Flujos de firma (https://devs.firmame.com/docs/guias/flujos) > Orden de firma, recordatorios, expiración y cancelación. Un flujo le dice a Firmame **cómo** recolectar las firmas: en qué orden, cada cuánto insistir, cuándo darse por vencido y a quién copiar. Sin flujo tienes un documento firmable y tú repartes las ligas. Con flujo, Firmame se encarga. Se puede adjuntar al crear el documento o agregarse después, con el mismo objeto en los dos casos. ```js // al crear await services.request({ url_doc: {...}, stickers: [...], workflow: {...} }); // o después, sobre un documento que ya existe await services.workflow(documentTicket, {...}); ``` ## Orden de firma [#orden-de-firma] `ordered: true` hace que cada persona reciba su invitación cuando la anterior firma. El orden lo marca `signersConfig`: ```js workflow: { ordered: true, signersConfig: [ { data: 'ana@dominio.com' }, { data: 'luis@dominio.com' }, { data: 'VECJ880326XXX' }, ], } ``` Con `ordered: false` (el valor por defecto) todos reciben la invitación de inmediato y firman en cualquier orden. Cada entrada se empareja con un sticker por su valor `data`, así que tiene que coincidir exactamente con lo que pusiste en el sticker. ## Watcher [#watcher] Destinatarios no firmantes. Este campo es opcional, si se encuentra presente, estos correos recibiran una notificación cuando inicie y otra cuando termine el flujo, sin tener que ser parte de los firmantes ```js workflow: { watchers: ['legal@dominio.com', 'archivo@dominio.com'], } ``` ## Recordatorios y expiración [#recordatorios-y-expiración] ```js workflow: { subject: 'Contrato de arrendamiento', message: 'Te compartimos el contrato para tu firma.', remindEvery: '1d', expirationDate: '25/11/2026', language: 'es', } ``` | Campo | Valores | Por defecto | | ---------------- | ------------------------------------ | ------------------ | | `remindEvery` | `none`, `12h`, o `Nd` con N de 1 a 7 | cada 24 h | | `expirationDate` | `dd/MM/yyyy` o `dd/MM/yyyy HH:mm` | a 14 días | | `language` | `es` o `en` | `es` | | `subject` | hasta 150 caracteres | el asunto estándar | | `message` | hasta 3500 caracteres | el texto estándar | `expirationDate` es `dd/MM/yyyy`: **`12/11/2026` es 12 de noviembre**, no 11 de diciembre. Un formato distinto se rechaza con `error parsing workflow expiration date`. También puedes ajustar qué correos recibe cada quien: ```js signersConfig: [ { data: 'ana@dominio.com', notifications: 'ALL' }, { data: 'luis@dominio.com', notifications: 'ONLY_START' }, ] ``` Los valores son `ALL`, `ONLY_START`, `ONLY_END`, `START_AND_END`, `START_AND_REMINDERS_NO_END` y `NONE`. ## Qué se adjunta al final [#qué-se-adjunta-al-final] ```js workflow: { attachFiles: true, // el documento firmado (por defecto) attachTimestamp: true, // el sello NOM-151 includeHistory: true, // el historial de eventos alertAll: true, // avisar a todos al concluir (por defecto) blockExpired: true, // impedir firmar después de la expiración } ``` `includeHistory` adjunta el mismo PDF que devuelve [`GET /api/v1/document/{frmxId}/history/pdf`](/docs/api/consultas/get-document-history-pdf). ## Editar un flujo en curso [#editar-un-flujo-en-curso] Manda solo lo que quieras cambiar; el resto se queda igual. El caso más común es revivir un flujo que expiró: ```js await services.workflow(documentTicket, { expiration_date: '25/12/2026' }); ``` Quitar el orden para que todos puedan firmar ya: ```js await services.workflow(documentTicket, { ordered: false }); ``` ## Cancelar [#cancelar] ```js await services.workflow(documentTicket, { cancel: true }); ``` `cancel: true` corta todo lo demás: cuando lo mandas, no se lee ninguna otra propiedad del objeto. Manda la cancelación sola, y si vas a reconfigurar, hazlo en una llamada aparte. ## Un flujo para varios documentos [#un-flujo-para-varios-documentos] Si el trámite abarca más de un documento —contrato, anexo y pagaré— conviene un [expediente](/docs/guias/expedientes): una sola invitación, un solo orden y una sola conclusión para todo el paquete. La lista completa está en el [esquema `Workflow`](/docs/api/firmantes-y-flujos/update-workflow). # Gestor (https://devs.firmame.com/docs/guias/gestor) > Asigna quién administra el documento dentro de tu organización. Por defecto, el documento pertenece a la cuenta cuyo Web ID firmó la petición. Con `owner` lo asignas a una persona concreta de tu organización, que será quien lo administre desde la aplicación de Firmame. Es útil cuando los documentos se generan de forma externa — por ejemplo, desde tu backend con las credenciales de una sola cuenta — pero cada usuario necesita verlos en su propio repositorio dentro de Firmame: sin `owner`, todos quedarían bajo la cuenta que firmó la petición en lugar de la persona a la que realmente le corresponden. ```js await services.request({ url_doc: { url: 'https://ejemplo.com/contrato.pdf', name: 'contrato.pdf' }, owner: { email: 'gestor@tuempresa.com' }, }); ``` Para que funcione hacen falta dos cosas: 1. Que esa persona ya esté registrada como parte de tu organización. 2. Que uses el mismo correo con el que se registró. ## Errores [#errores] | Mensaje | Qué pasó | Qué hacer | | ---------------------------------- | ----------------------------------------------- | ----------------------------------------------- | | `invalid owner` | No se encontró a esa persona en la organización | Revisa el correo; tiene que ser el del registro | | `can't verify user at this moment` | La verificación no se pudo completar | Reintenta más tarde | # NOM-151 y herramientas PDF (https://devs.firmame.com/docs/guias/nom151) > Sella cualquier archivo o hash con constancia de conservación, y utilidades de PDF. Un sello NOM-151 certifica ante un tercero que algo existía, con ese contenido exacto, en un momento dado. Firmame lo aplica solo a los documentos que firma; estos endpoints te dejan usarlo por separado, sobre archivos que nunca pasaron por la plataforma. ## Sellar un hash [#sellar-un-hash] El caso más flexible: sacas tú el SHA-256 de lo que sea —un registro de base de datos, una bitácora, un artefacto de compilación— y mandas solo el digest. ```js import { createHash } from 'node:crypto'; const hash = createHash('sha256').update(datos).digest('hex'); const sello = await services.timestamp({ hash }); ``` **Firmame nunca ve tus datos, solo el digest.** Eso lo hace viable para información que no puedes compartir. Ojo con el formato: aquí el hash va en **hexadecimal**. ### Validarlo después [#validarlo-después] Puedes validar contra el archivo, o contra su hash si prefieres no subirlo: ```js // con el archivo await services.timestampValidate({ file: contenido, timestamp: sello.timestamp }); // solo con el hash await services.timestampValidate({ hash, timestamp: sello.timestamp }); ``` ## Sellar un PDF [#sellar-un-pdf] Si lo que tienes es un PDF y quieres el sello embebido en el archivo: ```js const sellado = await services.nom151Stamp(fs.readFileSync('documento.pdf')); ``` Y para verificar uno que ya viene sellado: ```js const resultado = await services.nom151Validate(fs.readFileSync('sellado.pdf')); ``` `POST /api/nom151/stamp`, `/api/nom151/validate`, `/api/print` y `/api/pdf/pageData` reciben el archivo como `multipart/form-data`. En multipart la cadena firmada es el SHA-256 en Base64 del archivo, que además va en el header `Content-SHA256` — los SDKs se encargan de esto por ti. El playground funciona con los endpoints JSON. ## Los sellos de un documento de Firmame [#los-sellos-de-un-documento-de-firmame] Para los documentos que sí pasaron por la plataforma: ```js const { timestamps } = await services.getNom151StampsForDocument(firmamexId); ``` ```json { "timestamps": [ { "hash": "ce92da77b45ddbb26ab4210600023de8f0effb062276cd4c2765feed3988cfc4", "tsa": "L=MIGUEL HIDALGO,ST=CIUDAD DE MEXICO,C=MX,CN=CECOBAN-PSC-NOM151-1-01,O=CECOBAN SA DE CV", "genTime": "2023-07-10T18:21:28.467Z", "status": "granted", "policy": "2.16.484.101.10.316.2.3.1.2.1.1.1" } ] } ``` Con el `hash` de un sello bajas la revisión exacta del documento que ese sello ampara: ```js const revision = await services.getDocumentForStamp(firmamexId, timestamps[0].hash); ``` Eso es lo que necesita un tercero para verificar: el documento como estaba en ese momento, no como está ahora. ## Herramientas PDF [#herramientas-pdf] ### Dimensiones de una página [#dimensiones-de-una-página] Antes de colocar stickers en un documento que no generaste tú, conviene saber de qué tamaño es la página — `rect` va en unidades de PDF desde la esquina inferior izquierda, y una página rotada o de tamaño inusual deja un rectángulo fijo en el lugar equivocado. ```js const primera = await services.getPageData(archivo, 'first'); const segunda = await services.getPageData(archivo, '2'); const ultima = await services.getPageData(archivo, 'last'); ``` Acepta `first`, `last` o un número de página. **Aquí los números empiezan en 1**, a diferencia del `page` de un sticker, que empieza en 0. ### Aplanar un PDF [#aplanar-un-pdf] Convierte los campos de formulario en contenido estático y fusiona las capas: ```js const plano = await services.printPdf(fs.readFileSync('formulario.pdf')); ``` Útil antes de sellar o firmar un documento con formulario interactivo que no quieres que quede editable. ## Referencia [#referencia] # Recuadros de firma en el documento (https://devs.firmame.com/docs/guias/recuadros-de-firma) > Coloca las áreas de firma dibujándolas dentro del PDF, sin mandar coordenadas por API. Hay dos formas de decirle a Firmame dónde va cada firma. La documentada en la referencia de API es el arreglo `stickers`, con coordenadas explícitas. La otra es dibujar los recuadros **dentro del documento** y dejar que Firmame los encuentre. Esta casi siempre es mejor, ya que no se tienen que recalcular coordenadas y es especialmente útil si el documento lo produce alguien más — un área legal, una plantilla de Word, un generador de contratos. También puedes generar los PDFs en la herramienta de tu preferencia, o usar nuestros Templates para generar estos recuadros ## Cómo funciona [#cómo-funciona] Al recibir un PDF, Firmame analiza **las primeras 3 y las últimas 8 páginas** buscando recuadros. Un recuadro Firmame es cualquier rectángulo que contenga la palabra `Firmamex` seguida de los datos del firmante, o de `QR`. Cuando encuentra uno, lo borra y coloca en su lugar el área de firma o el código QR del documento Si tus recuadros están fuera de ese rango de páginas, o quieres que el análisis sea más rápido, manda `sticker_pages` en `POST /developers/json`. Empieza en cero y admite hasta 30 páginas: `[0, 2, 4]` son las páginas 1, 3 y 5. ## La sintaxis [#la-sintaxis] ``` Firmamex [datos del firmante] [configuración] ``` ### Datos del firmante [#datos-del-firmante] Va el dato en sí, no el nombre del tipo. Firmame deduce la autoridad de la forma del dato: | Dato | Autoridad por defecto | | --------------------------- | ------------------------------------------------------------------------ | | RFC, p. ej. `VECJ880326XXX` | SAT. Agrega también un correo si quieres que Firmame mande la invitación | | Correo | Correo por liga | | Teléfono a 10 dígitos | SMS por liga | ### Configuración [#configuración] Todas opcionales, y se puede combinar **una de cada categoría**. **Cambiar la autoridad** | Palabra | Efecto | | ------------- | ------------------------------------------------------------ | | `sso` | Correo por liga → Correo por Identity Provider | | `certificado` | Correo por liga → Correo; SMS por liga → SMS | | `pin` | Correo por liga → Correo por PIN; SMS por liga → SMS por PIN | | `wa` | SMS por liga → WhatsApp por liga | | `waotp` | SMS por liga → WhatsApp por PIN | **Proceso de firma** | Palabra | Efecto | | ------- | ---------------------------------------------------------------------------------------- | | `riv` | Exige [verificación de identidad](/docs/guias/verificacion-de-identidad) antes de firmar | **Imagen de la firma** | Palabra | Efecto | | ------------- | ------------------------------------------------------------- | | `desc` | Texto descriptivo | | `dibujo` | El firmante traza su firma o sube un PNG | | `solo_dibujo` | El firmante traza su firma, sin opción de subir imagen | | `dibujo+desc` | El trazo, con el texto descriptivo debajo | | `hash` | Un hash del documento como imagen | | `name` | El nombre del certificado, en cursiva | | `desc_data` | Emisor del certificado, número de serie y nombre del firmante | | `qr` | QR del documento, único para esa firma | **Alineación** | Palabra | Efecto | | ---------- | ------------------------------------------------ | | `linea` | El trazo queda en la parte inferior del recuadro | | `centrada` | El trazo queda centrado en el recuadro | **Repetir en varias páginas** Páginas separadas por `-` o `,`, por ejemplo `1-3,9`. El recuadro aparece en esas páginas, en la misma posición. ## Ejemplos [#ejemplos] ``` Firmamex firmante@correo.com ``` Invitación por correo por liga, sin especificar trazo. ``` Firmamex VECJ880326XXX firmante@correo.com dibujo ``` Invitación a `firmante@correo.com` para firmar con su certificado del SAT, dibujando su firma. ``` Firmamex 521234567890 pin name ``` Invitación por SMS por PIN, con el nombre del certificado como trazo. ## En un template HTML [#en-un-template-html] El mismo mecanismo sirve en los [templates](/docs/api/templates/save-template). Por ejemplo, con templates HTML, defines el recuadro en la posición donde quieres la firma: ```html
``` El texto va en blanco y a 2px a propósito: no es necesario que se vea el recuadro, solo tiene que existir para que Firmame lo encuentre. Al generar el documento inyectas ahí el contenido del recuadro como cualquier otro campo: ```js const response = await services.request({ template_title: 'ejemploSticker', sticker_pages: [0, 2, 4], fields: [ { id: 'signer1', value: 'Firmamex GOCF9002226A7 dibujo' }, ], tables: [], }); ``` ## Archivos de ejemplo [#archivos-de-ejemplo] * [Ejemplo en PDF](https://storage.googleapis.com/static.firmamex.com/documentBoxes2.pdf) * [Ejemplo en documento de texto](https://storage.googleapis.com/static.firmamex.com/documentBoxes.docx) # Subir un documento a un expediente (https://devs.firmame.com/docs/guias/subir-documento-a-expediente) > El mismo POST /developers/json de crear un documento, con document_set obligatorio. No hay un endpoint distinto para "agregar" un documento a un expediente: es exactamente `POST /developers/json`, el mismo que usas para un documento suelto, con un campo más. ## Lo mínimo que funciona [#lo-mínimo-que-funciona] ```js const response = await services.request({ url_doc: { url: 'https://ejemplo.com/contrato.pdf', name: 'contrato.pdf', }, document_set: '9v3d6a47-e4e8-48ef-b9b3-7af45c8207dn', stickers: [ { authority: 'EMAIL_LINK', data: 'firmante@dominio.com', page: 0, rect: { lx: 330, ly: 300, tx: 530, ty: 400 }, }, ], }); ``` Sin `document_set`, el documento se crea suelto — fuera del expediente. El valor es el `uuid` que devolvió [`POST /api/documentset`](/docs/api/expedientes/create-document-set) al crear el expediente, y tiene que seguir **abierto** (`status: "OPEN"`): una vez cerrado, ya no acepta documentos nuevos. Ver [Consultar expediente](/docs/api/expedientes/get-document-set) para confirmar el estado. La respuesta es la misma que al crear un documento suelto: guarda `document_ticket` y los `sticker_id` de cada firmante. ## Todo lo demás es igual que crear un documento suelto [#todo-lo-demás-es-igual-que-crear-un-documento-suelto] El origen del PDF (`url_doc`, `b64_doc` o `template`), cómo se ubica cada `sticker` con `page` y `rect`, y las autoridades de firma no cambian por estar dentro de un expediente. Ver [Crear y enviar un documento](/docs/guias/crear-documento) para el detalle de cada uno. ## No mandes `workflow` aquí [#no-mandes-workflow-aquí] El flujo de firma del expediente se define **una sola vez, al cerrarlo** — no por documento. Si mandas `workflow` en esta llamada, estás armando el flujo de un documento suelto, que no es lo que quieres dentro de un expediente. Ver [Expedientes](/docs/guias/expedientes#el-flujo-abarca-todo-el-paquete) para cómo cerrar el expediente y arrancar las invitaciones para todo el paquete de una vez. ## Y después [#y-después] # Templates (https://devs.firmame.com/docs/guias/templates) > Registra un documento una vez y genera cada instancia mandando solo los datos. Si mandas el mismo documento cambiando unos cuantos datos, no hace falta que lo generes y lo subas cada vez. Regístralo como template una sola vez, y a partir de ahí mandas nada más los valores: Firmame arma el PDF. ## El ciclo [#el-ciclo] ## Los cuatro tipos [#los-cuatro-tipos] Un solo endpoint —`POST /developers/template/save`— los atiende a todos. Lo que cambia es `template_type` y cómo va codificado el archivo. | Tipo | Qué es | Cómo se marcan los campos | | --------- | ------------------------------------------------------ | ----------------------------------------------------- | | `html` | Un HTML con CSS en línea | `class="signmage-template-field"` y un `id` único | | `pdf` | Un PDF con formulario AcroForm | Nada que marcar: Firmame detecta los campos dinámicos | | `pdfmake` | Una definición de [pdfmake](https://pdfmake.github.io) | `{{variable}}` | | `docx` | Un documento de Word | Campos de correspondencia y controles de contenido | ### HTML [#html] Marca cada hueco con la clase y un `id`: ```html

(aquí va el valor) es el arrendatario de este contrato.

``` Las tablas dinámicas usan `class="signmage-template-table"` con su propio `id`. ```js await services.saveTemplate({ template_type: 'html', template_name: 'contrato-arrendamiento', template_data: Buffer.from(html).toString('base64'), }); ``` ### PDF con AcroForm [#pdf-con-acroform] Si ya tienes un PDF con formulario, no hay que marcar nada — Firmame lee los campos que el formulario declara, con su tipo y sus valores permitidos. ```js await services.saveTemplate({ template_type: 'pdf', template_name: 'solicitud', url_doc: 'https://ejemplo.com/solicitud-con-formulario.pdf', }); ``` `url_doc` evita el sobrecosto de Base64, que en un PDF grande no es menor. ### pdfmake [#pdfmake] ```js await services.saveTemplate({ template_type: 'pdfmake', template_name: 'pagare', template_data: { content: [ 'El suscrito {{nombre}} se obliga a pagar el día {{fecha}}.', ], }, }); ``` Ojo: en `pdfmake` el `template_data` va como objeto JSON crudo, **no** en Base64. ### DOCX [#docx] Marca cada hueco con uno de los dos mecanismos nativos de Word — Firmame usa el nombre que le des al campo como identificador del placeholder, igual que el `id` en HTML: * **Campos de correspondencia**: pestaña **Correspondencia → Insertar campo combinado**, o **Insertar → Elementos rápidos → Campo → MergeField** para escribir el nombre a mano sin necesitar una lista de datos. En el documento se ven como `«nombre_del_campo»`. * **Controles de contenido**: 1. Activa la pestaña **Programador**: **Archivo → Opciones → Personalizar cinta de opciones** y marca la casilla «Programador». 2. En **Programador → Controles**, inserta un **control de contenido de texto sin formato** o de **texto enriquecido** en el punto donde va el valor. 3. Con el control seleccionado, abre **Propiedades** y escribe el nombre del campo en **Etiqueta** (`Tag`) — ese es el identificador que Firmame lee. ```js await services.saveTemplate({ template_type: 'docx', template_name: 'carta-oferta', template_data: fs.readFileSync('carta.docx').toString('base64'), }); ``` ## Qué te devuelve [#qué-te-devuelve] Los placeholders que encontró, con el valor vacío. Arreglo de `{id, value}`, más un arreglo `tables`: ```json { "template_title": "contrato-arrendamiento", "fields": [{ "id": "arrendatario", "value": "" }], "tables": [{ "id": "conceptos", "rows": [] }] } ``` Arreglo de `{key, value, type}`, con `field_options` en los campos de opción: ```json { "template_title": "solicitud", "fields": [ { "key": "nombre_completo", "value": "", "type": "text" }, { "key": "regimen", "value": "", "type": "combobox", "field_options": ["Persona física", "Persona moral"] } ] } ``` Objeto plano indexado por el nombre del placeholder: ```json { "template_title": "pagare", "fields": { "nombre": "", "fecha": "" } } ``` Esa misma forma es la que devuelves al generar el documento. ## Generar un documento [#generar-un-documento] Es el `POST /developers/json` de siempre, con `template` en lugar de `url_doc`: ```js await services.request({ template: { title: 'contrato-arrendamiento', name: 'Contrato — Ana Ruiz', }, fields: [ { id: 'arrendatario', value: 'Ana Ruiz' }, { id: 'monto', value: '$12,500.00' }, ], stickers: [ /* … */ ], }); ``` Como es el mismo endpoint, puedes combinarlo con todo lo demás: stickers, flujo, expediente, QR. ## Consultar los templates de tu cuenta [#consultar-los-templates-de-tu-cuenta] ```js const templates = await services.listTemplates(); const fields = await services.getTemplateFields('contrato-arrendamiento'); ``` Útil para construir un formulario en tu propia interfaz sin tener los nombres de los campos escritos a mano. ## Referencia [#referencia] # Verificación de identidad (https://devs.firmame.com/docs/guias/verificacion-de-identidad) > Comprueba que el firmante es una persona real, con identificación oficial y prueba de vida. Puedes pedirle al firmante una verificación de identidad antes de firmar: es una capa extra de seguridad que confirma quién es, además de la [autoridad](/docs/guias/firmantes) de firma que ya usas. No la sustituye — se combina con cualquiera de ellas —, y la activas cuando el trámite necesita ese nivel adicional de certeza. ## Cómo activarla [#cómo-activarla] Agrega `requireIdentityVerification: true` al sticker: ```js { authority: 'EMAIL_LINK', data: 'firmante@dominio.com', email: 'firmante@dominio.com', requireIdentityVerification: true, page: 0, rect: { lx: 330, ly: 300, tx: 530, ty: 400 }, } ``` Si en cambio defines el sticker [dibujando el recuadro dentro del PDF](/docs/guias/recuadros-de-firma), el equivalente es la palabra clave `riv`: ``` Firmamex firmante@correo.com riv ``` Es un campo opcional y por defecto `false` — nada cambia si no lo mandas. Hoy solo está disponible en el sticker que se manda al crear un documento (`POST /developers/json`); los stickers premade de un expediente no lo incluyen. ## El proceso [#el-proceso] El firmante pasa por dos pasos antes de poder firmar: * **Identificación oficial.** Sube una identificación vigente, legible y a color — una fotocopia, una imagen borrosa o un documento vencido no pasan. * **Prueba de vida.** Una selfie tomada en el momento, para comprobar que quien la toma es una persona real —no una foto, no una pantalla, no otra identificación— y que corresponde a la persona de la identificación. Firmame corre varias verificaciones independientes sobre esos dos elementos, y el firmante puede reintentar si alguna no pasa. El catálogo completo de verificaciones y de los mensajes que puede devolver cada una está en la referencia del webhook, más abajo. ## Créditos [#créditos] Cada verificación de identidad consume créditos adicionales de tu cuenta, aparte de los que usa el documento. ## El webhook [#el-webhook] El resultado no llega en la respuesta de la API — llega por webhook: * **`identity_verification_completed`** — el firmante ya quedó verificado y puede firmar. * **`identity_verification_failed`** — alguna verificación no pasó. Ambos comparten la misma forma: `meta` es un arreglo con **un intento por entrada**, así que un firmante que falla y reintenta no genera varias notificaciones — genera una sola, con el arreglo más largo. ```json { "notification_type": "identity_verification_completed", "notification_id": "2701", "firmamex_id": "04337396-e929-43ca-9b8a-ec958a44079d", "document_title": "documento.pdf", "meta": [ { "status": "FAILURE", "date": "2025-03-24T04:11:42.000Z", "signer": "firmante@dominio.com", "errors": [{ "category": "USABILITY", "message": "PHOTOCOPY" }] }, { "status": "SUCCESS", "date": "2025-03-25T10:38:11.000Z", "signer": "firmante@dominio.com", "errors": [] } ] } ``` Como `meta` guarda todo el historial, en `identity_verification_completed` **lee la entrada con `status: "SUCCESS"`** en vez de asumir que solo hay una — puede venir precedida de varios intentos fallidos, como en el ejemplo. En `identity_verification_failed`, en cambio, todas las entradas son fallos, y el evento trae además `first_notification_date`, la fecha del primer intento de esa serie. Cada `error` trae una `category` (qué verificación falló: identificación oficial o prueba de vida) y un `message` con el detalle — el catálogo completo de ambos está documentado en la referencia. ## Referencia [#referencia] # Cómo funcionan (https://devs.firmame.com/docs/webhooks) > Firmame te avisa cuando algo pasa; tú usas ese aviso para descargar el documento. Un webhook es una URL tuya a la que Firmame hace `POST` cuando ocurre algo: alguien firmó, el documento se completó, una invitación rebotó. Es la alternativa a estar consultando el estado cada tanto. ## Configurar tu URL [#configurar-tu-url] Entra a tu cuenta de desarrollador en [firmamex.com/developers/login](https://firmamex.com/developers/login) y registra ahí la URL. Es una sola por cuenta, y recibe **todas** las notificaciones de tus documentos. La URL tiene que aceptar `POST` con `Content-Type: application/json`. ## El ciclo [#el-ciclo] La notificación **no trae el documento**, trae el `firmamex_id` con el que lo pides. Esa segunda llamada va autenticada como cualquier otra. ## Lo que llega [#lo-que-llega] Todos los eventos comparten el mismo sobre: ```json { "notification_type": "document_completed", "notification_id": "2698", "firmamex_id": "04337396-e929-43ca-9b8a-ec958a44079d", "document_title": "contrato.pdf", "text": "Document 04337396-… has been completed" } ``` | Campo | Qué es | | ------------------- | --------------------------------------------------------------------- | | `notification_type` | Qué pasó | | `notification_id` | Id único de la notificación | | `firmamex_id` | El id del documento — el mismo `document_ticket` de cuando lo creaste | | `document_title` | El nombre del archivo | | `meta` | Solo en algunos eventos; lleva el detalle específico | Los eventos de firma traen además `signer_email`, `signer_fingerprint` y `signer_data`. Como llegan todos los tipos a la misma URL, **discrimina por `notification_type`** al recibir. ## Obtener el documento [#obtener-el-documento] Con el `firmamex_id` de la notificación descargas el documento firmado en [`GET /api/document/{type}/{ticket}`](/docs/api/descargas/download-document), eligiendo `type`: `original` para el PDF con las firmas tal como se aplicaron, o `universal` para esas mismas firmas envueltas en un sobre de validación a largo plazo. ```js const pdf = await services.downloadDocument(firmamexId, 'original'); ``` *Original* y *universal* son dos representaciones firmadas del mismo documento; la diferencia está en [Conceptos](/docs/conceptos#original-y-universal). ## Qué evento escuchar [#qué-evento-escuchar] Para la mayoría de las integraciones, **`document_completed`** es el único que importa: se dispara cuando ya firmaron todos y el documento está listo. `original_signed` llega una vez por cada firma, así que en un documento de tres firmantes lo vas a recibir tres veces. Úsalo si quieres mostrar avance parcial; no lo uses para disparar tu proceso de negocio. Los eventos de error —`email_error`, `sms_error`, `whatsapp_error`— avisan que una invitación no llegó. Vale la pena atenderlos: sin ellos, un correo mal escrito se ve igual que un firmante que se está tardando. El catálogo completo, con el payload de cada uno, está en la [referencia](/docs/api/webhooks-y-cuenta/document-completed). ## Qué espera Firmame de tu endpoint [#qué-espera-firmame-de-tu-endpoint] * Responde **`200`**. Cualquier otra cosa cuenta como fallo. * Responde rápido. Si vas a hacer trabajo pesado, encólalo y contesta el `200` de inmediato. * Responde `200` **también** cuando llegue un evento que no esperabas. Los tipos nuevos no deberían tumbar tu endpoint. ### Reintentos [#reintentos] Si tu servidor no contesta `200`, Firmame reintenta con backoff exponencial: ``` 10 segundos + min(2 ^ intentos, 600) + jitter ``` El tiempo entre intentos crece hasta un máximo de **600 segundos (10 minutos)**, con hasta **30 intentos**. En la práctica eso te da varias horas de margen para levantar un servicio caído sin perder notificaciones. **Una URL inválida no se reintenta.** Si el registro apunta a un host que no resuelve, no hay nada que reintentar — vale la pena verificar la URL al registrarla. ### Idempotencia [#idempotencia] `notification_id` es único por notificación. Guárdalo al procesar y descarta los repetidos: es la forma más simple de que un reintento no cobre dos veces, no mande dos correos, ni cree dos registros. ## Probarlo [#probarlo] Durante el desarrollo, un túnel hacia tu máquina (ngrok, Cloudflare Tunnel o similar) te deja registrar una URL pública que apunta a tu servidor local y ver las notificaciones llegar de verdad. ## Suscripciones por evento [#suscripciones-por-evento] Existe además un esquema de **suscripciones**, donde registras una URL distinta por tipo de evento vía API en lugar de una sola en el panel. Es lo que usan integraciones como Zapier, y está en [beta](/docs/webhooks/suscripciones). # Suscripciones por evento (beta) (https://devs.firmame.com/docs/webhooks/suscripciones) > Registra una URL distinta por tipo de evento vía API, en lugar de una sola en el panel. Este esquema está en beta. Es el que usan integraciones como Zapier, y su comportamiento puede cambiar. Para una integración en producción, el [webhook único](/docs/webhooks) es la vía estable. En lugar de una URL en el panel que recibe todo, aquí registras **una URL por tipo de evento** desde la API. Sirve cuando distintos eventos deben ir a distintos destinos — por ejemplo, las firmas a tu backend y los errores de envío a una herramienta de monitoreo. ## Cómo cambia el comportamiento [#cómo-cambia-el-comportamiento] En cuanto registras la primera suscripción, tu cuenta pasa al esquema de suscripciones y **la URL única del panel deja de recibir**. A partir de ahí, un evento se entrega solo si tiene una suscripción registrada; los tipos que no suscribas no se entregan. Por eso conviene planearlo: antes de registrar la primera, ten listas las suscripciones de todos los eventos que hoy procesas. Borrarlas después no regresa la cuenta al esquema anterior — si necesitas volver, escríbenos a **[soporte@firmamex.com](mailto:soporte@firmamex.com)**. ## Registrar [#registrar] ```js await services.addWebhook({ url: 'https://tu-servidor.com/hooks/firmas', type: 'document_completed', }); ``` La respuesta trae el id de la suscripción. Guárdalo: es lo único con lo que puedes darla de baja. Una URL puede repetirse en varios tipos, y un tipo puede tener varias URLs — se entrega a todas las registradas para ese evento. ```js // el mismo destino para dos eventos await services.addWebhook({ url: BACKEND, type: 'document_completed' }); await services.addWebhook({ url: BACKEND, type: 'document_rejected' }); // y los errores de envío a otro lado await services.addWebhook({ url: MONITOREO, type: 'email_error' }); ``` ## Dar de baja [#dar-de-baja] ```js await services.deleteWebhook({ id: subscriptionId }); ``` `DELETE /api/v1/webhooks` lleva el id **en el cuerpo JSON**, no en la ruta ni en el query string. Algunos clientes HTTP omiten el cuerpo en `DELETE`, así que verifica que el tuyo sí lo mande. Dar de baja un id que ya no existe también responde correctamente. ## Tipos de evento [#tipos-de-evento] Los mismos del [catálogo](/docs/api/webhooks-y-cuenta/document-completed): `document_completed`, `original_signed`, `universal_signed`, `document_rejected`, `workflow_expired`, los tres de error de envío, los dos de NOM-151 y los dos de verificación de identidad. El [esquema `WebhookType`](/docs/api/webhooks-y-cuenta/add-webhook) lista los valores aceptados. ## Lo demás no cambia [#lo-demás-no-cambia] El sobre de la notificación, la descarga del documento, la política de reintentos y la idempotencia funcionan igual que en el [esquema estándar](/docs/webhooks). ## Referencia [#referencia] # Obtener un documento desde una notificación de webhook (https://devs.firmame.com/docs/api/consultas/get-data-by-webhook) `POST https://app.firmame.com/developers/webhook` Requiere el header `Authorization` del esquema `signmage`. Convierte el `notification_id` de un webhook en el documento mismo. Este es el paso natural después de recibir una notificación: el webhook te dice *que* pasó algo, esto te dice *qué*. `action` elige qué representación obtienes, y si te la dan en bytes o en JSON: | `action` | Devuelve | |---|---| | `download_original_bytes` | El PDF firmado **original**, en bytes | | `download_original_document` | El original, como metadatos JSON | | `download_universal_bytes` | El PDF **universal** (encapsulado), en bytes | | `download_universal_document` | El universal, como metadatos JSON | | `download_document_bytes` | El documento tal como está guardado, en bytes | | `download_document` | El documento tal como está guardado, como metadatos JSON | *Original* y *universal* son dos representaciones firmadas del mismo documento: el original lleva las firmas tal como se aplicaron, el universal las envuelve en un sobre de validación a largo plazo. ## Cuerpo de la petición `application/json`: - `notification_id` (string, requerido) — El `notification_id` que venía en el payload del webhook. - `action` (string, requerido, `download_original_bytes` | `download_original_document` | `download_universal_bytes` | `download_universal_document` | `download_document` | `download_document_bytes`) Ejemplo — Descargar el PDF firmado: ```json { "notification_id": 84512, "action": "download_original_bytes" } ``` Ejemplo — Obtener los metadatos en vez del archivo: ```json { "notification_id": 84512, "action": "download_original_document" } ``` ## Respuestas ### `200` El PDF en las acciones `*_bytes`, servido como `application/pdf` con un header `Content-disposition` de adjunto; metadatos JSON en las demás. `application/pdf` — `string`. `application/json`: - `name` (string, opcional) — The document's original file name. - `signatures_count` (integer, opcional) — How many signatures the document carries. - `data` (string, opcional, format `byte`) — The PDF, Base64-encoded. - `error` (string, opcional) — Present instead of the other fields when no document matched. Ejemplo: ```json { "name": "contrato.pdf", "signatures_count": 2, "data": "JVBERi0xLjcNCiW1tbW1…" } ``` Ejemplo: ```json { "error": "no_document_found" } ``` ### `204` La petición no traía cuerpo. ### `400` O faltó `action` —cuerpo `action is missing`— o ninguna notificación correspondió a un documento —cuerpo `no_document_found`—. Los dos en texto plano. `text/plain` — `string`. Ejemplo: ``` action is missing ``` Ejemplo: ``` no_document_found ``` ### `401` No se logró autenticar. El cuerpo viene vacío; revisa tu web ID, que la llave de API esté activa, y que la cadena firmada siga las reglas del esquema `signmage`. # Descargar el historial de un documento en PDF (https://devs.firmame.com/docs/api/consultas/get-document-history-pdf) `GET https://app.firmame.com/api/v1/document/{frmxId}/history/pdf` Requiere el header `Authorization` del esquema `signmage`. El mismo historial que [`GET /api/v1/document/{frmxId}/history/json`](/docs/api/consultas/get-document-history), pero como PDF listo para entregárselo a alguien. Es el archivo que Firmame adjunta al correo de flujo completado, y el que un paquete de verificación NOM-151 espera como historial. La respuesta es el PDF mismo, no base64 dentro de un JSON: lee los bytes y escríbelos a un archivo. Llega como `Content-disposition: attachment; filename=historial.pdf`. Usa [la versión en JSON](/docs/api/consultas/get-document-history) cuando lo que quieras sea mover tu propia interfaz con los eventos — ahí está también el catálogo de tipos de evento. ## Parámetros | Nombre | En | Tipo | Requerido | Descripción | |---|---|---|---|---| | `frmxId` | path | string | requerido | El id del documento. | ## Respuestas ### `200` El historial de eventos como PDF. `application/pdf` — `string`. ### `401` No se logró autenticar. El cuerpo viene vacío; revisa tu web ID, que la llave de API esté activa, y que la cadena firmada siga las reglas del esquema `signmage`. ### `500` No se pudo generar el historial. El cuerpo viene vacío. # Obtener el historial de un documento (https://devs.firmame.com/docs/api/consultas/get-document-history) `GET https://app.firmame.com/api/v1/document/{frmxId}/history/json` Requiere el header `Authorization` del esquema `signmage`. El historial de un documento contiene información de la generación de un documento, envío, seguimiento y firmas. Este endpoint te lo devuelve como un arreglo de eventos, del más reciente al más viejo. Sirve para mostrar el avance en tu propia interfaz sin esperar a los webhooks. Si lo quieres como PDF para entregárselo a alguien, usa [`GET /api/v1/document/{frmxId}/history/pdf`](/docs/api/consultas/get-document-history-pdf). ### Los eventos | `documentEventType` | Significado | |---|---| | `DOCUMENT_CREATED` | Se creó el documento | | `SIGNATURE_INVITATION` | Se envió una invitación para firma | | `NON_SIGNER_RECIPENT_FLOW_STARTED` | Se notificó a un destinatario no firmante | | `FLOW_STARTED` | Se inició el proceso de firma | | `FLOW_EXPIRED` | El proceso de firma llegó a su fecha de expiración | | `DOCUMENT_SIGNED` | Se firmó el documento | | `DOCUMENT_STAMPED` | Se selló el documento | | `DOCUMENT_COMPLETED` | Se completó el documento | | `REJECTED_DOCUMENT` | Un firmante rechazó el documento | | `DOCUMENT_BLOCKED_FOR_SIGNATURE` | Se bloqueó el documento para firma | | `DOCUMENT_UNBLOCKED_FOR_SIGNATURE` | Se desbloqueó el documento para firma | | `DOCUMENT_FLATTENED` | Se aplanaron los campos de formulario del documento | | `DOCUMENT_TRANSFERRED` | Se transfirió el documento a otro usuario | | `IDENTITY_VERIFICATION_PASSED` | Un firmante pasó la verificación de identidad | | `IDENTITY_VERIFICATION_FAILED` | Un firmante no pasó la verificación de identidad | | `EMAIL_DELIVERED` | Se entregó un correo | | `EMAIL_OPENED` | El destinatario abrió el correo | | `EMAIL_CLICKED` | El destinatario hizo clic en el correo | | `EMAIL_FAILED` | Falló una entrega de correo | | `EMAIL_COMPLAINED` | El destinatario reportó el correo como spam | | `WHATSAPP_SENT` | Se envió un mensaje de WhatsApp | | `WHATSAPP_DELIVERED` | Se entregó un mensaje de WhatsApp | | `WHATSAPP_READ` | Se leyó un mensaje de WhatsApp | Compara contra estas cadenas tal como están escritas, `NON_SIGNER_RECIPENT_FLOW_STARTED` incluida. ### Qué trae `jsonMetaData` `jsonMetaData` es una **cadena con JSON dentro**, no un objeto JSON: parséala antes de leerla. Sus llaves dependen del evento, y viene en `null` para los eventos que no llevan nada extra. | Evento | Llaves | |---|---| | `DOCUMENT_CREATED` | `createdBy` | | `SIGNATURE_INVITATION` y los demás eventos de invitación | `sentTo` | | `FLOW_STARTED` | `ordered`, `remindEvery`, `lastSignatureCount`, `expirationDate`, `lockOnExpiration` | | `FLOW_EXPIRED` | `expirationDate`, `lockOnExpiration` | | `DOCUMENT_SIGNED` | `signer`, `ipAddress`, y `boxData` / `boxContactData` cuando el sticker los trae | | `REJECTED_DOCUMENT` | `rejector`, `rejectReason` | | `DOCUMENT_UNBLOCKED_FOR_SIGNATURE` | `comments` | | `DOCUMENT_TRANSFERRED` | `originalOwnerUserId`, `newOwnerUserId` | | `IDENTITY_VERIFICATION_PASSED` / `_FAILED` | `signerData`, `workflowId`, y `firstName`, `lastName`, `curp` cuando se leyó la identificación | | `EMAIL_*` | `threadId`, `recipient` | El `threadId` de los eventos de correo es el mismo id que le pasas a los endpoints de correos certificados, así que puedes pasar de «este correo se abrió» al mensaje mismo. ## Parámetros | Nombre | En | Tipo | Requerido | Descripción | |---|---|---|---|---| | `frmxId` | path | string | requerido | El id del documento. | ## Respuestas ### `200` Los eventos del documento, del más reciente al más viejo. `application/json`: - `documentEventType` (string, opcional) — Cuál evento es. Ve la tabla de arriba. - `jsonMetaData` (string | null, opcional) — Detalle extra como cadena JSON, o `null`. Las llaves dependen del evento. - `eventDate` (integer, opcional) — Cuándo ocurrió el evento, en milisegundos del epoch. - `createdAt` (integer, opcional) — Cuándo se registró el evento, en milisegundos del epoch. Ejemplo: ```json [ { "documentEventType": "DOCUMENT_STAMPED", "jsonMetaData": null, "eventDate": 1711128805000, "createdAt": 1711128805000 }, { "documentEventType": "EMAIL_OPENED", "jsonMetaData": "{\"threadId\":\"<20240322175325.1.abc@mg.firmamex.com>\",\"recipient\":\"firmante@dominio.com\"}", "eventDate": 1711128512000, "createdAt": 1711128512000 }, { "documentEventType": "DOCUMENT_CREATED", "jsonMetaData": "{\"createdBy\":\"tu-cuenta@dominio.com\"}", "eventDate": 1711128400000, "createdAt": 1711128400000 } ] ``` ### `401` No se logró autenticar. El cuerpo viene vacío; revisa tu web ID, que la llave de API esté activa, y que la cadena firmada siga las reglas del esquema `signmage`. ### `422` No hay ningún documento con ese id en esta cuenta. # Obtener el reporte de firmas de un documento (https://devs.firmame.com/docs/api/consultas/get-document-report) `GET https://app.firmame.com/api/report/{ticket}` Requiere el header `Authorization` del esquema `signmage`. Devuelve el reporte forense completo del documento: cada firma con su posición, su cadena de certificados, su estado OCSP, su autoridad de sellado de tiempo, y si sigue siendo válida. Este es el endpoint para verificar un documento por fuera, no para ver el avance — para el avance usa `GET /api/document` o los webhooks. ## Parámetros | Nombre | En | Tipo | Requerido | Descripción | |---|---|---|---|---| | `ticket` | path | string | requerido | El id del documento (`firmamexId` / `document_ticket`). | ## Respuestas ### `200` El reporte, con una entrada en `firmas` por cada firma encontrada en el documento. Las fechas dentro del reporte son cadenas con formato, no milisegundos del epoch — a diferencia del resto de esta API. Varios campos de una entrada son condicionales y simplemente no vienen cuando no aplican, así que revisa si la llave existe en vez de esperar un `null`. `application/json`: - `firmas` (array, opcional) — Una entrada por firma digital. - `page` (integer, opcional) Página donde está el recuadro de la firma, contando desde 1 — el `page` de un sticker cuenta desde 0, así que difieren en uno. `-1` cuando la firma no tiene recuadro. - `left` (number, opcional) — Distancia al borde izquierdo, en unidades PDF. - `top` (number, opcional) — Distancia del borde inferior a la parte superior del recuadro, en unidades PDF. - `width` (number, opcional) - `height` (number, opcional) - `campoFirma` (string, opcional) — Nombre del campo de firma del PDF. - `signatureType` (string, opcional, `nom151` | `universal` | `original`) — A cuál representación pertenece esta firma. - `validSignature` (boolean, opcional) — Si la firma verifica contra los bytes que cubre. - `coversDocument` (boolean, opcional) Si la firma cubre el documento completo. `false` significa que se agregó contenido después, lo normal en todas las firmas menos la última. - `downstream` (boolean, opcional) — Si la firma se recolectó a través de una autoridad de Firmame. - `signDate` (string, opcional) — Cuándo se aplicó la firma, como `EEE MMM dd HH:mm:ss zzz yyyy` — no es ISO 8601. - `totalRevisions` (integer, opcional) — Siempre `0`. - `reason` (string, opcional) — La razón registrada en la firma, cuando trae una. - `certType` (string, opcional, `EMAIL` | `IDENTITY` | `NONE`) El tipo de confianza detrás del certificado de firma. `NONE` significa que la autoridad emisora no es una que Firmame reconozca. - `signCertificateSerial` (string, opcional) — Número de serie del certificado de firma, en hexadecimal y minúsculas. - `box` (integer, opcional, format `int64`) — Id del sticker que llenó esta firma. Solo aparece en las firmas recolectadas por Firmame. - `email` (string, opcional) — El correo del firmante, cuando el certificado o la autoridad lo traen. - `rfc` (string, opcional) — El RFC del firmante, cuando el certificado trae un UID de menos de 20 caracteres. - `uid` (string, opcional) — El UID del certificado, cuando tiene 20 caracteres o más. - `ipAddress` (string, opcional) — Dirección desde la que se aplicó la firma, cuando quedó registrada. - `latLng` (string, opcional) — Coordenadas desde las que se aplicó la firma, cuando quedaron registradas. - `timestampAuthority` (string, opcional) — Nombre distintivo de la autoridad de sellado de tiempo, cuando la firma trae sello. - `downstreamData` (object, opcional) — Cómo se identificó el firmante, en las firmas recolectadas a través de Firmame. - `authority` (string, opcional) - `dataType` (string, opcional) - `subject` (string, opcional) - `certImage` (string, opcional) - `ocsp` (object, opcional) — La consulta de revocación; solo aparece cuando la firma trae una respuesta OCSP dentro. - `signatureValid` (boolean, opcional) — Siempre `true` cuando este objeto está presente. - `certStatus` (string, opcional) `good` cuando el certificado era válido al momento de firmar. Cualquier otro valor significa que no lo era, y no es una cadena estable con la que puedas comparar. - `signer` (string, opcional) — Nombre común del respondedor que contestó la consulta. - `certificados` (array, opcional) — La cadena de certificados de la firma. Siempre viene, aunque puede ir vacía. - `commonName` (string, opcional) - `email` (string, opcional) - `organization` (string, opcional) - `location` (string, opcional) - `country` (string, opcional) - `serial` (string, opcional) — Número de serie, en hexadecimal. - `signatureAlgorithm` (string, opcional) - `expirationDate` (string, opcional) - `validationDate` (string, opcional) - `issuerCommonName` (string, opcional) - `issuerEmail` (string, opcional) - `issuerOrganization` (string, opcional) - `issuerLocation` (string, opcional) - `issuerCountry` (string, opcional) - `publicKey` (string, opcional) - `hexCertificado` (string, opcional) — El certificado completo, DER, en hexadecimal y mayúsculas. - `InkSignatures` (array, opcional) Siempre vacío. El campo se conserva para que las integraciones existentes sigan parseando. Ejemplo: ```json { "InkSignatures": [], "firmas": [ { "page": 1, "left": 189.3, "top": 594.6, "width": 200.99998, "height": 42.599976, "downstream": true, "campoFirma": "Signature1", "signatureType": "original", "validSignature": true, "coversDocument": false, "totalRevisions": 0, "signDate": "Tue May 09 12:26:44 GMT-07:00 2023", "certType": "EMAIL", "signCertificateSerial": "63ce4e8552b71b338981f6781866912cb691033b", "box": 184213, "email": "firmante@dominio.com", "ipAddress": "0:0:0:0:0:0:0:1", "timestampAuthority": "C=CA,ST=Ontario,O=Entrust\\, Inc.,CN=Entrust Timestamp Authority - TSA1", "downstreamData": { "authority": "EMAIL", "dataType": "EMAIL", "subject": "firmante@dominio.com" }, "ocsp": { "signatureValid": true, "certStatus": "good", "signer": "Autoridad de Certificacion por Correo de Firmamex" }, "certificados": [ { "commonName": "firmante@dominio.com", "email": "firmante@dominio.com", "serial": "63ce4e8552b71b338981f6781866912cb691033b", "signatureAlgorithm": "SHA256WITHRSA", "issuerCommonName": "Autoridad de Certificacion por Correo de Firmamex" } ] } ] } ``` ### `400` No se pudo generar el reporte — normalmente, un ticket desconocido. `application/json`: - `error` (string, opcional) ### `401` No se logró autenticar. El cuerpo viene vacío; revisa tu web ID, que la llave de API esté activa, y que la cadena firmada siga las reglas del esquema `signmage`. # Listar documentos (https://devs.firmame.com/docs/api/consultas/list-documents) `GET https://app.firmame.com/api/document` Requiere el header `Authorization` del esquema `signmage`. Lista los documentos creados en una ventana de tiempo, del más reciente al más viejo. Los resultados vienen **paginados por cursor**: si la respuesta trae `nextToken` hay más resultados y se lo regresas para pedir la siguiente página. Si no lo trae, ya los tienes todos — así que itera hasta que desaparezca, no cuentes páginas. Acuérdate de que es un `GET`: la firma cubre la URI completa incluyendo estos parámetros de consulta, en el orden en que los mandes. ## Parámetros | Nombre | En | Tipo | Requerido | Descripción | |---|---|---|---|---| | `from` | query | integer, format `int64` | requerido | Inicio de la ventana, en milisegundos desde el epoch de Unix. | | `to` | query | integer, format `int64` | requerido | Fin de la ventana, en milisegundos desde el epoch de Unix. | | `nextToken` | query | string | opcional | El cursor de la respuesta anterior. Omítelo en la primera página. | ## Respuestas ### `200` Una página de documentos. `application/json`: - `documents` (array, opcional) - `firmamexId` (string, opcional) — El id del documento. - `originalName` (string, opcional) - `createdAt` (integer, opcional) — Fecha de creación, en milisegundos desde el epoch de Unix. - `active` (boolean, opcional) — Si el documento sigue siendo alcanzable, es decir, no eliminado. - `signaturesCount` (integer, opcional) - `stickerStatus` (string, opcional, `NOSTICKERS` | `STICKERS` | `SITCKERS_SIGNED`) - `NOSTICKERS` — el documento no tiene recuadros de firma. - `STICKERS` — al menos un recuadro sigue sin firmar. - `SITCKERS_SIGNED` — todos los recuadros están firmados. Compara contra esta cadena exacta. - `nextToken` (string, opcional) Cursor de la siguiente página. **No viene cuando ya no hay más resultados** — la condición de fin es su ausencia, no un valor vacío. Ejemplo: ```json { "documents": [ { "active": true, "createdAt": 1638320273000, "firmamexId": "0000-0000-0000-0000", "originalName": "Contrato", "signaturesCount": 1, "stickerStatus": "STICKERS" } ], "nextToken": "token" } ``` ### `401` No se logró autenticar. El cuerpo viene vacío; revisa tu web ID, que la llave de API esté activa, y que la cadena firmada siga las reglas del esquema `signmage`. # Descargar un documento firmado (https://devs.firmame.com/docs/api/descargas/download-document) `GET https://app.firmame.com/api/document/{type}/{ticket}` Requiere el header `Authorization` del esquema `signmage`. Descarga el PDF firmado, codificado en Base64 dentro de un sobre JSON, junto con su hash. `type` elige qué representación firmada quieres. La llave de la respuesta **coincide con el `type` que pediste** — si pides `universal`, el PDF regresa bajo `universal`, no bajo un `document` genérico. ## Parámetros | Nombre | En | Tipo | Requerido | Descripción | |---|---|---|---|---| | `type` | path | string, `original` | `universal` | requerido | - `original` — el documento con las firmas tal como se aplicaron. - `universal` — esas mismas firmas envueltas en un sobre de validación a largo plazo, que es el que conviene archivar. | | `ticket` | path | string | requerido | El id del documento. | ## Respuestas ### `200` El documento firmado. `application/json`: - `original` (string, opcional) — El PDF en Base64. Aparece cuando `type` es `original`. - `universal` (string, opcional) — El PDF en Base64. Aparece cuando `type` es `universal`. - `hash` (string, opcional) — SHA-256 en Base64 del PDF devuelto. Ejemplo: ```json { "universal": "JVBERi0xLjcNCiW1tbW1…", "hash": "JyK0EkYQ4IJHNi/x2D77z+Ja8JH/jx6YilFJHuto9kU=" } ``` Ejemplo: ```json { "original": "JVBERi0xLjcNCiW1tbW1…", "hash": "JyK0EkYQ4IJHNi/x2D77z+Ja8JH/jx6YilFJHuto9kU=" } ``` ### `400` No se pudo generar el documento — ticket desconocido o `type` desconocido. `application/json`: - `error` (string, opcional) ### `401` No se logró autenticar, o esta cuenta no tiene acceso a ese documento. Los dos casos devuelven un `401` vacío. # Descargar la revisión que ampara un sello (https://devs.firmame.com/docs/api/descargas/download-stamped-revision) `GET https://app.firmame.com/api/document/{frmxId}/stamp/{stampHash}/pdf` Requiere el header `Authorization` del esquema `signmage`. Devuelve la revisión exacta del documento sobre la que se aplicó un sello NOM-151 — los bytes cuyo hash certifica el sello. El `stampHash` sale de `GET /api/document/{frmxId}/stamps`. Esto es lo que permite que un tercero verifique el sello: necesita el documento como estaba en ese momento, no como está ahora. ## Parámetros | Nombre | En | Tipo | Requerido | Descripción | |---|---|---|---|---| | `frmxId` | path | string | requerido | El id del documento. | | `stampHash` | path | string | requerido | El `hash` del sello. | ## Respuestas ### `200` La revisión sellada, en Base64 dentro de un sobre JSON. `application/json`: - `name` (string, opcional) — The document's file name. - `data` (string, opcional, format `byte`) — The PDF of that revision, Base64-encoded. Ejemplo: ```json { "name": "contrato.pdf", "data": "JVBERi0xLjcNCiW1tbW1…" } ``` ### `400` Documento o sello desconocido. El cuerpo trae la descripción en texto plano. `text/plain` — `string`. ### `401` No se logró autenticar. El cuerpo viene vacío; revisa tu web ID, que la llave de API esté activa, y que la cadena firmada siga las reglas del esquema `signmage`. # Obtener los datos de un correo certificado (https://devs.firmame.com/docs/api/descargas/get-certified-email-data) `GET https://app.firmame.com/api/certifiedEmail/{frmxId}/data` Requiere el header `Authorization` del esquema `signmage`. Devuelve la evidencia de un correo certificado — a quién se envió, cuándo se entregó, cuándo se abrió, y el contenido que se mandó. Lee el `Content-Type` de la respuesta en lugar de dar por hecho que es JSON: refleja el formato en el que está guardada la evidencia. ## Parámetros | Nombre | En | Tipo | Requerido | Descripción | |---|---|---|---|---| | `frmxId` | path | string | requerido | El id del correo certificado. | ## Respuestas ### `200` Los datos del correo certificado. `application/json` — `object`. ### `401` No se logró autenticar. El cuerpo viene vacío; revisa tu web ID, que la llave de API esté activa, y que la cadena firmada siga las reglas del esquema `signmage`. # Listar los sellos NOM-151 de un documento (https://devs.firmame.com/docs/api/descargas/list-nom151-stamps) `GET https://app.firmame.com/api/document/{frmxId}/stamps` Requiere el header `Authorization` del esquema `signmage`. Devuelve todos los sellos de tiempo NOM-151 aplicados al documento, cada uno con la autoridad que lo emitió, cuándo se emitió, y el token mismo. Usa el `hash` de un sello con `GET /api/document/{frmxId}/stamp/{stampHash}/pdf` para bajar la revisión exacta del documento que ese sello ampara. ## Parámetros | Nombre | En | Tipo | Requerido | Descripción | |---|---|---|---|---| | `frmxId` | path | string | requerido | El id del documento. | ## Respuestas ### `200` Los sellos del documento. `application/json`: - `timestamps` (array, opcional) - `hash` (string, opcional) — Hash del contenido sellado; también el id con el que se pide el PDF. - `tsa` (string, opcional) — Nombre distinguido de la autoridad de sellado de tiempo. - `genTime` (string, opcional, format `date-time`) — Cuándo se emitió el sello, en ISO 8601 — no en milisegundos del epoch. - `status` (string, opcional) - `timestamp` (string, opcional) — El token RFC 3161, codificado en Base64. - `policy` (string, opcional) — OID de la política de sellado de tiempo. Ejemplo: ```json { "timestamps": [ { "hash": "ce92da77b45ddbb26ab4210600023de8f0effb062276cd4c2765feed3988cfc4", "tsa": "L=MIGUEL HIDALGO,ST=CIUDAD DE MEXICO,C=MX,CN=CECOBAN-PSC-NOM151-1-01,O=CECOBAN SA DE CV", "genTime": "2023-07-10T18:21:28.467Z", "status": "granted", "timestamp": "MIISvDADAgEAMIISswYJKoZ…", "policy": "2.16.484.101.10.316.2.3.1.2.1.1.1" } ] } ``` ### `400` Documento desconocido. El cuerpo trae la descripción en texto plano. `text/plain` — `string`. ### `401` No se logró autenticar. El cuerpo viene vacío; revisa tu web ID, que la llave de API esté activa, y que la cadena firmada siga las reglas del esquema `signmage`. # Crear un documento (https://devs.firmame.com/docs/api/documentos/create-document) `POST https://app.firmame.com/developers/json` Requiere el header `Authorization` del esquema `signmage`. El endpoint principal. Sube un PDF, coloca los recuadros de firma sobre él y devuelve la URL donde se puede firmar. El documento puede venir de una URL, de Base64 o de una plantilla ya registrada — elige uno. Cada entrada de `stickers` es un firmante en un lugar; dale `email` y configura un flujo si quieres que Firmame mande las invitaciones, si no, reparte tú `document_url`. ## Cuerpo de la petición `application/json`: - `url_doc` (object, opcional) — Una URL desde la que Firmame descarga el PDF. - `url` (string, requerido, format `uri`) - `name` (string, opcional) — Nombre de archivo que se muestra en la interfaz y en las notificaciones. - `b64_doc` (object, opcional) — El PDF en línea, codificado en Base64. - `data` (string, requerido) — El PDF, codificado en Base64. - `name` (string, opcional) - `template` (object, opcional) — Una plantilla dada de alta antes con `POST /developers/template/save`. - `title` (string, requerido) — El nombre con el que se registró la plantilla. - `name` (string, opcional) — Título del documento que se genere a partir de ella. - `stickers` (array, opcional) — Los recuadros de firma. Omítelo para guardar el documento sin firmantes. - `authority` (Authority, requerido) Cómo comprueba el firmante quién es. En integraciones nuevas usa el código corto. | Código corto | Qué hace el firmante | |---|---| | `SAT` | Se identifica con sus archivos `.cer` y `.key` del SAT | | `OAUTH_TOKEN` | Entra con Google o Outlook, o se verifica con una liga enviada por correo | | `EMAIL_LINK` | Recibe una liga de firma por correo; tener la liga es la prueba | | `EMAIL` | Responde a un correo para verificarse | | `WHATSAPP_LINK` | Recibe una liga de firma por WhatsApp | | `WHATSAPP_PIN` | Recibe un PIN de un solo uso por WhatsApp al momento de firmar | | `SMS_LINK` | Recibe una liga de firma por SMS | | `SMS` | Recibe la invitación a firmar por SMS | Con las autoridades por liga solo el firmante tiene la URL que autoriza firmar, y Firmame manda la invitación en automático. Con `SAT`, `EMAIL` y `SMS`, si no configuras un flujo, **repartir la URL de firma es responsabilidad tuya**. - `data` (string, requerido) El valor que identifica al firmante, interpretado según `authority` — un correo, un número de teléfono, o un RFC para certificados del SAT. - `page` (integer, requerido) — Página donde va la firma. **Empieza en cero** — la primera página es `0`. - `rect` (Rect, requerido) Dónde va el sticker dentro de la página, en coordenadas de PDF. **El origen `(0,0)` es la esquina inferior izquierda** de la página, no la superior izquierda — un rectángulo que se ve bien en un editor con coordenadas de pantalla aquí saldrá de cabeza. - `lx` (number, requerido) — X de la esquina inferior izquierda. - `ly` (number, requerido) — Y de la esquina inferior izquierda. - `tx` (number, requerido) — X de la esquina superior derecha. - `ty` (number, requerido) — Y de la esquina superior derecha. - `stickerType` (string, opcional, `rect` | `line`, por defecto `rect`) - `rect` — centra la firma dentro del rectángulo. - `line` — la coloca centrada sobre el borde inferior, como una línea de firma. - `imageType` (ImageType, opcional, `desc` | `name` | `hash` | `stroke` | `endorsment`, por defecto `desc`) Cómo se ve la firma dibujada en la página. - `desc` — el texto «Firma realizada con certificado emitido por [emisor] para: [nombre]». - `name` — el nombre del firmante según su certificado, con una tipografía manuscrita. - `hash` — el SHA-256 en Base64 del documento antes de firmarse, seguido del nombre del firmante. - `stroke` — la firma dibujada a mano en la app. - `endorsment` — el texto del endoso. Solo tiene sentido en el sticker `endorser` de `POST /api/document/{firmamexId}/endorse`. Se pueden combinar dos con `+`, por ejemplo `stroke+name`. Más de un `+` es un error. - `email` (string, opcional, format `email`) El correo del firmante. **Obligatorio si quieres que Firmame lleve el flujo de firma** (mandar la invitación, los recordatorios); opcional si tú repartes la URL de firma. Omítelo en las autoridades por SMS, que identifican al firmante por teléfono; mandar los dos se rechaza. - `dataType` (string, opcional) — Tipo explícito de `data`, cuando no se puede inferir. - `customData` (string, opcional) — Cadena opaca que se te regresa en los webhooks de este firmante. - `sendEmailInvitation` (boolean, opcional, por defecto `false`) — Enviar el correo de invitación para este firmante. - `requireLocation` (boolean, opcional, por defecto `false`) — Exigir que el firmante comparta su ubicación antes de firmar. - `requireIdentityVerification` (boolean, opcional, por defecto `false`) Exigir verificación remota de identidad antes de firmar. Los fallos y los éxitos llegan como los eventos de webhook `identity_verification_failed` e `identity_verification_completed`. - `workflow` (Workflow, opcional) Quién firma, en qué orden, y cómo los persigue Firmame. Adjúntalo a `POST /developers/json` para dejar el flujo armado al crear el documento, o mándalo a `POST /api/{firmamexId}/workflow` para agregarlo o cambiarlo después. **Todas las propiedades aceptan `snake_case` y `camelCase`** — `attach_timestamp` y `attachTimestamp` son el mismo campo. Los ejemplos usan camelCase, igual que los SDKs. Lo que no mandes toma su valor por defecto: recordatorios cada 24 h, archivos adjuntos, aviso a todos, español, y expiración a 14 días. - `subject` (string, opcional) Asunto del correo de invitación. Mantenlo por debajo de 150 caracteres; si es más largo se usa el asunto por defecto. - `message` (string, opcional) Cuerpo del correo de invitación. También se acepta como `user_message`. Mantenlo por debajo de 3500 caracteres. - `expirationDate` (string, opcional) Cuándo expira el flujo, como `dd/MM/yyyy` o `dd/MM/yyyy HH:mm`. **Primero el día** — `12/11/2021` es 12 de noviembre. Cualquier otra cosa falla con `error parsing workflow expiration date`. Por defecto, 14 días. - `remindEvery` (string, opcional) Cada cuánto recordarle a los firmantes pendientes: `none`, `12h`, o `Nd` con N de 1 a 7. Los valores fuera de ese rango conservan el valor por defecto de 24 h. - `language` (string, opcional, `es` | `en`, por defecto `es`) — Idioma de los correos de invitación y recordatorio. Cualquier otro valor es error. - `ordered` (boolean, opcional, por defecto `false`) Si las firmas se solicitan en un orden determinado. `true` necesita `signersConfig` en la misma llamada, y el orden de ese arreglo es el orden de firma. Por sí solo regresa `signersConfig must be an array made by emails or certificates`. `false` cancela el orden: a partir de ese momento cada firmante pendiente puede firmar, en cualquier orden. Si no lo mandas, el flujo conserva la configuración que ya tenía. - `watchers` (array, opcional) Correos que reciben copia sin firmar. También se acepta como `additionalEmails`, una sola cadena separada por comas. - `attachFiles` (boolean, opcional, por defecto `true`) — Adjuntar el documento firmado al correo de conclusión. - `attachTimestamp` (boolean, opcional, por defecto `false`) — Adjuntar el sello de tiempo NOM-151. - `includeHistory` (boolean, opcional, por defecto `false`) — Adjuntar el historial de eventos. - `alertAll` (boolean, opcional, por defecto `true`) — Avisar a todos cuando el documento quede concluido. - `blockExpired` (boolean, opcional, por defecto `false`) — Bloquear la firma una vez que el flujo expiró, en vez de permitirla tarde. - `handler` (string, opcional) — Identificador de un manejador de flujo personalizado configurado en tu cuenta. - `cancel` (boolean, opcional) `true` cancela el flujo. Cuando lo mandas, no se lee ninguna otra propiedad del objeto: manda la cancelación sola, y reconfigura en una llamada aparte. - `signersConfig` (array, opcional) Los firmantes, en orden de firma cuando `ordered` está activo. Cada entrada se empareja con un sticker por su valor `data`, sin distinguir mayúsculas de minúsculas; en los stickers con autoridad por teléfono el número se normaliza antes de comparar. Un arreglo vacío regresa `if provided, signersConfig must not be empty`. **Cada sticker que siga esperando firma necesita su entrada.** Si dejas uno fuera, la llamada regresa `workflow: please verify that the signers and order data is correct`. Las entradas cuyo `data` no empareja con ningún sticker se omiten, así que `watchers` es el campo para quienes solo reciben copia. - `data` (string, requerido) — El `data` del firmante, el mismo valor que pusiste en el sticker. - `notifications` (string, opcional, `ALL` | `ONLY_START` | `ONLY_END` | `START_AND_END` | `START_AND_REMINDERS_NO_END` | `NONE`, por defecto `ALL`) — Qué correos recibe este firmante. No distingue mayúsculas. - `sticker_pages` (array, opcional) Qué páginas se analizan en busca de **recuadros de firma dentro del documento** — los rectángulos `Firmamex …` que puedes dibujar en el PDF mismo en lugar de mandar un arreglo `stickers`. Por defecto Firmame analiza las primeras 3 y las últimas 8 páginas. Usa este parámetro cuando tus recuadros estén fuera de ese rango, o para que el análisis sea más rápido acotándolo. **Empieza en cero**: `[0,2,4]` son las páginas 1, 3 y 5. Hasta 30 entradas. La sintaxis de los recuadros está en la guía «Recuadros de firma en el documento». - `document_set` (string, opcional) Agrega el documento a un expediente abierto en lugar de dejarlo suelto. También se acepta como `documentSet`. **Así es como entran los documentos a un expediente** — no hay un endpoint aparte de «agregar al expediente». Crea el expediente con `POST /api/documentset`, agrega los documentos aquí, y arranca el flujo con `POST /api/documentset/close`. - `fields` (TemplateFieldValues, opcional) Los placeholders de una plantilla. **La forma depende del tipo de plantilla.** Cada tipo lo procesa un motor distinto y Firmame entrega lo que ese motor produjo, sin normalizarlo, así que los `fields` que lees y los `fields` que mandas a `POST /developers/json` siguen la misma forma por tipo: | `template_type` | `fields` | |---|---| | `html` | Arreglo de `{id, value}` | | `pdf` | Arreglo de `{key, value, type}`, más `field_options` en los campos de opción | | `pdfmake` | Objeto indexado por el nombre de la `{{variable}}` | | `docx` | Objeto indexado por el nombre del placeholder | - Uno de: - `id` (string, requerido) El atributo `id` del elemento en tu plantilla. Un `id` que no esté en la plantilla se omite. - `value` (string, opcional) El texto que va ahí. Entra como texto, así que cualquier marcado llega escapado. Por defecto, vacío. - `key` (string, requerido) El nombre completo del campo del formulario, tal como lo entregó el descubrimiento. Un nombre que no esté en el formulario se omite. - `value` (string, opcional) - `type` (string, opcional, `text` | `checkbox` | `radio` | `combobox` | `list` | `signature` | `na`) Qué tipo de campo es, según lo encontró el descubrimiento. `na` es cualquier otro. El llenado solo surte efecto en los campos `text`; un valor mandado para cualquier otro tipo no se escribe en el PDF. - `field_options` (array, opcional) — Los valores que acepta el campo. Aparece en `checkbox`, `radio` y `combobox`. - `tables` (array, opcional) Renglones para agregar a las tablas dinámicas de una plantilla HTML. Solo tiene sentido con `template`. - `id` (string, requerido) El atributo `id` del elemento `signmage-template-table` de tu plantilla. A diferencia de un campo, un `id` que no esté en la plantilla hace fallar la petición, así que tómalo de lo que devolvió el alta de la plantilla. - `rows` (array>, requerido) Una entrada por renglón, cada una un arreglo con los valores de las celdas en orden de columna. Los renglones se agregan a la tabla, no reemplazan lo que ya tiene. Ejemplo — Desde una URL, un firmante verificado por liga al correo: ```json { "url_doc": { "url": "https://ejemplo.com/contrato.pdf", "name": "contrato.pdf" }, "stickers": [ { "authority": "EMAIL_LINK", "stickerType": "rect", "dataType": "email", "imageType": "stroke", "data": "firmante@dominiodelfirmante.com", "page": 0, "rect": { "lx": 330, "ly": 300, "tx": 530, "ty": 400 } } ] } ``` Ejemplo — Desde Base64: ```json { "b64_doc": { "data": "JVBERi0xLjQKJcfsj6IK…", "name": "contrato.pdf" }, "stickers": [ { "authority": "EMAIL_LINK", "data": "firmante@dominiodelfirmante.com", "page": 0, "rect": { "lx": 330, "ly": 300, "tx": 530, "ty": 400 } } ] } ``` Ejemplo — Certificado SAT, línea de firma: ```json { "url_doc": { "url": "https://ejemplo.com/contrato.pdf", "name": "contrato.pdf" }, "stickers": [ { "authority": "SAT", "stickerType": "line", "data": "ARCX9012226P8", "imageType": "hash", "email": "firmante@dominiodelfirmante.com", "page": 0, "rect": { "lx": 388, "ly": 400, "tx": 496, "ty": 480 } } ] } ``` Ejemplo — Desde una plantilla registrada: ```json { "template": { "title": "contrato-arrendamiento", "name": "Contrato — marzo" }, "fields": [ { "id": "full-name", "value": "John Doe" } ], "tables": [ { "id": "directory", "rows": [ [ "John Doe", "202-555-0141", "843 James Lane" ] ] } ] } ``` ## Respuestas ### `200` El documento se guardó y se crearon los recuadros de firma. `application/json`: - `document_ticket` (string, opcional) El id único del documento. El mismo valor aparece como `firmamex_id` en los webhooks y como `ticket` en casi todos los demás endpoints. - `document_flow` (boolean, opcional) — Si Firmame está llevando el flujo de firma de este documento. - `sticker_count` (integer, opcional) - `stickers` (array, opcional) - `sticker_id` (string, opcional) — Id público del firmante, usado por `POST /api/{firmamexId}/signer/{publicSignerId}/update`. - `sticker_index` (integer, opcional) - `sticker_email` (string, opcional) - `sticker_data` (string, opcional) - `sticker_page` (integer, opcional) - `sticker_coordinates` (object, opcional) El rectángulo guardado. La esquina superior vuelve como `ux`/`uy`; la petición la manda como `tx`/`ty`. - `lx` (number, opcional) - `ly` (number, opcional) - `ux` (number, opcional) - `uy` (number, opcional) - `sticker_type` (string, opcional) - `sticker_image_type` (string, opcional) - `sticker_authorities` (array, opcional) - `authority_name` (string, opcional) - `document_url` (string, opcional, format `uri`) — Dónde se puede ver y firmar el documento. Ejemplo: ```json { "document_ticket": "c6279a7c-b3d5-4c71-b417-4687a4f38914", "document_flow": false, "sticker_count": 1, "stickers": [ { "sticker_id": "qv3ZzAL3w", "sticker_index": 0, "sticker_email": "firmante@dominiodelfirmante.com", "sticker_data": "firmante@dominiodelfirmante.com", "sticker_page": 0, "sticker_coordinates": { "lx": 388, "ly": 400, "ux": 496, "uy": 480 }, "sticker_type": "EMAIL", "sticker_image_type": "hash", "sticker_authorities": [ { "authority_name": "Vinculada a Correo Electrónico" } ] } ], "document_url": "https://app.firmame.com/pdf/c6279a7c-b3d5-4c71-b417-4687a4f38914/?child=true&inv=true" } ``` ### `204` La petición no traía cuerpo. ### `400` Se rechazó el payload — falta `authority`, un rectángulo sin `page`, un `email` en una autoridad por SMS, o un documento origen que no se pudo descargar o decodificar. El cuerpo trae el motivo en texto plano. `text/plain` — `string`. Ejemplo: ``` Signer authority is required ``` ### `401` No se logró autenticar. El cuerpo viene vacío; revisa tu web ID, que la llave de API esté activa, y que la cadena firmada siga las reglas del esquema `signmage`. # Eliminar un documento (https://devs.firmame.com/docs/api/documentos/delete-document) `POST https://app.firmame.com/developers/delete/` Requiere el header `Authorization` del esquema `signmage`. Elimina un documento y descarta sus notificaciones pendientes. **La diagonal final es parte de la ruta.** ## Cuerpo de la petición `application/json`: - `ticket` (string, requerido) ## Respuestas ### `200` El resultado de la eliminación. ### `401` No se logró autenticar. El cuerpo viene vacío; revisa tu web ID, que la llave de API esté activa, y que la cadena firmada siga las reglas del esquema `signmage`. # Restaurar un documento (https://devs.firmame.com/docs/api/documentos/restore-document) `POST https://app.firmame.com/developers/restore/` Requiere el header `Authorization` del esquema `signmage`. Reactiva un documento eliminado. Si nunca se había firmado, restaurarlo consume un crédito de documento de tu plan. **La diagonal final es parte de la ruta.** ## Cuerpo de la petición `application/json`: - `ticket` (string, requerido) ## Respuestas ### `200` La confirmación, en texto plano. `text/plain` — `string`. Ejemplo: ``` Ticket: 7481782e-391a-445d-995c-5e759290ad54.pdf restored ``` ### `400` No hay ningún documento inactivo con ese ticket. El cuerpo es la cadena de texto plano `invalid ticket`. `text/plain` — `string`. ### `401` No se logró autenticar, o el documento es de otro web ID u otro usuario. Los dos casos devuelven un `401` vacío. # Agregar un firmante al expediente (https://devs.firmame.com/docs/api/expedientes/add-premade-sticker) `POST https://app.firmame.com/api/documentset/{uuid}/signer` Requiere el header `Authorization` del esquema `signmage`. Precarga un firmante para todo el expediente, sin ligarlo todavía a una página o posición. Desde la interfaz de Firmame arrastras después el recuadro de ese firmante sobre cada documento del expediente; una vez colocado en todos, cerrar el expediente arranca el flujo. Devuelve el `uuid` del nuevo firmante; guárdalo para poder eliminarlo después. ## Parámetros | Nombre | En | Tipo | Requerido | Descripción | |---|---|---|---|---| | `uuid` | path | string | requerido | El id del expediente. | ## Cuerpo de la petición `application/json`: - `authority` (Authority, requerido) Cómo comprueba el firmante quién es. En integraciones nuevas usa el código corto. | Código corto | Qué hace el firmante | |---|---| | `SAT` | Se identifica con sus archivos `.cer` y `.key` del SAT | | `OAUTH_TOKEN` | Entra con Google o Outlook, o se verifica con una liga enviada por correo | | `EMAIL_LINK` | Recibe una liga de firma por correo; tener la liga es la prueba | | `EMAIL` | Responde a un correo para verificarse | | `WHATSAPP_LINK` | Recibe una liga de firma por WhatsApp | | `WHATSAPP_PIN` | Recibe un PIN de un solo uso por WhatsApp al momento de firmar | | `SMS_LINK` | Recibe una liga de firma por SMS | | `SMS` | Recibe la invitación a firmar por SMS | Con las autoridades por liga solo el firmante tiene la URL que autoriza firmar, y Firmame manda la invitación en automático. Con `SAT`, `EMAIL` y `SMS`, si no configuras un flujo, **repartir la URL de firma es responsabilidad tuya**. - `data` (string, requerido) — El valor que identifica al firmante. - `email` (string, requerido, format `email`) - `imageType` (ImageType, requerido, `desc` | `name` | `hash` | `stroke` | `endorsment`, por defecto `desc`) Cómo se ve la firma dibujada en la página. - `desc` — el texto «Firma realizada con certificado emitido por [emisor] para: [nombre]». - `name` — el nombre del firmante según su certificado, con una tipografía manuscrita. - `hash` — el SHA-256 en Base64 del documento antes de firmarse, seguido del nombre del firmante. - `stroke` — la firma dibujada a mano en la app. - `endorsment` — el texto del endoso. Solo tiene sentido en el sticker `endorser` de `POST /api/document/{firmamexId}/endorse`. Se pueden combinar dos con `+`, por ejemplo `stroke+name`. Más de un `+` es un error. - `stickerType` (string, requerido, `rect` | `line`) - `line` (boolean, requerido) — Dibujarlo como línea de firma. - `width` (integer, requerido) — Ancho del recuadro de firma, en unidades de PDF. - `height` (integer, requerido) — Alto del recuadro de firma, en unidades de PDF. Ejemplo: ```json { "authority": "EMAIL_LINK", "data": "firmante@dominio.com", "email": "firmante@dominio.com", "imageType": "stroke", "stickerType": "rect", "line": false, "width": 200, "height": 100 } ``` ## Respuestas ### `200` El éxito es `{"result": "success", "uuid": "…"}`. Si el id del expediente no corresponde a uno tuyo, el cuerpo es `{"error": "documentSet doesn't exist"}`. `application/json`: - `result` (string, opcional) - `uuid` (string, opcional) — Id del firmante creado, necesario para eliminarlo. - `error` (string, opcional) Ejemplo: ```json { "result": "success", "uuid": "3f2b1a90-0c4e-4a11-9a3d-5c7e1b2f8d64" } ``` Ejemplo: ```json { "error": "documentSet doesn't exist" } ``` ### `401` No se logró autenticar. El cuerpo viene vacío; revisa tu web ID, que la llave de API esté activa, y que la cadena firmada siga las reglas del esquema `signmage`. # Cerrar un expediente y arrancar su flujo (https://devs.firmame.com/docs/api/expedientes/close-document-set) `POST https://app.firmame.com/api/documentset/close` Requiere el header `Authorization` del esquema `signmage`. Sella el expediente —ya no se le agregan documentos— y arranca el flujo de firma sobre todos los documentos que contiene. El objeto `workflow` es el mismo que documenta `POST /api/{firmamexId}/workflow`, y **es obligatorio** al cerrar. Como el flujo abarca varios documentos, `signersConfig` lista los firmantes de todo el expediente por su `data`, no una lista por documento. ## Cuerpo de la petición `application/json`: - `documentSet` (string, requerido) — El id del expediente. También se acepta como `document_set`. - `workflow` (DocumentSetWorkflow, requerido) El flujo de un expediente completo: una sola invitación y un solo orden de firma para todos sus documentos. Los mismos campos que el objeto `Workflow`, y su `signersConfig` lleva además el `role` de cada firmante. - `subject` (string, opcional) Asunto del correo de invitación. Mantenlo por debajo de 150 caracteres; si es más largo se usa el asunto por defecto. - `message` (string, opcional) Cuerpo del correo de invitación. También se acepta como `user_message`. Mantenlo por debajo de 3500 caracteres. - `expirationDate` (string, opcional) Cuándo expira el flujo, como `dd/MM/yyyy` o `dd/MM/yyyy HH:mm`. **Primero el día** — `12/11/2021` es 12 de noviembre. Cualquier otra cosa falla con `error parsing workflow expiration date`. Por defecto, 14 días. - `remindEvery` (string, opcional) Cada cuánto recordarle a los firmantes pendientes: `none`, `12h`, o `Nd` con N de 1 a 7. Los valores fuera de ese rango conservan el valor por defecto de 24 h. - `language` (string, opcional, `es` | `en`, por defecto `es`) — Idioma de los correos de invitación y recordatorio. Cualquier otro valor es error. - `ordered` (boolean, opcional, por defecto `false`) Si las firmas se solicitan en un orden determinado. `true` necesita `signersConfig` en la misma llamada, y el orden de ese arreglo es el orden de firma. Por sí solo regresa `signersConfig must be an array made by emails or certificates`. `false` cancela el orden: a partir de ese momento cada firmante pendiente puede firmar, en cualquier orden. Si no lo mandas, el flujo conserva la configuración que ya tenía. - `watchers` (array, opcional) Correos que reciben copia sin firmar. También se acepta como `additionalEmails`, una sola cadena separada por comas. - `attachFiles` (boolean, opcional, por defecto `true`) — Adjuntar el documento firmado al correo de conclusión. - `attachTimestamp` (boolean, opcional, por defecto `false`) — Adjuntar el sello de tiempo NOM-151. - `includeHistory` (boolean, opcional, por defecto `false`) — Adjuntar el historial de eventos. - `alertAll` (boolean, opcional, por defecto `true`) — Avisar a todos cuando el documento quede concluido. - `blockExpired` (boolean, opcional, por defecto `false`) — Bloquear la firma una vez que el flujo expiró, en vez de permitirla tarde. - `handler` (string, opcional) — Identificador de un manejador de flujo personalizado configurado en tu cuenta. - `cancel` (boolean, opcional) `true` cancela el flujo. Cuando lo mandas, no se lee ninguna otra propiedad del objeto: manda la cancelación sola, y reconfigura en una llamada aparte. - `signersConfig` (array, opcional) Los firmantes del expediente, en orden de firma cuando `ordered` está activo. Cada entrada se empareja con un firmante del expediente por su valor `data`; aquí la comparación es exacta, así que respeta las mayúsculas y minúsculas con las que lo registraste. Un arreglo vacío regresa `if provided, signersConfig must not be empty`, y un `data` que no corresponde a ningún firmante del expediente regresa `signer not found`. - `data` (string, requerido) — El `data` del firmante, el mismo valor que pusiste en el sticker. - `notifications` (string, opcional, `ALL` | `ONLY_START` | `ONLY_END` | `START_AND_END` | `START_AND_REMINDERS_NO_END` | `NONE`, por defecto `ALL`) — Qué correos recibe este firmante. No distingue mayúsculas. - `role` (string, opcional, `SIGNER` | `WATCHER` | `ADMIN` | `OWNER`, por defecto `SIGNER`) `SIGNER` firma. `WATCHER` solo recibe copias. `ADMIN` firma y puede ver el expediente completo. `OWNER` es quien creó el expediente. No distingue mayúsculas. Ejemplo: ```json { "documentSet": "7c320b07-5b71-4ef4-8b8e-7db8cb1d8f21", "workflow": { "remind_every": "1d", "language": "es", "ordered": true, "signersConfig": [ { "data": "GOCF9002225A711" }, { "data": "firmante@dominio.com" } ] } } ``` ## Respuestas ### `200` Si sale bien, el cuerpo es `{"document_set": "…"}`. Si algo de la petición no se pudo aplicar —una fecha del `workflow` que no se pudo interpretar, por ejemplo— el cuerpo es `{"error": "…"}`. Revisa cuál de las dos llaves llegó. `application/json`: - `document_set` (string, opcional) - `error` (string, opcional) Ejemplo: ```json { "document_set": "7c320b07-5b71-4ef4-8b8e-7db8cb1d8f21" } ``` Ejemplo: ```json { "error": "error parsing workflow expiration date" } ``` ### `400` No venía ni `documentSet` ni `document_set`. El cuerpo viene vacío. ### `401` No se logró autenticar. El cuerpo viene vacío; revisa tu web ID, que la llave de API esté activa, y que la cadena firmada siga las reglas del esquema `signmage`. # Crear un expediente (https://devs.firmame.com/docs/api/expedientes/create-document-set) `POST https://app.firmame.com/api/documentset` Requiere el header `Authorization` del esquema `signmage`. Crea un expediente vacío y devuelve su id. Un expediente agrupa varios documentos en un solo paquete de firma: una invitación, un orden de firma, una conclusión. El ciclo es: 1. `POST /api/documentset` — crear el expediente. 2. `POST /developers/json` con `document_set` — agregar cada documento. 3. `POST /api/documentset/close` — cerrarlo y arrancar el flujo. ## Cuerpo de la petición `application/json`: - `name` (string, opcional) — Un nombre para el expediente, visible para los firmantes. Ejemplo: ```json { "name": "Expediente de contratación" } ``` ## Respuestas ### `200` El expediente se creó. `application/json`: - `document_set` (string, opcional) Ejemplo: ```json { "document_set": "7c320b07-5b71-4ef4-8b8e-7db8cb1d8f21" } ``` ### `401` No se logró autenticar. El cuerpo viene vacío; revisa tu web ID, que la llave de API esté activa, y que la cadena firmada siga las reglas del esquema `signmage`. # Quitar un firmante del expediente (https://devs.firmame.com/docs/api/expedientes/delete-premade-sticker) `POST https://app.firmame.com/api/documentset/{uuid}/deleteSigner` Requiere el header `Authorization` del esquema `signmage`. Elimina un firmante agregado antes con `POST /api/documentset/{uuid}/signer`. Este endpoint es un `POST`, y el id del firmante va en el cuerpo. ## Parámetros | Nombre | En | Tipo | Requerido | Descripción | |---|---|---|---|---| | `uuid` | path | string | requerido | El id del expediente. | ## Cuerpo de la petición `application/json`: - `uuid` (string, requerido) El id del firmante, tal como lo devolvió `POST /api/documentset/{uuid}/signer` — **no** el id del expediente, que va en la ruta. ## Respuestas ### `200` El éxito es `{"result": "success"}`. Si el id del expediente no corresponde a uno tuyo, el cuerpo es `{"error": "documentSet doesn't exist"}`. Verifica que el id del firmante sea uno que te devolvió `POST /api/documentset/{uuid}/signer`. `application/json`: - `result` (string, opcional) - `error` (string, opcional) Ejemplo: ```json { "result": "success" } ``` Ejemplo: ```json { "error": "documentSet doesn't exist" } ``` ### `401` No se logró autenticar. El cuerpo viene vacío; revisa tu web ID, que la llave de API esté activa, y que la cadena firmada siga las reglas del esquema `signmage`. # Consultar un expediente (https://devs.firmame.com/docs/api/expedientes/get-document-set) `GET https://app.firmame.com/api/documentset/{uuid}` Requiere el header `Authorization` del esquema `signmage`. Devuelve los documentos, los firmantes y el estado del expediente. Sirve para consultar el avance — mientras hay un flujo corriendo, la respuesta trae además un objeto `workflow` con de quién es el turno y cuándo expira. ## Parámetros | Nombre | En | Tipo | Requerido | Descripción | |---|---|---|---|---| | `uuid` | path | string | requerido | El id del expediente. | ## Respuestas ### `200` El expediente. `application/json`: - `uuid` (string, opcional) — El id del expediente. - `status` (string, opcional) `OPEN` mientras todavía se le pueden agregar documentos, `CLOSED` una vez que arrancó el flujo. - `documents` (array, opcional) — Los ids de los documentos del expediente. - `signers` (array, opcional) — El valor `data` de cada firmante del expediente. - `workflow` (object, opcional) **Solo aparece mientras el expediente está en proceso.** No viene si está abierto o ya terminó — pregunta por él en vez de darlo por hecho. - `currentSigner` (string, opcional) — De quién es el turno, cuando el flujo va en orden. - `lastNotification` (integer, opcional) — Cuándo salió la última notificación, en milisegundos desde el epoch de Unix. - `expirationDate` (integer, opcional) — Cuándo expira el flujo, en milisegundos desde el epoch de Unix. Ejemplo — Un expediente con un flujo corriendo: ```json { "uuid": "9v3d6a47-e4e8-48ef-b9b3-7af45c8207dn", "status": "CLOSED", "documents": [ "4a3d6a47-e4e8-48ef-b9b3-7af45c8207de", "5d08eabe-ecd0-46ad-9dbd-5d54098aca70" ], "workflow": { "lastNotification": 1665001495000, "expirationDate": 1666211094000, "currentSigner": "firmante@dominio.com" }, "signers": [ "GOCF9002226A511", "firmante@dominio.com" ] } ``` ### `401` No se logró autenticar. El cuerpo viene vacío; revisa tu web ID, que la llave de API esté activa, y que la cadena firmada siga las reglas del esquema `signmage`. ### `404` No hay ningún expediente con ese id en esta cuenta. El cuerpo viene vacío — un id que existe pero es de alguien más no se distingue de uno que no existe. # Subir un documento a un expediente (https://devs.firmame.com/docs/api/expedientes/subir-documento-a-expediente) > El mismo POST /developers/json de crear un documento, con document_set obligatorio. No hay un endpoint distinto para "agregar" un documento a un expediente: es exactamente `POST /developers/json` — el mismo que crea un documento suelto — con un campo más. Sin `document_set`, el documento se crea suelto, fuera del expediente. El valor es el `uuid` que devolvió [Crear un expediente](/docs/api/expedientes/create-document-set), y el expediente tiene que seguir **abierto** (`status: "OPEN"`) — confirma su estado en [Consultar expediente](/docs/api/expedientes/get-document-set). Todo lo demás —de dónde viene el PDF, dónde va cada `sticker`— es igual que al crear un documento suelto. Ver [Crear y enviar un documento](/docs/guias/crear-documento) para el detalle. **No mandes `workflow` aquí.** El flujo de firma del expediente se define una sola vez, al cerrarlo, no por documento — ver [Expedientes](/docs/guias/expedientes) para entender cómo cerrarlo y arrancar las invitaciones para todo el paquete de una vez. # Endosar un documento (https://devs.firmame.com/docs/api/firmantes-y-flujos/prepare-endorsement) `POST https://app.firmame.com/api/document/{firmamexId}/endorse` Requiere el header `Authorization` del esquema `signmage`. Agrega un endoso a un documento **completamente firmado** — el equivalente electrónico de endosar un pagaré. El texto del endoso se escribe sobre el documento y lo firma el endosante. `endorser` y `endorsee` son stickers normales. Dale al endosante `imageType: "endorsment"` para que el propio texto del endoso se dibuje como imagen de firma. `endorsee` es opcional: un endoso en blanco no tiene destinatario nombrado. ## Parámetros | Nombre | En | Tipo | Requerido | Descripción | |---|---|---|---|---| | `firmamexId` | path | string | requerido | El id del documento que se endosa. Tiene que estar completamente firmado. | ## Cuerpo de la petición `application/json`: - `endorsment` (string, requerido) — El texto del endoso. El campo se escribe `endorsment`. - `endorser` (Sticker, requerido) Un recuadro de firma: un firmante, en un lugar, en una página. Un documento sin stickers se guarda pero no se puede firmar. - `authority` (Authority, requerido) Cómo comprueba el firmante quién es. En integraciones nuevas usa el código corto. | Código corto | Qué hace el firmante | |---|---| | `SAT` | Se identifica con sus archivos `.cer` y `.key` del SAT | | `OAUTH_TOKEN` | Entra con Google o Outlook, o se verifica con una liga enviada por correo | | `EMAIL_LINK` | Recibe una liga de firma por correo; tener la liga es la prueba | | `EMAIL` | Responde a un correo para verificarse | | `WHATSAPP_LINK` | Recibe una liga de firma por WhatsApp | | `WHATSAPP_PIN` | Recibe un PIN de un solo uso por WhatsApp al momento de firmar | | `SMS_LINK` | Recibe una liga de firma por SMS | | `SMS` | Recibe la invitación a firmar por SMS | Con las autoridades por liga solo el firmante tiene la URL que autoriza firmar, y Firmame manda la invitación en automático. Con `SAT`, `EMAIL` y `SMS`, si no configuras un flujo, **repartir la URL de firma es responsabilidad tuya**. - `data` (string, requerido) El valor que identifica al firmante, interpretado según `authority` — un correo, un número de teléfono, o un RFC para certificados del SAT. - `page` (integer, requerido) — Página donde va la firma. **Empieza en cero** — la primera página es `0`. - `rect` (Rect, requerido) Dónde va el sticker dentro de la página, en coordenadas de PDF. **El origen `(0,0)` es la esquina inferior izquierda** de la página, no la superior izquierda — un rectángulo que se ve bien en un editor con coordenadas de pantalla aquí saldrá de cabeza. - `lx` (number, requerido) — X de la esquina inferior izquierda. - `ly` (number, requerido) — Y de la esquina inferior izquierda. - `tx` (number, requerido) — X de la esquina superior derecha. - `ty` (number, requerido) — Y de la esquina superior derecha. - `stickerType` (string, opcional, `rect` | `line`, por defecto `rect`) - `rect` — centra la firma dentro del rectángulo. - `line` — la coloca centrada sobre el borde inferior, como una línea de firma. - `imageType` (ImageType, opcional, `desc` | `name` | `hash` | `stroke` | `endorsment`, por defecto `desc`) Cómo se ve la firma dibujada en la página. - `desc` — el texto «Firma realizada con certificado emitido por [emisor] para: [nombre]». - `name` — el nombre del firmante según su certificado, con una tipografía manuscrita. - `hash` — el SHA-256 en Base64 del documento antes de firmarse, seguido del nombre del firmante. - `stroke` — la firma dibujada a mano en la app. - `endorsment` — el texto del endoso. Solo tiene sentido en el sticker `endorser` de `POST /api/document/{firmamexId}/endorse`. Se pueden combinar dos con `+`, por ejemplo `stroke+name`. Más de un `+` es un error. - `email` (string, opcional, format `email`) El correo del firmante. **Obligatorio si quieres que Firmame lleve el flujo de firma** (mandar la invitación, los recordatorios); opcional si tú repartes la URL de firma. Omítelo en las autoridades por SMS, que identifican al firmante por teléfono; mandar los dos se rechaza. - `dataType` (string, opcional) — Tipo explícito de `data`, cuando no se puede inferir. - `customData` (string, opcional) — Cadena opaca que se te regresa en los webhooks de este firmante. - `sendEmailInvitation` (boolean, opcional, por defecto `false`) — Enviar el correo de invitación para este firmante. - `requireLocation` (boolean, opcional, por defecto `false`) — Exigir que el firmante comparta su ubicación antes de firmar. - `requireIdentityVerification` (boolean, opcional, por defecto `false`) Exigir verificación remota de identidad antes de firmar. Los fallos y los éxitos llegan como los eventos de webhook `identity_verification_failed` e `identity_verification_completed`. - `endorsee` (Sticker, opcional) Un recuadro de firma: un firmante, en un lugar, en una página. Un documento sin stickers se guarda pero no se puede firmar. - `authority` (Authority, requerido) Cómo comprueba el firmante quién es. En integraciones nuevas usa el código corto. | Código corto | Qué hace el firmante | |---|---| | `SAT` | Se identifica con sus archivos `.cer` y `.key` del SAT | | `OAUTH_TOKEN` | Entra con Google o Outlook, o se verifica con una liga enviada por correo | | `EMAIL_LINK` | Recibe una liga de firma por correo; tener la liga es la prueba | | `EMAIL` | Responde a un correo para verificarse | | `WHATSAPP_LINK` | Recibe una liga de firma por WhatsApp | | `WHATSAPP_PIN` | Recibe un PIN de un solo uso por WhatsApp al momento de firmar | | `SMS_LINK` | Recibe una liga de firma por SMS | | `SMS` | Recibe la invitación a firmar por SMS | Con las autoridades por liga solo el firmante tiene la URL que autoriza firmar, y Firmame manda la invitación en automático. Con `SAT`, `EMAIL` y `SMS`, si no configuras un flujo, **repartir la URL de firma es responsabilidad tuya**. - `data` (string, requerido) El valor que identifica al firmante, interpretado según `authority` — un correo, un número de teléfono, o un RFC para certificados del SAT. - `page` (integer, requerido) — Página donde va la firma. **Empieza en cero** — la primera página es `0`. - `rect` (Rect, requerido) Dónde va el sticker dentro de la página, en coordenadas de PDF. **El origen `(0,0)` es la esquina inferior izquierda** de la página, no la superior izquierda — un rectángulo que se ve bien en un editor con coordenadas de pantalla aquí saldrá de cabeza. - `lx` (number, requerido) — X de la esquina inferior izquierda. - `ly` (number, requerido) — Y de la esquina inferior izquierda. - `tx` (number, requerido) — X de la esquina superior derecha. - `ty` (number, requerido) — Y de la esquina superior derecha. - `stickerType` (string, opcional, `rect` | `line`, por defecto `rect`) - `rect` — centra la firma dentro del rectángulo. - `line` — la coloca centrada sobre el borde inferior, como una línea de firma. - `imageType` (ImageType, opcional, `desc` | `name` | `hash` | `stroke` | `endorsment`, por defecto `desc`) Cómo se ve la firma dibujada en la página. - `desc` — el texto «Firma realizada con certificado emitido por [emisor] para: [nombre]». - `name` — el nombre del firmante según su certificado, con una tipografía manuscrita. - `hash` — el SHA-256 en Base64 del documento antes de firmarse, seguido del nombre del firmante. - `stroke` — la firma dibujada a mano en la app. - `endorsment` — el texto del endoso. Solo tiene sentido en el sticker `endorser` de `POST /api/document/{firmamexId}/endorse`. Se pueden combinar dos con `+`, por ejemplo `stroke+name`. Más de un `+` es un error. - `email` (string, opcional, format `email`) El correo del firmante. **Obligatorio si quieres que Firmame lleve el flujo de firma** (mandar la invitación, los recordatorios); opcional si tú repartes la URL de firma. Omítelo en las autoridades por SMS, que identifican al firmante por teléfono; mandar los dos se rechaza. - `dataType` (string, opcional) — Tipo explícito de `data`, cuando no se puede inferir. - `customData` (string, opcional) — Cadena opaca que se te regresa en los webhooks de este firmante. - `sendEmailInvitation` (boolean, opcional, por defecto `false`) — Enviar el correo de invitación para este firmante. - `requireLocation` (boolean, opcional, por defecto `false`) — Exigir que el firmante comparta su ubicación antes de firmar. - `requireIdentityVerification` (boolean, opcional, por defecto `false`) Exigir verificación remota de identidad antes de firmar. Los fallos y los éxitos llegan como los eventos de webhook `identity_verification_failed` e `identity_verification_completed`. Ejemplo: ```json { "endorsment": "Se hace constar que, en esta fecha, XX, S.A.P.I. de C.V., como tenedor, cedió el presente pagaré al que esta anotación va adherida.", "endorser": { "authority": "SAT", "stickerType": "line", "dataType": "rfc", "email": "endosante@ejemplo.com", "data": "ARCX9012226P8", "imageType": "endorsment", "page": 1, "rect": { "lx": 145, "ly": 496, "tx": 443, "ty": 610 } }, "endorsee": { "authority": "SAT", "stickerType": "line", "dataType": "rfc", "email": "endosatario@ejemplo.com", "data": "BACX9012226P8", "imageType": "stroke", "page": 1, "rect": { "lx": 145, "ly": 296, "tx": 443, "ty": 410 } } } ``` ## Respuestas ### `200` El endoso quedó preparado y listo para firmarse. ### `400` Rechazado — el documento no está completamente firmado, o el sticker del endosante es inválido. El cuerpo trae la descripción en texto plano. `text/plain` — `string`. ### `401` No se logró autenticar. El cuerpo viene vacío; revisa tu web ID, que la llave de API esté activa, y que la cadena firmada siga las reglas del esquema `signmage`. # Crear o editar el flujo de un expediente (https://devs.firmame.com/docs/api/firmantes-y-flujos/update-document-set-workflow) `POST https://app.firmame.com/api/{workflowUUID}/workflowSet` Requiere el header `Authorization` del esquema `signmage`. Ajusta el flujo de un expediente que ya cerraste: su expiración, cada cuánto insiste, qué lleva el correo de conclusión, o su cancelación. Ojo: el id de la ruta es el UUID **del expediente**, no el de un documento. Dos cosas distinguen a este endpoint de `POST /api/{firmamexId}/workflow`. Sus claves son `snake_case` únicamente, y acepta menos: los firmantes del expediente quedan fijos cuando lo cierras con `POST /api/documentset/close`, y aquí no se editan. **Cada llamada reinicia el flujo.** Mandes lo que mandes, los recordatorios pendientes se cancelan y la invitación de firma sale de nuevo de inmediato, así que un cuerpo vacío no es inocuo. Usa este endpoint solo cuando quieras volver a llegarle a los firmantes. ## Parámetros | Nombre | En | Tipo | Requerido | Descripción | |---|---|---|---|---| | `workflowUUID` | path | string | requerido | El UUID del expediente, tal como lo devuelve `POST /api/documentset`. | ## Cuerpo de la petición `application/json`: - `cancel` (boolean, opcional) `true` cancela el flujo. Cuando lo mandas, no se lee nada más del objeto: manda la cancelación sola. - `expiration_date` (string, opcional) Cuándo expira el flujo, como `dd/MM/yyyy` o `dd/MM/yyyy HH:mm`. **Primero el día** — `12/11/2021` es 12 de noviembre. Una fecha en el futuro además revive el flujo: vuelve a quedar activo y se desbloquean los documentos bloqueados del expediente. - `remind_every` (string, opcional) Cada cuánto recordarle a los firmantes pendientes: `12h`, o `Nd` con N de 1 a 7. Cualquier otra cosa conserva el intervalo que ya tiene el flujo. - `attach_files` (boolean, opcional) — Adjuntar los documentos firmados al correo de conclusión. - `alert_all` (boolean, opcional) — Avisar a todos cuando el expediente quede concluido. Ejemplo — Extender la fecha de expiración: ```json { "expiration_date": "25/11/2021" } ``` Ejemplo — Cancelar el flujo: ```json { "cancel": true } ``` ## Respuestas ### `200` El resultado se reporta en el cuerpo: `status` es `success` o `error`, y `error` trae el motivo cuando algo no se pudo aplicar. Lee `status` en vez de apoyarte en el código HTTP. `application/json`: - `status` (string, opcional, `success` | `error`) - `error` (string, opcional) — Solo aparece cuando `status` es `error`. Ejemplo: ```json { "status": "success" } ``` Ejemplo: ```json { "status": "error", "error": "descripción del error" } ``` ### `400` El payload no pasó la validación. El cuerpo trae en texto plano la descripción de qué estuvo mal. `text/plain` — `string`. ### `401` No se logró autenticar. El cuerpo viene vacío; revisa tu web ID, que la llave de API esté activa, y que la cadena firmada siga las reglas del esquema `signmage`. # Editar un firmante (https://devs.firmame.com/docs/api/firmantes-y-flujos/update-signer) `POST https://app.firmame.com/api/{firmamexId}/signer/{publicSignerId}/update` Requiere el header `Authorization` del esquema `signmage`. Corrige los datos de un firmante **antes de que haya firmado** — un correo mal escrito, el dato de verificación equivocado, o un cambio de autoridad. Una vez que ese firmante firmó, su recuadro queda fijo. Necesitas dos ids, los dos los devuelve `POST /developers/json`: el `document_ticket` del documento y el `sticker_id` del firmante. Este endpoint es un `POST`. ## Parámetros | Nombre | En | Tipo | Requerido | Descripción | |---|---|---|---|---| | `firmamexId` | path | string | requerido | El id del documento (`document_ticket`). | | `publicSignerId` | path | string | requerido | El `sticker_id` del firmante. | ## Cuerpo de la petición `application/json`: - `authority` (Authority, opcional) Cómo comprueba el firmante quién es. En integraciones nuevas usa el código corto. | Código corto | Qué hace el firmante | |---|---| | `SAT` | Se identifica con sus archivos `.cer` y `.key` del SAT | | `OAUTH_TOKEN` | Entra con Google o Outlook, o se verifica con una liga enviada por correo | | `EMAIL_LINK` | Recibe una liga de firma por correo; tener la liga es la prueba | | `EMAIL` | Responde a un correo para verificarse | | `WHATSAPP_LINK` | Recibe una liga de firma por WhatsApp | | `WHATSAPP_PIN` | Recibe un PIN de un solo uso por WhatsApp al momento de firmar | | `SMS_LINK` | Recibe una liga de firma por SMS | | `SMS` | Recibe la invitación a firmar por SMS | Con las autoridades por liga solo el firmante tiene la URL que autoriza firmar, y Firmame manda la invitación en automático. Con `SAT`, `EMAIL` y `SMS`, si no configuras un flujo, **repartir la URL de firma es responsabilidad tuya**. - `data` (string, opcional) — El nuevo valor de verificación. - `email` (string, opcional, format `email`) - `imageType` (ImageType, opcional, `desc` | `name` | `hash` | `stroke` | `endorsment`, por defecto `desc`) Cómo se ve la firma dibujada en la página. - `desc` — el texto «Firma realizada con certificado emitido por [emisor] para: [nombre]». - `name` — el nombre del firmante según su certificado, con una tipografía manuscrita. - `hash` — el SHA-256 en Base64 del documento antes de firmarse, seguido del nombre del firmante. - `stroke` — la firma dibujada a mano en la app. - `endorsment` — el texto del endoso. Solo tiene sentido en el sticker `endorser` de `POST /api/document/{firmamexId}/endorse`. Se pueden combinar dos con `+`, por ejemplo `stroke+name`. Más de un `+` es un error. Ejemplo: ```json { "authority": "EMAIL_LINK", "email": "email@ejemplo.com", "data": "email@ejemplo.com", "imageType": "desc" } ``` ## Respuestas ### `200` El resultado se reporta en el cuerpo: `status` es `success` o `error`, y `error` trae el motivo cuando algo no se pudo aplicar. Lee `status` en vez de apoyarte en el código HTTP. `application/json`: - `status` (string, opcional, `success` | `error`) - `error` (string, opcional) — Solo aparece cuando `status` es `error`. Ejemplo: ```json { "status": "success" } ``` Ejemplo: ```json { "status": "error", "error": "descripción del error" } ``` ### `400` Se rechazó el cambio — id de firmante desconocido, o un firmante que ya firmó. El cuerpo trae la descripción en texto plano. `text/plain` — `string`. ### `401` No se logró autenticar. El cuerpo viene vacío; revisa tu web ID, que la llave de API esté activa, y que la cadena firmada siga las reglas del esquema `signmage`. # Crear o editar el flujo de un documento (https://devs.firmame.com/docs/api/firmantes-y-flujos/update-workflow) `POST https://app.firmame.com/api/{firmamexId}/workflow` Requiere el header `Authorization` del esquema `signmage`. Le pone un flujo de firma a un documento existente, cambia el que ya tiene, o lo cancela. Manda solo lo que quieras cambiar — el resto se queda como está. Usos típicos: extender un flujo que expiró, quitar el orden de firma, o cancelarlo. Para dejar el flujo armado desde la creación, manda `workflow` en `POST /developers/json`. ## Parámetros | Nombre | En | Tipo | Requerido | Descripción | |---|---|---|---|---| | `firmamexId` | path | string | requerido | El id del documento (`document_ticket`). | ## Cuerpo de la petición `application/json`: - `subject` (string, opcional) Asunto del correo de invitación. Mantenlo por debajo de 150 caracteres; si es más largo se usa el asunto por defecto. - `message` (string, opcional) Cuerpo del correo de invitación. También se acepta como `user_message`. Mantenlo por debajo de 3500 caracteres. - `expirationDate` (string, opcional) Cuándo expira el flujo, como `dd/MM/yyyy` o `dd/MM/yyyy HH:mm`. **Primero el día** — `12/11/2021` es 12 de noviembre. Cualquier otra cosa falla con `error parsing workflow expiration date`. Por defecto, 14 días. - `remindEvery` (string, opcional) Cada cuánto recordarle a los firmantes pendientes: `none`, `12h`, o `Nd` con N de 1 a 7. Los valores fuera de ese rango conservan el valor por defecto de 24 h. - `language` (string, opcional, `es` | `en`, por defecto `es`) — Idioma de los correos de invitación y recordatorio. Cualquier otro valor es error. - `ordered` (boolean, opcional, por defecto `false`) Si las firmas se solicitan en un orden determinado. `true` necesita `signersConfig` en la misma llamada, y el orden de ese arreglo es el orden de firma. Por sí solo regresa `signersConfig must be an array made by emails or certificates`. `false` cancela el orden: a partir de ese momento cada firmante pendiente puede firmar, en cualquier orden. Si no lo mandas, el flujo conserva la configuración que ya tenía. - `watchers` (array, opcional) Correos que reciben copia sin firmar. También se acepta como `additionalEmails`, una sola cadena separada por comas. - `attachFiles` (boolean, opcional, por defecto `true`) — Adjuntar el documento firmado al correo de conclusión. - `attachTimestamp` (boolean, opcional, por defecto `false`) — Adjuntar el sello de tiempo NOM-151. - `includeHistory` (boolean, opcional, por defecto `false`) — Adjuntar el historial de eventos. - `alertAll` (boolean, opcional, por defecto `true`) — Avisar a todos cuando el documento quede concluido. - `blockExpired` (boolean, opcional, por defecto `false`) — Bloquear la firma una vez que el flujo expiró, en vez de permitirla tarde. - `handler` (string, opcional) — Identificador de un manejador de flujo personalizado configurado en tu cuenta. - `cancel` (boolean, opcional) `true` cancela el flujo. Cuando lo mandas, no se lee ninguna otra propiedad del objeto: manda la cancelación sola, y reconfigura en una llamada aparte. - `signersConfig` (array, opcional) Los firmantes, en orden de firma cuando `ordered` está activo. Cada entrada se empareja con un sticker por su valor `data`, sin distinguir mayúsculas de minúsculas; en los stickers con autoridad por teléfono el número se normaliza antes de comparar. Un arreglo vacío regresa `if provided, signersConfig must not be empty`. **Cada sticker que siga esperando firma necesita su entrada.** Si dejas uno fuera, la llamada regresa `workflow: please verify that the signers and order data is correct`. Las entradas cuyo `data` no empareja con ningún sticker se omiten, así que `watchers` es el campo para quienes solo reciben copia. - `data` (string, requerido) — El `data` del firmante, el mismo valor que pusiste en el sticker. - `notifications` (string, opcional, `ALL` | `ONLY_START` | `ONLY_END` | `START_AND_END` | `START_AND_REMINDERS_NO_END` | `NONE`, por defecto `ALL`) — Qué correos recibe este firmante. No distingue mayúsculas. Ejemplo — Extender la fecha de expiración: ```json { "expiration_date": "25/11/2021" } ``` Ejemplo — Dejar que los firmantes firmen en cualquier orden: ```json { "ordered": false } ``` Ejemplo — Cancelar el flujo: ```json { "cancel": true } ``` ## Respuestas ### `200` El resultado se reporta en el cuerpo: `status` es `success` o `error`, y `error` trae el motivo cuando algo no se pudo aplicar. Lee `status` en vez de apoyarte en el código HTTP. `application/json`: - `status` (string, opcional, `success` | `error`) - `error` (string, opcional) — Solo aparece cuando `status` es `error`. Ejemplo: ```json { "status": "success" } ``` Ejemplo: ```json { "status": "error", "error": "descripción del error" } ``` ### `401` No se logró autenticar. El cuerpo viene vacío; revisa tu web ID, que la llave de API esté activa, y que la cadena firmada siga las reglas del esquema `signmage`. ### `500` No se pudo aplicar el flujo. El cuerpo trae el mensaje de la excepción en texto plano. `text/plain` — `string`. # Obtener las dimensiones de una página (https://devs.firmame.com/docs/api/nom-151-y-herramientas-pdf/get-page-data) `POST https://app.firmame.com/api/pdf/pageData` Requiere el header `Authorization` del esquema `signmage`. Devuelve el tamaño y la rotación de una página, para que calcules los rectángulos de los stickers y caigan donde esperas en vez de adivinar entre Carta y A4. Vale la pena llamarlo antes de colocar stickers en un documento que no generaste tú: `rect` va en unidades de PDF medidas desde la esquina inferior izquierda, y una página rotada o de un tamaño inusual deja un rectángulo fijo en el lugar equivocado. `multipart/form-data` con dos campos: `file` y `page`. ## Cuerpo de la petición `multipart/form-data`: - `file` (string, requerido, format `binary`) - `page` (string, requerido) Qué página: `first`, `last`, o un número de página empezando en 1. El `page` de un sticker empieza en 0, así que los dos difieren en uno. No distingue mayúsculas. ## Respuestas ### `200` Las dimensiones de la página. `application/json`: - `width` (number, opcional) — Page width in PDF units (1/72 inch). - `height` (number, opcional) — Page height in PDF units (1/72 inch). - `totalPages` (integer, opcional) — How many pages the document has. Ejemplo: ```json { "width": 612, "height": 792, "totalPages": 3 } ``` ### `400` La página no existe, o `page` no era un número ni `first`/`last`. El cuerpo trae la descripción en texto plano. `text/plain` — `string`. ### `401` No se logró autenticar. El cuerpo viene vacío; revisa tu web ID, que la llave de API esté activa, y que la cadena firmada siga las reglas del esquema `signmage`. ### `500` No se pudo leer el archivo. El cuerpo viene vacío. # Sellar un hash (https://devs.firmame.com/docs/api/nom-151-y-herramientas-pdf/get-timestamp) `POST https://app.firmame.com/api/timestamp` Requiere el header `Authorization` del esquema `signmage`. Emite un sello de tiempo NOM-151 sobre un hash cualquiera — no hace falta un documento. Sirve para certificar que unos datos existían en un momento dado: un registro de base de datos, una bitácora, un artefacto de compilación. Saca tú el SHA-256 de los datos y manda el digest en **hexadecimal**. Firmame nunca ve los datos, solo el digest. ## Cuerpo de la petición `application/json`: - `hash` (string, requerido) — SHA-256 de los datos, en hexadecimal. Ejemplo: ```json { "hash": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08" } ``` ## Respuestas ### `200` El token del sello y sus metadatos. `application/json`: - `status` (string, opcional, `success` | `error`) - `timestamp` (string, opcional, format `byte`) The timestamp token itself: the DER-encoded RFC 3161 response, Base64-encoded. This is the value `POST /api/timestamp/validate` takes. - `timestampData` (TimestampData, opcional) — Un sello NOM-151 junto con el veredicto de la autoridad sobre la solicitud. - `hash` (string, opcional) — El digest que cubre el sello, en hexadecimal. - `tsa` (string, opcional) — Nombre distintivo de la autoridad de sellado de tiempo. - `genTime` (string, opcional, format `date-time`) — Cuándo emitió el sello la autoridad. - `policy` (string, opcional) — OID de la política bajo la que la autoridad lo emitió. - `status` (string, opcional, `granted` | `grantedWithMods` | `rejection` | `waiting` | `revocationWarning` | `revocationNotification`) El veredicto de la autoridad sobre la solicitud, según el RFC 3161. Un sello que puedes usar es `granted`. Está un nivel por debajo del `status` que va hasta arriba de la respuesta, que reporta si la llamada prosperó. Son campos distintos. - `error` (string, opcional) — Present only when `status` is `error`. Ejemplo: ```json { "status": "success", "timestamp": "MIIRWgYJKoZIhvcNAQcCoIIRSzCCEUcCAQMxDzANBglg…", "timestampData": { "hash": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08", "tsa": "L=MIGUEL HIDALGO,ST=CIUDAD DE MEXICO,C=MX,CN=CECOBAN-PSC-NOM151-1-01,O=CECOBAN SA DE CV", "genTime": "2021-03-30T02:34:24.582Z", "status": "granted", "policy": "2.16.484.101.10.316.2.3.1.2.1.1.1" } } ``` Ejemplo: ```json { "status": "error", "error": "no more timestamps allowed" } ``` ### `401` No se logró autenticar. El cuerpo viene vacío; revisa tu web ID, que la llave de API esté activa, y que la cadena firmada siga las reglas del esquema `signmage`. ### `500` No se pudo contactar a la autoridad de sellado, o faltó `hash`. El cuerpo trae el mensaje de la excepción en texto plano. `text/plain` — `string`. # Aplanar un PDF (https://devs.firmame.com/docs/api/nom-151-y-herramientas-pdf/print-pdf) `POST https://app.firmame.com/api/print` Requiere el header `Authorization` del esquema `signmage`. Devuelve una versión aplanada del PDF — los campos de formulario se vuelven contenido estático, las capas se fusionan, y el resultado es un PDF imprimible simple. Útil antes de sellar o firmar un documento que trae un formulario interactivo que no quieres que quede editable. `multipart/form-data`, archivo en `file`, mismas reglas de firma que los endpoints NOM-151. ## Cuerpo de la petición `multipart/form-data`: - `file` (string, requerido, format `binary`) ## Respuestas ### `200` El PDF aplanado. `application/pdf` — `string`. ### `400` No se pudo procesar el archivo. El cuerpo viene vacío. ### `401` No se logró autenticar. El cuerpo viene vacío; revisa tu web ID, que la llave de API esté activa, y que la cadena firmada siga las reglas del esquema `signmage`. # Sellar un PDF con NOM-151 (https://devs.firmame.com/docs/api/nom-151-y-herramientas-pdf/stamp-pdf-with-nom151) `POST https://app.firmame.com/api/nom151/stamp` Requiere el header `Authorization` del esquema `signmage`. Aplica un sello de tiempo NOM-151 a un PDF que ya tienes y devuelve el archivo sellado — para documentos firmados fuera de Firmame, o PDFs ya firmados a los que hay que agregarles el sello. Es una subida `multipart/form-data` con el archivo en el campo `file`. En multipart la cadena firmada es el SHA-256 en Base64 del archivo, que además va en el header `Content-SHA256` — los SDKs se encargan de esto por ti, y también el playground. ## Cuerpo de la petición `multipart/form-data`: - `file` (string, requerido, format `binary`) — El PDF a sellar. ## Respuestas ### `200` El PDF sellado y los datos del sello. `application/json`: - `status` (string, opcional, `success` | `error`) - `document` (string, opcional, format `byte`) — The stamped PDF, Base64-encoded. - `timestampData` (TimestampData, opcional) — Un sello NOM-151 junto con el veredicto de la autoridad sobre la solicitud. - `hash` (string, opcional) — El digest que cubre el sello, en hexadecimal. - `tsa` (string, opcional) — Nombre distintivo de la autoridad de sellado de tiempo. - `genTime` (string, opcional, format `date-time`) — Cuándo emitió el sello la autoridad. - `policy` (string, opcional) — OID de la política bajo la que la autoridad lo emitió. - `status` (string, opcional, `granted` | `grantedWithMods` | `rejection` | `waiting` | `revocationWarning` | `revocationNotification`) El veredicto de la autoridad sobre la solicitud, según el RFC 3161. Un sello que puedes usar es `granted`. Está un nivel por debajo del `status` que va hasta arriba de la respuesta, que reporta si la llamada prosperó. Son campos distintos. - `error` (string, opcional) The reason when `status` is `error`: `The document is not a PDF`, `Error getting the timestamp, please retry later`, `Error signing the PDF document` or `no more timestamps allowed`. Ejemplo: ```json { "status": "success", "document": "JVBERi0xLjcNCiW1tbW1…", "timestampData": { "hash": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08", "tsa": "L=MIGUEL HIDALGO,ST=CIUDAD DE MEXICO,C=MX,CN=CECOBAN-PSC-NOM151-1-01,O=CECOBAN SA DE CV", "genTime": "2021-03-30T02:34:24.582Z", "status": "granted", "policy": "2.16.484.101.10.316.2.3.1.2.1.1.1" } } ``` Ejemplo: ```json { "status": "error", "error": "The document is not a PDF" } ``` ### `401` No se logró autenticar. El cuerpo viene vacío; revisa tu web ID, que la llave de API esté activa, y que la cadena firmada siga las reglas del esquema `signmage`. # Validar el sello NOM-151 de un PDF (https://devs.firmame.com/docs/api/nom-151-y-herramientas-pdf/validate-pdf-nom151) `POST https://app.firmame.com/api/nom151/validate` Requiere el header `Authorization` del esquema `signmage`. Verifica el sello NOM-151 embebido en un PDF: que el token sea genuino, que lo haya emitido una autoridad reconocida, y que el documento no haya cambiado desde entonces. Misma subida `multipart/form-data` y las mismas reglas de firma que `POST /api/nom151/stamp`. ## Cuerpo de la petición `multipart/form-data`: - `file` (string, requerido, format `binary`) — El PDF sellado a verificar. ## Respuestas ### `200` El resultado de la validación. `application/json`: - `status` (string, opcional, `success` | `error`) - `document` (string, opcional, format `byte`) — The PDF with the stamp removed, Base64-encoded. - `timestamp` (string, opcional, format `byte`) — The stamp extracted from the PDF, Base64-encoded. - `timestampData` (TimestampData, opcional) — Un sello NOM-151 junto con el veredicto de la autoridad sobre la solicitud. - `hash` (string, opcional) — El digest que cubre el sello, en hexadecimal. - `tsa` (string, opcional) — Nombre distintivo de la autoridad de sellado de tiempo. - `genTime` (string, opcional, format `date-time`) — Cuándo emitió el sello la autoridad. - `policy` (string, opcional) — OID de la política bajo la que la autoridad lo emitió. - `status` (string, opcional, `granted` | `grantedWithMods` | `rejection` | `waiting` | `revocationWarning` | `revocationNotification`) El veredicto de la autoridad sobre la solicitud, según el RFC 3161. Un sello que puedes usar es `granted`. Está un nivel por debajo del `status` que va hasta arriba de la respuesta, que reporta si la llamada prosperó. Son campos distintos. - `error` (string, opcional) Present only when `status` is `error`: `error parsing the timestamp`, `error validating the timestamp` or `error parsing the document`. Ejemplo: ```json { "status": "success", "document": "JVBERi0xLjcNCiW1tbW1…", "timestamp": "MIISvDADAgEAMIISswYJKoZ…", "timestampData": { "hash": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08", "tsa": "L=MIGUEL HIDALGO,ST=CIUDAD DE MEXICO,C=MX,CN=CECOBAN-PSC-NOM151-1-01,O=CECOBAN SA DE CV", "genTime": "2021-03-30T02:34:24.582Z", "status": "granted", "policy": "2.16.484.101.10.316.2.3.1.2.1.1.1" } } ``` Ejemplo: ```json { "status": "error", "error": "error validating the timestamp" } ``` ### `401` No se logró autenticar. El cuerpo viene vacío; revisa tu web ID, que la llave de API esté activa, y que la cadena firmada siga las reglas del esquema `signmage`. # Validar un sello de tiempo (https://devs.firmame.com/docs/api/nom-151-y-herramientas-pdf/validate-timestamp) `POST https://app.firmame.com/api/timestamp/validate` Requiere el header `Authorization` del esquema `signmage`. Comprueba un sello NOM-151 contra los datos que dice amparar. Hay dos formas de darle los datos: mandar el archivo, o —si prefieres no subirlo— solo su hash. En los dos casos `timestamp` es el token que te dio `POST /api/timestamp`. ## Cuerpo de la petición `application/json`: - `timestamp` (string, requerido) — El token del sello a verificar. - `file` (string, opcional) — Los datos que ampara el token. Mutuamente excluyente con `hash`. - `hash` (string, opcional) SHA-256 en hexadecimal de los datos, cuando prefieres no mandar los datos mismos. Mutuamente excluyente con `file`. Ejemplo — Validar contra el archivo: ```json { "file": "JVBERi0xLjcNCiW1tbW1…", "timestamp": "MIISvDADAgEAMIISswYJKoZ…" } ``` Ejemplo — Validar solo contra el hash: ```json { "hash": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08", "timestamp": "MIISvDADAgEAMIISswYJKoZ…" } ``` ## Respuestas ### `200` El resultado de la validación. `application/json`: - `status` (string, opcional, `success` | `error`) - `timestampData` (TimestampData, opcional) — Un sello NOM-151 junto con el veredicto de la autoridad sobre la solicitud. - `hash` (string, opcional) — El digest que cubre el sello, en hexadecimal. - `tsa` (string, opcional) — Nombre distintivo de la autoridad de sellado de tiempo. - `genTime` (string, opcional, format `date-time`) — Cuándo emitió el sello la autoridad. - `policy` (string, opcional) — OID de la política bajo la que la autoridad lo emitió. - `status` (string, opcional, `granted` | `grantedWithMods` | `rejection` | `waiting` | `revocationWarning` | `revocationNotification`) El veredicto de la autoridad sobre la solicitud, según el RFC 3161. Un sello que puedes usar es `granted`. Está un nivel por debajo del `status` que va hasta arriba de la respuesta, que reporta si la llamada prosperó. Son campos distintos. - `error` (string, opcional) Present only when `status` is `error`: `error parsing the timestamp`, `error validating the timestamp` or `error parsing the files`. Ejemplo: ```json { "status": "success", "timestampData": { "hash": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08", "tsa": "L=MIGUEL HIDALGO,ST=CIUDAD DE MEXICO,C=MX,CN=CECOBAN-PSC-NOM151-1-01,O=CECOBAN SA DE CV", "genTime": "2021-03-30T02:34:24.582Z", "status": "granted", "policy": "2.16.484.101.10.316.2.3.1.2.1.1.1" } } ``` Ejemplo: ```json { "status": "error", "error": "error validating the timestamp" } ``` ### `401` No se logró autenticar. El cuerpo viene vacío; revisa tu web ID, que la llave de API esté activa, y que la cadena firmada siga las reglas del esquema `signmage`. ### `500` No se pudo correr la validación. El cuerpo trae el mensaje de la excepción en texto plano. `text/plain` — `string`. # Obtener los campos de un template (https://devs.firmame.com/docs/api/templates/get-template-fields) `GET https://app.firmame.com/api/v1/templates/{templateName}` Requiere el header `Authorization` del esquema `signmage`. Devuelve los campos que espera una plantilla, para que armes el payload de `POST /developers/json` sin adivinar los nombres de los placeholders. ## Parámetros | Nombre | En | Tipo | Requerido | Descripción | |---|---|---|---|---| | `templateName` | path | string | requerido | El `title` de la plantilla, tal como lo devuelve `GET /api/v1/templates`. | ## Respuestas ### `200` El tipo y los campos de la plantilla. `application/json`: - `type` (TemplateType, requerido, `html` | `pdf` | `pdfmake` | `docx`) Cómo se convierte la plantilla en PDF. - `html` — un documento HTML cuyos placeholders se sustituyen antes de convertir a PDF. Marca cada placeholder con `class="signmage-template-field"` y un `id` único; las tablas usan `class="signmage-template-table"`. - `pdf` — un PDF con **formulario AcroForm**. Sus campos se vuelven los campos de la plantilla, con su tipo de PDF (`text`, `checkbox`, `radio`, `combobox`, `list`, `signature`) y, en los de opción, sus valores aceptados. No hay que marcar nada: lo que ya declara el formulario es lo que obtienes. Un PDF sin AcroForm se da de alta con la lista de campos vacía. - `pdfmake` — una definición de documento de [pdfmake](https://pdfmake.github.io), con placeholders `{{variable}}`. - `docx` — un documento de Word. Firmame extrae sus placeholders y, una vez lleno, entrega el resultado como PDF. - `fields` (DiscoveredFields, requerido) Los placeholders que Firmame encontró en la plantilla, entregados con el valor vacío para que veas qué hay que llenar. - `template_title` (string, opcional) — El título de la plantilla. - `fields` (TemplateFieldValues, opcional) Los placeholders de una plantilla. **La forma depende del tipo de plantilla.** Cada tipo lo procesa un motor distinto y Firmame entrega lo que ese motor produjo, sin normalizarlo, así que los `fields` que lees y los `fields` que mandas a `POST /developers/json` siguen la misma forma por tipo: | `template_type` | `fields` | |---|---| | `html` | Arreglo de `{id, value}` | | `pdf` | Arreglo de `{key, value, type}`, más `field_options` en los campos de opción | | `pdfmake` | Objeto indexado por el nombre de la `{{variable}}` | | `docx` | Objeto indexado por el nombre del placeholder | - Uno de: - `id` (string, requerido) El atributo `id` del elemento en tu plantilla. Un `id` que no esté en la plantilla se omite. - `value` (string, opcional) El texto que va ahí. Entra como texto, así que cualquier marcado llega escapado. Por defecto, vacío. - `key` (string, requerido) El nombre completo del campo del formulario, tal como lo entregó el descubrimiento. Un nombre que no esté en el formulario se omite. - `value` (string, opcional) - `type` (string, opcional, `text` | `checkbox` | `radio` | `combobox` | `list` | `signature` | `na`) Qué tipo de campo es, según lo encontró el descubrimiento. `na` es cualquier otro. El llenado solo surte efecto en los campos `text`; un valor mandado para cualquier otro tipo no se escribe en el PDF. - `field_options` (array, opcional) — Los valores que acepta el campo. Aparece en `checkbox`, `radio` y `combobox`. - `tables` (array, opcional) Las tablas dinámicas de la plantilla. Solo en plantillas HTML — cada elemento que lleva la clase `signmage-template-table`. - `id` (string, opcional) - `rows` (array>, opcional) Ejemplo — Plantilla HTML: ```json { "type": "html", "fields": { "template_title": "contrato-arrendamiento", "fields": [ { "id": "arrendador", "value": "" }, { "id": "arrendatario", "value": "" }, { "id": "monto", "value": "" } ], "tables": [ { "id": "mobiliario", "rows": [] } ] } } ``` Ejemplo — pdfmake template: ```json { "type": "pdfmake", "fields": { "template_title": "pagare", "fields": { "nombre": "", "fecha": "" } } } ``` ### `401` No se logró autenticar. El cuerpo viene vacío; revisa tu web ID, que la llave de API esté activa, y que la cadena firmada siga las reglas del esquema `signmage`. ### `404` No existe una plantilla con ese nombre en la cuenta. El cuerpo es la cadena de texto plano `Template not found`. `text/plain` — `string`. Ejemplo: ``` Template not found ``` # Listar templates (https://devs.firmame.com/docs/api/templates/list-templates) `GET https://app.firmame.com/api/v1/templates` Requiere el header `Authorization` del esquema `signmage`. Devuelve todas las plantillas guardadas en la cuenta dueña del web ID que firmó la petición. Usa el `title` de un resultado como `templateName` de `GET /api/v1/templates/{templateName}`. ## Respuestas ### `200` Las plantillas de la cuenta. Una cuenta sin plantillas devuelve un arreglo vacío. `application/json`: - `title` (string, requerido) — Nombre de la plantilla; se usa como parámetro de ruta `templateName`. - `type` (TemplateType, requerido, `html` | `pdf` | `pdfmake` | `docx`) Cómo se convierte la plantilla en PDF. - `html` — un documento HTML cuyos placeholders se sustituyen antes de convertir a PDF. Marca cada placeholder con `class="signmage-template-field"` y un `id` único; las tablas usan `class="signmage-template-table"`. - `pdf` — un PDF con **formulario AcroForm**. Sus campos se vuelven los campos de la plantilla, con su tipo de PDF (`text`, `checkbox`, `radio`, `combobox`, `list`, `signature`) y, en los de opción, sus valores aceptados. No hay que marcar nada: lo que ya declara el formulario es lo que obtienes. Un PDF sin AcroForm se da de alta con la lista de campos vacía. - `pdfmake` — una definición de documento de [pdfmake](https://pdfmake.github.io), con placeholders `{{variable}}`. - `docx` — un documento de Word. Firmame extrae sus placeholders y, una vez lleno, entrega el resultado como PDF. Ejemplo: ```json [ { "title": "contrato-arrendamiento", "type": "html" }, { "title": "pagare", "type": "pdfmake" } ] ``` ### `401` No se logró autenticar. El cuerpo viene vacío; revisa tu web ID, que la llave de API esté activa, y que la cadena firmada siga las reglas del esquema `signmage`. # Dar de alta un template PDF desde bytes (https://devs.firmame.com/docs/api/templates/save-template-bytes) `POST https://app.firmame.com/developers/template/save/{title}` Requiere el header `Authorization` del esquema `signmage`. Igual que `POST /developers/template/save`, pero el cuerpo es el PDF crudo en vez de JSON — útil cuando el archivo es lo bastante grande como para que pese el sobrecosto de Base64. Esta ruta maneja PDFs con formulario AcroForm, y los campos del formulario se vuelven los campos de la plantilla. Siempre crea una plantilla nueva; para reemplazar una, usa `POST /developers/template/save`. ## Parámetros | Nombre | En | Tipo | Requerido | Descripción | |---|---|---|---|---| | `title` | path | string | requerido | Nombre con el que se dará de alta la plantilla. | ## Cuerpo de la petición `application/pdf` — `string`. ## Respuestas ### `200` Los campos de formulario encontrados en el PDF. Una cadena vacía significa que el archivo no se pudo procesar; verifica que traiga un AcroForm. `application/json`: - `template_title` (string, opcional) — El título de la plantilla. - `fields` (TemplateFieldValues, opcional) Los placeholders de una plantilla. **La forma depende del tipo de plantilla.** Cada tipo lo procesa un motor distinto y Firmame entrega lo que ese motor produjo, sin normalizarlo, así que los `fields` que lees y los `fields` que mandas a `POST /developers/json` siguen la misma forma por tipo: | `template_type` | `fields` | |---|---| | `html` | Arreglo de `{id, value}` | | `pdf` | Arreglo de `{key, value, type}`, más `field_options` en los campos de opción | | `pdfmake` | Objeto indexado por el nombre de la `{{variable}}` | | `docx` | Objeto indexado por el nombre del placeholder | - Uno de: - `id` (string, requerido) El atributo `id` del elemento en tu plantilla. Un `id` que no esté en la plantilla se omite. - `value` (string, opcional) El texto que va ahí. Entra como texto, así que cualquier marcado llega escapado. Por defecto, vacío. - `key` (string, requerido) El nombre completo del campo del formulario, tal como lo entregó el descubrimiento. Un nombre que no esté en el formulario se omite. - `value` (string, opcional) - `type` (string, opcional, `text` | `checkbox` | `radio` | `combobox` | `list` | `signature` | `na`) Qué tipo de campo es, según lo encontró el descubrimiento. `na` es cualquier otro. El llenado solo surte efecto en los campos `text`; un valor mandado para cualquier otro tipo no se escribe en el PDF. - `field_options` (array, opcional) — Los valores que acepta el campo. Aparece en `checkbox`, `radio` y `combobox`. - `tables` (array, opcional) Las tablas dinámicas de la plantilla. Solo en plantillas HTML — cada elemento que lleva la clase `signmage-template-table`. - `id` (string, opcional) - `rows` (array>, opcional) ### `401` No se logró autenticar. El cuerpo viene vacío; revisa tu web ID, que la llave de API esté activa, y que la cadena firmada siga las reglas del esquema `signmage`. ### `500` No se pudo leer el archivo. `text/plain` — `string`. # Dar de alta un template (https://devs.firmame.com/docs/api/templates/save-template) `POST https://app.firmame.com/developers/template/save` Requiere el header `Authorization` del esquema `signmage`. Registra una plantilla en tu cuenta, o la reemplaza si ya existe una con el mismo nombre. **Un solo endpoint atiende los cuatro tipos de plantilla** — HTML, PDF con AcroForm, pdfmake y DOCX. Lo que cambia es `template_type` y cómo se codifica `template_data`; el flujo siempre es el mismo: 1. Da de alta la plantilla aquí. 2. Firmame responde con los placeholders que encontró, así que te enteras de los nombres de los campos en vez de adivinarlos. 3. De ahí en adelante genera documentos con `POST /developers/json`, mandando solo `template` y `fields`. ## Cuerpo de la petición `application/json`: - `template_name` (string, opcional) Nombre de la plantilla. También se acepta como `template_title`; se requiere uno de los dos, y se convierte en el parámetro de ruta `templateName` de `GET /api/v1/templates/{templateName}`. - `template_title` (string, opcional) — Alias de `template_name`, se usa cuando `template_name` no viene. - `template_type` (TemplateType, requerido, `html` | `pdf` | `pdfmake` | `docx`) Cómo se convierte la plantilla en PDF. - `html` — un documento HTML cuyos placeholders se sustituyen antes de convertir a PDF. Marca cada placeholder con `class="signmage-template-field"` y un `id` único; las tablas usan `class="signmage-template-table"`. - `pdf` — un PDF con **formulario AcroForm**. Sus campos se vuelven los campos de la plantilla, con su tipo de PDF (`text`, `checkbox`, `radio`, `combobox`, `list`, `signature`) y, en los de opción, sus valores aceptados. No hay que marcar nada: lo que ya declara el formulario es lo que obtienes. Un PDF sin AcroForm se da de alta con la lista de campos vacía. - `pdfmake` — una definición de documento de [pdfmake](https://pdfmake.github.io), con placeholders `{{variable}}`. - `docx` — un documento de Word. Firmame extrae sus placeholders y, una vez lleno, entrega el resultado como PDF. - `template_data` (string | object, opcional) La plantilla misma, en línea. Su codificación depende de `template_type`: - `html`, `pdf`, `docx` — el archivo **codificado en Base64**. - `pdfmake` — la definición del documento como objeto JSON crudo, **no** en Base64. Manda este campo o `url_doc`. En `html`, `pdf` y `docx`, si llegan los dos el que cuenta es `url_doc`; en `pdfmake` solo se lee `template_data`. - `url_doc` (string, opcional, format `uri`) URL desde la que Firmame descarga la plantilla, en lugar de mandarla en línea. Útil para `pdf` y `docx`, donde Base64 duplica el tamaño del payload. No aplica para `pdfmake`. Ejemplo — Plantilla HTML (Base64): ```json { "template_type": "html", "template_name": "contrato-arrendamiento", "template_data": "PHA+SG9sYSB7e25vbWJyZX19PC9wPg==" } ``` Ejemplo — PDF con AcroForm, descargado de una URL: ```json { "template_type": "pdf", "template_name": "solicitud", "url_doc": "https://ejemplo.com/solicitud-con-formulario.pdf" } ``` Ejemplo — Plantilla pdfmake (JSON crudo, no Base64): ```json { "template_type": "pdfmake", "template_name": "pagare", "template_data": { "content": [ "Este es un ejemplo de un template con dos campos dinámicos: nombre y fecha", "En este renglón se inyectará el nombre: {{nombre}}", "Y la fecha aquí: {{fecha}}" ] } } ``` Ejemplo — Documento Word (Base64): ```json { "template_type": "docx", "template_name": "carta-oferta", "template_data": "UEsDBBQABgAIAAAAIQ…" } ``` ## Respuestas ### `200` Los placeholders que Firmame encontró en la plantilla, devueltos como cadena JSON. `application/json`: - `template_title` (string, opcional) — El título de la plantilla. - `fields` (TemplateFieldValues, opcional) Los placeholders de una plantilla. **La forma depende del tipo de plantilla.** Cada tipo lo procesa un motor distinto y Firmame entrega lo que ese motor produjo, sin normalizarlo, así que los `fields` que lees y los `fields` que mandas a `POST /developers/json` siguen la misma forma por tipo: | `template_type` | `fields` | |---|---| | `html` | Arreglo de `{id, value}` | | `pdf` | Arreglo de `{key, value, type}`, más `field_options` en los campos de opción | | `pdfmake` | Objeto indexado por el nombre de la `{{variable}}` | | `docx` | Objeto indexado por el nombre del placeholder | - Uno de: - `id` (string, requerido) El atributo `id` del elemento en tu plantilla. Un `id` que no esté en la plantilla se omite. - `value` (string, opcional) El texto que va ahí. Entra como texto, así que cualquier marcado llega escapado. Por defecto, vacío. - `key` (string, requerido) El nombre completo del campo del formulario, tal como lo entregó el descubrimiento. Un nombre que no esté en el formulario se omite. - `value` (string, opcional) - `type` (string, opcional, `text` | `checkbox` | `radio` | `combobox` | `list` | `signature` | `na`) Qué tipo de campo es, según lo encontró el descubrimiento. `na` es cualquier otro. El llenado solo surte efecto en los campos `text`; un valor mandado para cualquier otro tipo no se escribe en el PDF. - `field_options` (array, opcional) — Los valores que acepta el campo. Aparece en `checkbox`, `radio` y `combobox`. - `tables` (array, opcional) Las tablas dinámicas de la plantilla. Solo en plantillas HTML — cada elemento que lleva la clase `signmage-template-table`. - `id` (string, opcional) - `rows` (array>, opcional) Ejemplo — Respuesta para una plantilla HTML: ```json { "tables": [ { "id": "directory", "rows": [] } ], "template_title": "contrato-arrendamiento", "fields": [ { "id": "full-name", "value": "" } ] } ``` Ejemplo — Respuesta para una plantilla pdfmake: ```json { "template_title": "pagare", "fields": { "nombre": "", "fecha": "" } } ``` ### `400` Al payload le falta un campo requerido, o la plantilla no se pudo procesar. El cuerpo es el mensaje de error en texto plano, no JSON. `text/plain` — `string`. ### `401` No se logró autenticar. El cuerpo viene vacío; revisa tu web ID, que la llave de API esté activa, y que la cadena firmada siga las reglas del esquema `signmage`. # Suscribirse a un evento (https://devs.firmame.com/docs/api/webhooks-y-cuenta/add-webhook) `POST https://app.firmame.com/api/v1/webhooks` Requiere el header `Authorization` del esquema `signmage`. Registra una URL para recibir aviso cuando ocurra un evento. Una suscripción es un tipo de evento — para recibir varios, registra varios. La URL tiene que aceptar `POST` con `application/json`. Firmame espera un `200`; cualquier otra cosa se considera un fallo y se reintenta con backoff exponencial. Hasta 30 intentos o 10 minutos entre uno y otro. Una URL inválida no se reintenta nunca. El payload de cada evento está en la sección de Webhooks. ## Cuerpo de la petición `application/json`: - `url` (string, requerido, format `uri`) — A dónde mandar la notificación por POST. - `type` (WebhookType, requerido, `original_signed` | `universal_signed` | `document_completed` | `document_rejected` | `workflow_expired` | `whatsapp_error` | `sms_error` | `email_error` | `original_nom151_stamped` | `universal_nom151_stamped` | `identity_verification_failed` | `identity_verification_completed`) — El evento al que se suscribe. Ejemplo: ```json { "url": "https://tu-servidor.com/hooks/firmame", "type": "document_completed" } ``` ## Respuestas ### `200` Se creó la suscripción. El cuerpo trae su id, que necesitas para eliminarla. `application/json`: - `id` (string, requerido, format `uuid`) — The subscription's id. Ejemplo: ```json { "id": "3f9a1c62-5d4e-4a1b-9c77-2e0b8f6d4a13" } ``` ### `400` No se pudo interpretar el cuerpo de la petición. `application/json`: - `error` (string, opcional) Ejemplo: ```json { "error": "could not parse request body" } ``` ### `401` No se logró autenticar. El cuerpo viene vacío; revisa tu web ID, que la llave de API esté activa, y que la cadena firmada siga las reglas del esquema `signmage`. # Cancelar la suscripción a un evento (https://devs.firmame.com/docs/api/webhooks-y-cuenta/delete-webhook) `DELETE https://app.firmame.com/api/v1/webhooks` Requiere el header `Authorization` del esquema `signmage`. Elimina una suscripción por su id. El id viaja en el cuerpo JSON, no en la ruta ni en el query string. Algunos clientes HTTP omiten el cuerpo en `DELETE`, así que verifica que el tuyo sí lo mande. ## Cuerpo de la petición `application/json`: - `id` (string, requerido) — El id de la suscripción. ## Respuestas ### `200` Se eliminó la suscripción. El cuerpo viene vacío. Eliminar un id que ya no existe también responde `200`. ### `400` No se pudo interpretar el cuerpo de la petición. `application/json`: - `error` (string, opcional) ### `401` No se logró autenticar. El cuerpo viene vacío; revisa tu web ID, que la llave de API esté activa, y que la cadena firmada siga las reglas del esquema `signmage`. # Ya se firmaron todos los recuadros (https://devs.firmame.com/docs/api/webhooks-y-cuenta/document-completed) `POST` — Firmame envía esta petición a la URL que registres. El evento sobre el que conviene construir tu lógica de negocio: el documento ya está completamente firmado y listo para descargar con `GET /api/document/{type}/{ticket}`. ## Cuerpo que recibes `application/json`: - `notification_type` (WebhookType, requerido, `original_signed` | `universal_signed` | `document_completed` | `document_rejected` | `workflow_expired` | `whatsapp_error` | `sms_error` | `email_error` | `original_nom151_stamped` | `universal_nom151_stamped` | `identity_verification_failed` | `identity_verification_completed`) — El evento al que se suscribe. - `notification_id` (string, requerido) Id de esta notificación. Mándalo a `POST /developers/webhook` para obtener el documento al que se refiere. - `firmamex_id` (string, requerido) Id del documento. El mismo valor es `document_ticket` cuando creas el documento y `ticket` en la mayoría de los demás endpoints. - `document_title` (string, opcional) - `text` (string, opcional) — Una descripción del evento en lenguaje natural, en inglés. - `signer_email` (string, opcional) — Presente en los eventos de firma. - `signer_fingerprint` (string, opcional) — Huella del certificado del firmante. Presente en los eventos de firma. - `signer_data` (object, opcional) — Los datos del firmante. Presente en los eventos de firma. - `id` (integer, opcional) - `email` (string, opcional) - `name` (string, opcional) - `fingerprint` (string, opcional) - `authority` (string, opcional) - `user` (integer, opcional) - `signer` (integer, opcional) Ejemplo: ```json { "notification_type": "document_completed", "notification_id": "2698", "document_title": "contrato.pdf", "firmamex_id": "04337396-e929-43ca-9b8a-ec958a44079d", "text": "Document 04337396-e929-43ca-9b8a-ec958a44079d.pdf has been completed" } ``` ## Lo que debes responder ### `200` Responde `200` para confirmarlo. # Un firmante rechazó el documento (https://devs.firmame.com/docs/api/webhooks-y-cuenta/document-rejected) `POST` — Firmame envía esta petición a la URL que registres. `meta` trae quién lo rechazó y el motivo que dio, hasta 300 caracteres. ## Cuerpo que recibes `application/json`: - `notification_type` (WebhookType, requerido, `original_signed` | `universal_signed` | `document_completed` | `document_rejected` | `workflow_expired` | `whatsapp_error` | `sms_error` | `email_error` | `original_nom151_stamped` | `universal_nom151_stamped` | `identity_verification_failed` | `identity_verification_completed`) — El evento al que se suscribe. - `notification_id` (string, requerido) Id de esta notificación. Mándalo a `POST /developers/webhook` para obtener el documento al que se refiere. - `firmamex_id` (string, requerido) Id del documento. El mismo valor es `document_ticket` cuando creas el documento y `ticket` en la mayoría de los demás endpoints. - `document_title` (string, opcional) - `text` (string, opcional) — Una descripción del evento en lenguaje natural, en inglés. - `signer_email` (string, opcional) — Presente en los eventos de firma. - `signer_fingerprint` (string, opcional) — Huella del certificado del firmante. Presente en los eventos de firma. - `signer_data` (object, opcional) — Los datos del firmante. Presente en los eventos de firma. - `id` (integer, opcional) - `email` (string, opcional) - `name` (string, opcional) - `fingerprint` (string, opcional) - `authority` (string, opcional) - `user` (integer, opcional) - `signer` (integer, opcional) - `meta` (object, opcional) - `signer` (string, opcional) — Correo, RFC o teléfono del firmante que rechazó. - `reason` (string, opcional) — El motivo que dio, hasta 300 caracteres. Ejemplo: ```json { "meta": { "signer": "GOCF9002226A7", "reason": "solicito cambio en la cláusula tercera" }, "notification_type": "document_rejected", "notification_id": "2693", "document_title": "contrato.pdf", "firmamex_id": "38a81807-be84-4ec2-8d39-33a1b5c1d78a" } ``` ## Lo que debes responder ### `200` Responde `200` para confirmarlo. # No se pudo entregar un correo (https://devs.firmame.com/docs/api/webhooks-y-cuenta/email-error) `POST` — Firmame envía esta petición a la URL que registres. `meta` transmite lo que respondió el servidor de correo receptor, para que distingas un correo mal escrito de un buzón lleno. Siempre trae al menos `message`, `recipient` y `subject`. ## Cuerpo que recibes `application/json`: - `notification_type` (WebhookType, requerido, `original_signed` | `universal_signed` | `document_completed` | `document_rejected` | `workflow_expired` | `whatsapp_error` | `sms_error` | `email_error` | `original_nom151_stamped` | `universal_nom151_stamped` | `identity_verification_failed` | `identity_verification_completed`) — El evento al que se suscribe. - `notification_id` (string, requerido) Id de esta notificación. Mándalo a `POST /developers/webhook` para obtener el documento al que se refiere. - `firmamex_id` (string, requerido) Id del documento. El mismo valor es `document_ticket` cuando creas el documento y `ticket` en la mayoría de los demás endpoints. - `document_title` (string, opcional) - `text` (string, opcional) — Una descripción del evento en lenguaje natural, en inglés. - `signer_email` (string, opcional) — Presente en los eventos de firma. - `signer_fingerprint` (string, opcional) — Huella del certificado del firmante. Presente en los eventos de firma. - `signer_data` (object, opcional) — Los datos del firmante. Presente en los eventos de firma. - `id` (integer, opcional) - `email` (string, opcional) - `name` (string, opcional) - `fingerprint` (string, opcional) - `authority` (string, opcional) - `user` (integer, opcional) - `signer` (integer, opcional) - `meta` (object, opcional) - `message` (string, opcional) — Mensaje de error del servidor receptor. - `description` (string, opcional) — Descripción más larga del servidor receptor, cuando la manda. - `code` (string, opcional) — Código de estado SMTP. - `mx-host` (string, opcional) — Host del servidor receptor. - `recipient` (string, opcional) — La dirección a la que se envió el correo. - `subject` (string, opcional) Ejemplo: ```json { "meta": { "description": "", "message": "5.1.1 The email account that you tried to reach does not exist.", "mx-host": "aspmx.l.google.com", "code": "550", "recipient": "firmante@dominio.com", "subject": "Ha sido invitado a firmar un documento Firmame:kYb" }, "notification_type": "email_error", "notification_id": "2693", "document_title": "contrato.pdf", "firmamex_id": "38a81807-be84-4ec2-8d39-33a1b5c1d78a" } ``` ## Lo que debes responder ### `200` Responde `200` para confirmarlo. # Consultar el detalle de paquetes (https://devs.firmame.com/docs/api/webhooks-y-cuenta/get-account-info-detail) `GET https://app.firmame.com/api/account/detail` Requiere el header `Authorization` del esquema `signmage`. Devuelve los paquetes de la cuenta uno por uno — cuántas piezas trae cada uno, cuántas ya se usaron y la ventana en la que es válido. De cada tipo obtienes el paquete vigente más el último que venció, así que el mismo `type` aparece más de una vez. Distínguelos con `active`, no supongas una fila por tipo. ## Respuestas ### `200` Los paquetes de la cuenta. Si no se pudieron leer, el resultado viene en el cuerpo como `{"status": "error", "message": "service not available"}` en vez de un código de estado, así que confirma que recibiste un arreglo antes de recorrerlo. `application/json`: - `type` (AccountPackageType, requerido, `DOCUMENTS` | `TIMESTAMPS` | `WHATSAPP` | `IDENTITY_VERIFICATIONS`) En qué se gasta el paquete. - `DOCUMENTS` — documentos creados. - `TIMESTAMPS` — sellos de tiempo NOM-151. - `WHATSAPP` — mensajes de WhatsApp enviados a los firmantes. - `IDENTITY_VERIFICATIONS` — verificaciones remotas de identidad. - `assignedAt` (string, requerido, format `date-time`) — Cuándo se asignó el paquete. - `expiresAt` (string, requerido, format `date-time`) — Cuándo deja de poder usarse. - `assignedItems` (integer, requerido) — Cuántas piezas trae el paquete. - `consumedItems` (integer, requerido) — Cuántas de esas piezas ya se usaron. - `active` (boolean, requerido) — Si este es el paquete vigente para su tipo. Ejemplo: ```json [ { "type": "DOCUMENTS", "assignedAt": "2023-05-30T00:00:00Z", "expiresAt": "2025-05-30T00:00:00Z", "assignedItems": 1000, "consumedItems": 705, "active": false }, { "type": "TIMESTAMPS", "assignedAt": "2023-05-30T00:00:00Z", "expiresAt": "2025-05-30T00:00:00Z", "assignedItems": 1000, "consumedItems": 133, "active": false }, { "type": "WHATSAPP", "assignedAt": "2023-05-30T00:00:00Z", "expiresAt": "2025-05-30T00:00:00Z", "assignedItems": 100, "consumedItems": 6, "active": false }, { "type": "WHATSAPP", "assignedAt": "2023-05-30T00:00:00Z", "expiresAt": "2027-05-30T00:00:00Z", "assignedItems": 100, "consumedItems": 52, "active": true }, { "type": "IDENTITY_VERIFICATIONS", "assignedAt": "2024-09-25T00:55:47Z", "expiresAt": "2025-12-25T00:55:47Z", "assignedItems": 10, "consumedItems": 5, "active": false }, { "type": "DOCUMENTS", "assignedAt": "2025-05-30T12:30:16Z", "expiresAt": "2030-05-30T00:00:00Z", "assignedItems": 1000, "consumedItems": 792, "active": true }, { "type": "TIMESTAMPS", "assignedAt": "2025-05-30T12:30:16Z", "expiresAt": "2030-05-30T00:00:00Z", "assignedItems": 1000, "consumedItems": 79, "active": true }, { "type": "IDENTITY_VERIFICATIONS", "assignedAt": "2026-03-17T13:06:13Z", "expiresAt": "2027-03-17T13:06:13Z", "assignedItems": 500, "consumedItems": 129, "active": true } ] ``` ### `400` No se pudieron leer los paquetes. `application/json`: - `error` (string, opcional) ### `401` No se logró autenticar. El cuerpo viene vacío; revisa tu web ID, que la llave de API esté activa, y que la cadena firmada siga las reglas del esquema `signmage`. # Consultar los paquetes restantes (https://devs.firmame.com/docs/api/webhooks-y-cuenta/get-account-info) `GET https://app.firmame.com/api/account` Requiere el header `Authorization` del esquema `signmage`. Devuelve lo que queda de los paquetes contratados. Vale la pena revisarlo antes de un envío masivo, porque quedarse sin saldo a media tanda falla documento por documento, no de entrada. ## Respuestas ### `200` Los saldos restantes. `application/json`: - `documents` (integer, opcional) — Documentos restantes. - `remaining` (integer, opcional) El mismo número que `documents`; se conservan los dos nombres para que las integraciones existentes sigan funcionando. - `verifications` (integer, opcional) — Verificaciones remotas de identidad restantes. - `whatsAppMessages` (integer, opcional) — Mensajes de WhatsApp restantes. Ejemplo: ```json { "remaining": 1200, "documents": 1200, "verifications": 600, "whatsAppMessages": 2000 } ``` ### `400` No se pudieron leer los saldos. `application/json`: - `error` (string, opcional) ### `401` No se logró autenticar. El cuerpo viene vacío; revisa tu web ID, que la llave de API esté activa, y que la cadena firmada siga las reglas del esquema `signmage`. # Consultar el reporte de consumo (https://devs.firmame.com/docs/api/webhooks-y-cuenta/get-account-report) `GET https://app.firmame.com/api/accountReport/{startDate}/{endDate}` Requiere el header `Authorization` del esquema `signmage`. Devuelve lo que consumió la cuenta entre dos fechas — documentos creados, verificaciones realizadas, mensajes enviados — para conciliación y cobro interno. Las fechas van como segmentos de la ruta, así que acuérdate de la regla de firma de `GET`: la firma cubre la URI completa, estos segmentos incluidos. **Mándalas como ISO-8601 completo, no solo la fecha** — el mismo string que da `new Date().toISOString()` en JavaScript, por ejemplo `2026-08-11T00:00:00.000Z`. Una fecha sin hora (`2026-08-11`) llega al endpoint sin error, pero no recupera nada. ## Parámetros | Nombre | En | Tipo | Requerido | Descripción | |---|---|---|---|---| | `startDate` | path | string, format `date-time` | requerido | Inicio del periodo, como ISO-8601 completo. | | `endDate` | path | string, format `date-time` | requerido | Fin del periodo, como ISO-8601 completo. | ## Respuestas ### `200` El reporte de consumo — o un error, reportado en el cuerpo en vez del código de estado. Una fecha sin hora en vez de un ISO-8601 completo produce `{"error": "could not retrieve information"}` aquí, no un `400`. `application/json`: - `error` (string, opcional) — Solo aparece cuando no se pudo generar el reporte. Ejemplo — Se mandó una fecha sin hora en vez de un ISO-8601 completo: ```json { "error": "could not retrieve information" } ``` ### `400` No se pudo generar el reporte — normalmente, una fecha que no se pudo interpretar. El cuerpo trae el motivo. `application/json`: - `error` (string, opcional) ### `401` No se logró autenticar. El cuerpo viene vacío; revisa tu web ID, que la llave de API esté activa, y que la cadena firmada siga las reglas del esquema `signmage`. # Una verificación de identidad pasó (https://devs.firmame.com/docs/api/webhooks-y-cuenta/identity-verification-completed) `POST` — Firmame envía esta petición a la URL que registres. Se confirmó la identidad del firmante y ya puede firmar. `meta` guarda todo el historial, así que los intentos anteriores siguen en el arreglo. Lee la entrada con `status: "SUCCESS"` en vez de asumir que solo hay una. ## Cuerpo que recibes `application/json`: - `notification_type` (WebhookType, requerido, `original_signed` | `universal_signed` | `document_completed` | `document_rejected` | `workflow_expired` | `whatsapp_error` | `sms_error` | `email_error` | `original_nom151_stamped` | `universal_nom151_stamped` | `identity_verification_failed` | `identity_verification_completed`) — El evento al que se suscribe. - `notification_id` (string, requerido) Id de esta notificación. Mándalo a `POST /developers/webhook` para obtener el documento al que se refiere. - `firmamex_id` (string, requerido) Id del documento. El mismo valor es `document_ticket` cuando creas el documento y `ticket` en la mayoría de los demás endpoints. - `document_title` (string, opcional) - `text` (string, opcional) — Una descripción del evento en lenguaje natural, en inglés. - `signer_email` (string, opcional) — Presente en los eventos de firma. - `signer_fingerprint` (string, opcional) — Huella del certificado del firmante. Presente en los eventos de firma. - `signer_data` (object, opcional) — Los datos del firmante. Presente en los eventos de firma. - `id` (integer, opcional) - `email` (string, opcional) - `name` (string, opcional) - `fingerprint` (string, opcional) - `authority` (string, opcional) - `user` (integer, opcional) - `signer` (integer, opcional) - `meta` (array, opcional) - `status` (string, opcional, `SUCCESS` | `FAILURE`) - `date` (string, opcional, format `date-time`) - `signer` (string, opcional) — A qué firmante pertenece este intento. - `errors` (array, opcional) — Vacío cuando `status` es `SUCCESS`. - `category` (string, opcional, `USABILITY` | `IMAGE_CHECK` | `LIVENESS` | `SIMILARITY` | `DATA_CHECK`) - `message` (string, opcional) Ejemplo — Un firmante que falló una vez y pasó en el reintento: ```json { "meta": [ { "status": "FAILURE", "date": "2025-03-24T04:11:42.000Z", "errors": [ { "category": "USABILITY", "message": "PHOTOCOPY" } ], "signer": "firmante@dominio.com" }, { "status": "SUCCESS", "date": "2025-03-25T10:38:11.000Z", "errors": [], "signer": "firmante@dominio.com" } ], "notification_type": "identity_verification_completed", "notification_id": "2701", "document_title": "documento.pdf", "firmamex_id": "04337396-e929-43ca-9b8a-ec958a44079d" } ``` ## Lo que debes responder ### `200` Responde `200` para confirmarlo. # Una verificación de identidad no pasó (https://devs.firmame.com/docs/api/webhooks-y-cuenta/identity-verification-failed) `POST` — Firmame envía esta petición a la URL que registres. Se manda para firmantes con `requireIdentityVerification`. `meta` es un arreglo con una entrada por intento, así que un firmante que reintenta produce un arreglo más largo en vez de varias notificaciones. ## Cuerpo que recibes `application/json`: - `notification_type` (WebhookType, requerido, `original_signed` | `universal_signed` | `document_completed` | `document_rejected` | `workflow_expired` | `whatsapp_error` | `sms_error` | `email_error` | `original_nom151_stamped` | `universal_nom151_stamped` | `identity_verification_failed` | `identity_verification_completed`) — El evento al que se suscribe. - `notification_id` (string, requerido) Id de esta notificación. Mándalo a `POST /developers/webhook` para obtener el documento al que se refiere. - `firmamex_id` (string, requerido) Id del documento. El mismo valor es `document_ticket` cuando creas el documento y `ticket` en la mayoría de los demás endpoints. - `document_title` (string, opcional) - `text` (string, opcional) — Una descripción del evento en lenguaje natural, en inglés. - `signer_email` (string, opcional) — Presente en los eventos de firma. - `signer_fingerprint` (string, opcional) — Huella del certificado del firmante. Presente en los eventos de firma. - `signer_data` (object, opcional) — Los datos del firmante. Presente en los eventos de firma. - `id` (integer, opcional) - `email` (string, opcional) - `name` (string, opcional) - `fingerprint` (string, opcional) - `authority` (string, opcional) - `user` (integer, opcional) - `signer` (integer, opcional) - `meta` (array, opcional) - `status` (string, opcional, `SUCCESS` | `FAILURE`) - `date` (string, opcional, format `date-time`) - `signer` (string, opcional) — A qué firmante pertenece este intento. - `errors` (array, opcional) — Vacío cuando `status` es `SUCCESS`. - `category` (string, opcional, `USABILITY` | `IMAGE_CHECK` | `LIVENESS` | `SIMILARITY` | `DATA_CHECK`) - `message` (string, opcional) - `first_notification_date` (string, opcional, format `date-time`) Ejemplo: ```json { "meta": [ { "status": "FAILURE", "date": "2025-03-24T04:11:42.000Z", "errors": [ { "category": "USABILITY", "message": "PHOTOCOPY" } ] } ], "notification_type": "identity_verification_failed", "notification_id": "2701", "first_notification_date": "2025-03-25T01:53:46Z", "document_title": "documento.pdf", "firmamex_id": "04337396-e929-43ca-9b8a-ec958a44079d" } ``` ## Lo que debes responder ### `200` Responde `200` para confirmarlo. # El documento original se selló con NOM-151 (https://devs.firmame.com/docs/api/webhooks-y-cuenta/original-nom151-stamped) `POST` — Firmame envía esta petición a la URL que registres. `meta` trae el sello mismo. Su `hash` es lo que recibe `GET /api/document/{frmxId}/stamp/{stampHash}/pdf`. ## Cuerpo que recibes `application/json`: - `notification_type` (WebhookType, requerido, `original_signed` | `universal_signed` | `document_completed` | `document_rejected` | `workflow_expired` | `whatsapp_error` | `sms_error` | `email_error` | `original_nom151_stamped` | `universal_nom151_stamped` | `identity_verification_failed` | `identity_verification_completed`) — El evento al que se suscribe. - `notification_id` (string, requerido) Id de esta notificación. Mándalo a `POST /developers/webhook` para obtener el documento al que se refiere. - `firmamex_id` (string, requerido) Id del documento. El mismo valor es `document_ticket` cuando creas el documento y `ticket` en la mayoría de los demás endpoints. - `document_title` (string, opcional) - `text` (string, opcional) — Una descripción del evento en lenguaje natural, en inglés. - `signer_email` (string, opcional) — Presente en los eventos de firma. - `signer_fingerprint` (string, opcional) — Huella del certificado del firmante. Presente en los eventos de firma. - `signer_data` (object, opcional) — Los datos del firmante. Presente en los eventos de firma. - `id` (integer, opcional) - `email` (string, opcional) - `name` (string, opcional) - `fingerprint` (string, opcional) - `authority` (string, opcional) - `user` (integer, opcional) - `signer` (integer, opcional) - `meta` (Nom151StampMeta, opcional) — Un sello NOM-151 tal como lo reportan los webhooks. - `hash` (string, opcional) — El digest que cubre el sello, en hexadecimal. - `tsa` (string, opcional) — Nombre distintivo de la autoridad de sellado de tiempo. - `genTime` (string, opcional, format `date-time`) — Cuándo emitió el sello la autoridad. - `policy` (string, opcional) — OID de la política bajo la que la autoridad lo emitió. - `status` (integer, opcional) El veredicto de la autoridad, como el código numérico del RFC 3161 — `0` es concedido. Los endpoints de sellado reportan ese mismo veredicto con una palabra. Ejemplo: ```json { "meta": { "hash": "017a330e54ba39ec30919b41d96bf9bc2a3b64c259158b1d88f1c59f8e21c2d3", "tsa": "L=MIGUEL HIDALGO,ST=CIUDAD DE MEXICO,C=MX,CN=CECOBAN-PSC-NOM151-1-01,O=CECOBAN SA DE CV", "policy": "2.16.484.101.10.316.2.3.1.2.1.1.1", "genTime": "2021-03-30T02:34:24.582Z", "status": 0 }, "notification_type": "original_nom151_stamped", "notification_id": "2699", "document_title": "contrato.pdf", "firmamex_id": "04337396-e929-43ca-9b8a-ec958a44079d" } ``` ## Lo que debes responder ### `200` Responde `200` para confirmarlo. # Un firmante firmó el documento (https://devs.firmame.com/docs/api/webhooks-y-cuenta/original-signed) `POST` — Firmame envía esta petición a la URL que registres. Se dispara cada vez que alguien firma. En un documento con varios firmantes recibes uno de estos por cada firma; espera `document_completed` para saber que ya terminaron todos. ## Cuerpo que recibes `application/json`: - `notification_type` (WebhookType, requerido, `original_signed` | `universal_signed` | `document_completed` | `document_rejected` | `workflow_expired` | `whatsapp_error` | `sms_error` | `email_error` | `original_nom151_stamped` | `universal_nom151_stamped` | `identity_verification_failed` | `identity_verification_completed`) — El evento al que se suscribe. - `notification_id` (string, requerido) Id de esta notificación. Mándalo a `POST /developers/webhook` para obtener el documento al que se refiere. - `firmamex_id` (string, requerido) Id del documento. El mismo valor es `document_ticket` cuando creas el documento y `ticket` en la mayoría de los demás endpoints. - `document_title` (string, opcional) - `text` (string, opcional) — Una descripción del evento en lenguaje natural, en inglés. - `signer_email` (string, opcional) — Presente en los eventos de firma. - `signer_fingerprint` (string, opcional) — Huella del certificado del firmante. Presente en los eventos de firma. - `signer_data` (object, opcional) — Los datos del firmante. Presente en los eventos de firma. - `id` (integer, opcional) - `email` (string, opcional) - `name` (string, opcional) - `fingerprint` (string, opcional) - `authority` (string, opcional) - `user` (integer, opcional) - `signer` (integer, opcional) Ejemplo: ```json { "signer_data": { "id": 2000, "email": "firmante@dominio.com", "fingerprint": "667483ad8b935fd6874477da9e822a60d6ab3b22", "authority": "Vinculada a Correo Electrónico por Liga", "name": "firmante@dominio.com" }, "signer_email": "firmante@dominio.com", "signer_fingerprint": "667483ad8b935fd6874477da9e822a60d6ab3b22", "user": 1000, "signer": 2000, "notification_type": "original_signed", "notification_id": "2697", "document_title": "contrato.pdf", "firmamex_id": "04337396-e929-43ca-9b8a-ec958a44079d", "text": "Document 04337396-e929-43ca-9b8a-ec958a44079d.pdf just signed" } ``` ## Lo que debes responder ### `200` Responde `200` para confirmarlo. Cualquier otra cosa se reintenta con backoff exponencial. # No se pudo entregar un SMS (https://devs.firmame.com/docs/api/webhooks-y-cuenta/sms-error) `POST` — Firmame envía esta petición a la URL que registres. Misma forma y los mismos códigos que `whatsapp_error`. ## Cuerpo que recibes `application/json`: - `notification_type` (WebhookType, requerido, `original_signed` | `universal_signed` | `document_completed` | `document_rejected` | `workflow_expired` | `whatsapp_error` | `sms_error` | `email_error` | `original_nom151_stamped` | `universal_nom151_stamped` | `identity_verification_failed` | `identity_verification_completed`) — El evento al que se suscribe. - `notification_id` (string, requerido) Id de esta notificación. Mándalo a `POST /developers/webhook` para obtener el documento al que se refiere. - `firmamex_id` (string, requerido) Id del documento. El mismo valor es `document_ticket` cuando creas el documento y `ticket` en la mayoría de los demás endpoints. - `document_title` (string, opcional) - `text` (string, opcional) — Una descripción del evento en lenguaje natural, en inglés. - `signer_email` (string, opcional) — Presente en los eventos de firma. - `signer_fingerprint` (string, opcional) — Huella del certificado del firmante. Presente en los eventos de firma. - `signer_data` (object, opcional) — Los datos del firmante. Presente en los eventos de firma. - `id` (integer, opcional) - `email` (string, opcional) - `name` (string, opcional) - `fingerprint` (string, opcional) - `authority` (string, opcional) - `user` (integer, opcional) - `signer` (integer, opcional) - `meta` (MessagingError, opcional) — La falla reportada por el proveedor de mensajería. - `code` (string, opcional) | Código | Significado | Qué hacer | |---|---|---| | `400` | Falta el texto del mensaje | Corrige la petición | | `401` | Falta el número de celular | Corrige la petición | | `402` | El mensaje es demasiado largo | Corrige la petición | | `403` | Número inválido | Corrige la petición | | `450` | No se pudo entregar | Reintenta | | `500` | Error del proveedor | Contacta a Firmame | - `message` (string, opcional) Ejemplo: ```json { "meta": { "message": "Error interno de servidor", "code": "500" }, "notification_type": "sms_error", "notification_id": "2702", "document_title": "contrato.pdf", "firmamex_id": "24ab99f2-eb3b-43c4-812d-2976490156e8" } ``` ## Lo que debes responder ### `200` Responde `200` para confirmarlo. # El documento universal se selló con NOM-151 (https://devs.firmame.com/docs/api/webhooks-y-cuenta/universal-nom151-stamped) `POST` — Firmame envía esta petición a la URL que registres. Misma forma que `original_nom151_stamped`, para la representación universal. ## Cuerpo que recibes `application/json`: - `notification_type` (WebhookType, requerido, `original_signed` | `universal_signed` | `document_completed` | `document_rejected` | `workflow_expired` | `whatsapp_error` | `sms_error` | `email_error` | `original_nom151_stamped` | `universal_nom151_stamped` | `identity_verification_failed` | `identity_verification_completed`) — El evento al que se suscribe. - `notification_id` (string, requerido) Id de esta notificación. Mándalo a `POST /developers/webhook` para obtener el documento al que se refiere. - `firmamex_id` (string, requerido) Id del documento. El mismo valor es `document_ticket` cuando creas el documento y `ticket` en la mayoría de los demás endpoints. - `document_title` (string, opcional) - `text` (string, opcional) — Una descripción del evento en lenguaje natural, en inglés. - `signer_email` (string, opcional) — Presente en los eventos de firma. - `signer_fingerprint` (string, opcional) — Huella del certificado del firmante. Presente en los eventos de firma. - `signer_data` (object, opcional) — Los datos del firmante. Presente en los eventos de firma. - `id` (integer, opcional) - `email` (string, opcional) - `name` (string, opcional) - `fingerprint` (string, opcional) - `authority` (string, opcional) - `user` (integer, opcional) - `signer` (integer, opcional) - `meta` (Nom151StampMeta, opcional) — Un sello NOM-151 tal como lo reportan los webhooks. - `hash` (string, opcional) — El digest que cubre el sello, en hexadecimal. - `tsa` (string, opcional) — Nombre distintivo de la autoridad de sellado de tiempo. - `genTime` (string, opcional, format `date-time`) — Cuándo emitió el sello la autoridad. - `policy` (string, opcional) — OID de la política bajo la que la autoridad lo emitió. - `status` (integer, opcional) El veredicto de la autoridad, como el código numérico del RFC 3161 — `0` es concedido. Los endpoints de sellado reportan ese mismo veredicto con una palabra. Ejemplo: ```json { "meta": { "hash": "017a330e54ba39ec30919b41d96bf9bc2a3b64c259158b1d88f1c59f8e21c2d3", "tsa": "L=MIGUEL HIDALGO,ST=CIUDAD DE MEXICO,C=MX,CN=CECOBAN-PSC-NOM151-1-01,O=CECOBAN SA DE CV", "policy": "2.16.484.101.10.316.2.3.1.2.1.1.1", "genTime": "2021-03-30T02:34:24.582Z", "status": 0 }, "notification_type": "universal_nom151_stamped", "notification_id": "2701", "document_title": "contrato.pdf", "firmamex_id": "04337396-e929-43ca-9b8a-ec958a44079d" } ``` ## Lo que debes responder ### `200` Responde `200` para confirmarlo. # Se encapsuló una firma (https://devs.firmame.com/docs/api/webhooks-y-cuenta/universal-signed) `POST` — Firmame envía esta petición a la URL que registres. Se dispara cuando la firma ya se envolvió en la representación universal de validación a largo plazo. Llega después de `original_signed` para esa misma firma. ## Cuerpo que recibes `application/json`: - `notification_type` (WebhookType, requerido, `original_signed` | `universal_signed` | `document_completed` | `document_rejected` | `workflow_expired` | `whatsapp_error` | `sms_error` | `email_error` | `original_nom151_stamped` | `universal_nom151_stamped` | `identity_verification_failed` | `identity_verification_completed`) — El evento al que se suscribe. - `notification_id` (string, requerido) Id de esta notificación. Mándalo a `POST /developers/webhook` para obtener el documento al que se refiere. - `firmamex_id` (string, requerido) Id del documento. El mismo valor es `document_ticket` cuando creas el documento y `ticket` en la mayoría de los demás endpoints. - `document_title` (string, opcional) - `text` (string, opcional) — Una descripción del evento en lenguaje natural, en inglés. - `signer_email` (string, opcional) — Presente en los eventos de firma. - `signer_fingerprint` (string, opcional) — Huella del certificado del firmante. Presente en los eventos de firma. - `signer_data` (object, opcional) — Los datos del firmante. Presente en los eventos de firma. - `id` (integer, opcional) - `email` (string, opcional) - `name` (string, opcional) - `fingerprint` (string, opcional) - `authority` (string, opcional) - `user` (integer, opcional) - `signer` (integer, opcional) Ejemplo: ```json { "signer_data": { "id": 2000, "email": "firmante@dominio.com", "fingerprint": "667483ad8b935fd6874477da9e822a60d6ab3b22", "authority": "Vinculada a Correo Electrónico por Liga", "name": "firmante@dominio.com" }, "signer_email": "firmante@dominio.com", "signer_fingerprint": "667483ad8b935fd6874477da9e822a60d6ab3b22", "user": 1000, "signer": 2000, "notification_type": "universal_signed", "notification_id": "2700", "document_title": "contrato.pdf", "firmamex_id": "04337396-e929-43ca-9b8a-ec958a44079d", "text": "Document 04337396-e929-43ca-9b8a-ec958a44079d.pdf just signed" } ``` ## Lo que debes responder ### `200` Responde `200` para confirmarlo. # No se pudo entregar un mensaje de WhatsApp (https://devs.firmame.com/docs/api/webhooks-y-cuenta/whatsapp-error) `POST` — Firmame envía esta petición a la URL que registres. El firmante nunca recibió su invitación. `meta.code` te dice si conviene corregir la petición, reintentar, o ponerte en contacto. ## Cuerpo que recibes `application/json`: - `notification_type` (WebhookType, requerido, `original_signed` | `universal_signed` | `document_completed` | `document_rejected` | `workflow_expired` | `whatsapp_error` | `sms_error` | `email_error` | `original_nom151_stamped` | `universal_nom151_stamped` | `identity_verification_failed` | `identity_verification_completed`) — El evento al que se suscribe. - `notification_id` (string, requerido) Id de esta notificación. Mándalo a `POST /developers/webhook` para obtener el documento al que se refiere. - `firmamex_id` (string, requerido) Id del documento. El mismo valor es `document_ticket` cuando creas el documento y `ticket` en la mayoría de los demás endpoints. - `document_title` (string, opcional) - `text` (string, opcional) — Una descripción del evento en lenguaje natural, en inglés. - `signer_email` (string, opcional) — Presente en los eventos de firma. - `signer_fingerprint` (string, opcional) — Huella del certificado del firmante. Presente en los eventos de firma. - `signer_data` (object, opcional) — Los datos del firmante. Presente en los eventos de firma. - `id` (integer, opcional) - `email` (string, opcional) - `name` (string, opcional) - `fingerprint` (string, opcional) - `authority` (string, opcional) - `user` (integer, opcional) - `signer` (integer, opcional) - `meta` (MessagingError, opcional) — La falla reportada por el proveedor de mensajería. - `code` (string, opcional) | Código | Significado | Qué hacer | |---|---|---| | `400` | Falta el texto del mensaje | Corrige la petición | | `401` | Falta el número de celular | Corrige la petición | | `402` | El mensaje es demasiado largo | Corrige la petición | | `403` | Número inválido | Corrige la petición | | `450` | No se pudo entregar | Reintenta | | `500` | Error del proveedor | Contacta a Firmame | - `message` (string, opcional) Ejemplo: ```json { "meta": { "message": "Error interno de servidor", "code": "500" }, "notification_type": "whatsapp_error", "notification_id": "2702", "document_title": "contrato.pdf", "firmamex_id": "24ab99f2-eb3b-43c4-812d-2976490156e8" } ``` ## Lo que debes responder ### `200` Responde `200` para confirmarlo. # El flujo de firma expiró (https://devs.firmame.com/docs/api/webhooks-y-cuenta/workflow-expired) `POST` — Firmame envía esta petición a la URL que registres. El flujo llegó a su `expirationDate` con firmas todavía pendientes. Extiéndelo con `POST /api/{firmamexId}/workflow` para darle más tiempo a los firmantes que faltan. ## Cuerpo que recibes `application/json`: - `notification_type` (WebhookType, requerido, `original_signed` | `universal_signed` | `document_completed` | `document_rejected` | `workflow_expired` | `whatsapp_error` | `sms_error` | `email_error` | `original_nom151_stamped` | `universal_nom151_stamped` | `identity_verification_failed` | `identity_verification_completed`) — El evento al que se suscribe. - `notification_id` (string, requerido) Id de esta notificación. Mándalo a `POST /developers/webhook` para obtener el documento al que se refiere. - `firmamex_id` (string, requerido) Id del documento. El mismo valor es `document_ticket` cuando creas el documento y `ticket` en la mayoría de los demás endpoints. - `document_title` (string, opcional) - `text` (string, opcional) — Una descripción del evento en lenguaje natural, en inglés. - `signer_email` (string, opcional) — Presente en los eventos de firma. - `signer_fingerprint` (string, opcional) — Huella del certificado del firmante. Presente en los eventos de firma. - `signer_data` (object, opcional) — Los datos del firmante. Presente en los eventos de firma. - `id` (integer, opcional) - `email` (string, opcional) - `name` (string, opcional) - `fingerprint` (string, opcional) - `authority` (string, opcional) - `user` (integer, opcional) - `signer` (integer, opcional) Ejemplo: ```json { "notification_type": "workflow_expired", "notification_id": "2693", "document_title": "contrato.pdf", "firmamex_id": "38a81807-be84-4ec2-8d39-33a1b5c1d78a" } ``` ## Lo que debes responder ### `200` Responde `200` para confirmarlo.