Con el canal de WhatsApp, un usuario del portal factura escribiendo: «Factura a 131000000, 10 sacos de cemento a 500». Firmo le responde con un resumen, espera su SÍ, emite el e-CF y le devuelve el e-NCF, el estado en la DGII y el PDF. Funciona sobre la API de WhatsApp de Twilio.
Cómo funciona#
Tú: Factura a 131000000, 10 sacos de cemento a 500
Firmo: *Factura de crédito fiscal (E31)*
Cliente: Ferretería X · RNC 131000000
• 10 × Cemento a RD$500 = RD$5,000
Subtotal: RD$5,000
ITBIS: RD$900
*Total: RD$5,900*
Modo prueba: no llega a la DGII.
Responde *SÍ* para emitir o *NO* para cancelar.
Tú: SÍ
Firmo: *Factura de crédito fiscal (E31) E310000000001*
Estado: Aceptado por el simulador (modo prueba)
Total: RD$5,900
Te adjunto el PDF (el enlace vence en 7 días). [PDF]- Twilio recibe el mensaje y hace
POSTal webhook de Firmo con el formulario del mensaje. - Firmo valida la firma
X-Twilio-Signature, responde 200 al instante y encola el mensaje (Twilio corta a los 15 s). - El worker identifica al usuario por el número
Fromvinculado, interpreta el texto y guarda la propuesta 15 minutos. - Con el SÍ, emite con el id de la conversación como llave de idempotencia: un reintento nunca factura dos veces.
- Responde por la API REST de Twilio con el resultado y el PDF en
MediaUrl, un enlace firmado que vence en 7 días.
Si la empresa está en producción, el resumen lo dice en mayúsculas y hay que escribir SÍ EMITIR. Con modo prueba el usuario practica en sandbox aunque su empresa ya esté en producción.
Configurar Twilio#
1. Pruebas con el sandbox de Twilio
- Crea una cuenta en Twilio y abre Messaging → Try it out → Send a WhatsApp message. El sandbox usa el número
+1 415 523 8886. - Desde tu teléfono, envía a ese número el mensaje
join <palabra-clave>que te muestra Twilio. Cada teléfono que vaya a probar debe hacerlo (y repetirlo a los 3 días). - En Sandbox settings, en When a message comes in, pon la URL del webhook con el método
POST:
https://api.firmo.do/v1/channels/whatsapp/twilioLuego define las variables en el servicio de la API y reinícialo:
# apps/api/.env (Railway: Variables del servicio)
TWILIO_ACCOUNT_SID=AC… # Twilio Console → Account Info
TWILIO_AUTH_TOKEN=… # firma los webhooks y autentica los envíos
TWILIO_WHATSAPP_FROM=whatsapp:+14155238886 # sandbox; en producción, tu sender aprobado
TWILIO_PLANTILLA_CODIGO_SID=HX… # opcional: plantilla del código de verificación
ANTHROPIC_API_KEY=sk-ant-… # opcional: lenguaje natural (si falta, formato fijo)
WEB_URL_PUBLICA=https://firmo.doLa URL debe coincidir
Twilio firma la URL exacta que configuraste. Firmo la compara conAPI_URL_PUBLICA + la ruta: si usas otro dominio, actualiza esa variable o la firma no coincidirá y el webhook responderá 403.2. Producción: sender de WhatsApp aprobado
- En Twilio, registra un WhatsApp Sender con tu número y tu cuenta de WhatsApp Business (Meta verifica la empresa; tarda de horas a días).
- Cambia
TWILIO_WHATSAPP_FROMporwhatsapp:+1809…y configura el mismo webhook en el sender. - Crea y somete a aprobación las plantillas de abajo.
3. Plantillas de mensajes
WhatsApp solo deja escribir texto libre dentro de las 24 horas siguientes al último mensaje del usuario. Fuera de esa ventana, el primer mensaje debe ser una plantilla aprobada por Meta (Twilio Content Template Builder).
| Plantilla | Categoría | Texto sugerido | Uso |
|---|---|---|---|
| firmo_codigo | Authentication | Tu código de Firmo es {{1}}. Vence en 10 minutos. | Código de vinculación. Pon su ContentSid en TWILIO_PLANTILLA_CODIGO_SID. |
| firmo_bienvenida | Utility | Hola {{1}}, ya puedes facturar por WhatsApp con Firmo. Escribe «ayuda» para ver ejemplos. | Para iniciar conversación (opcional). |
Sin plantilla de código, Firmo lo envía como texto libre: llega si el usuario escribió en las últimas 24 horas (el portal le pide escribir «hola» primero). En el sandbox de Twilio no hace falta plantilla.
Vincular el número#
Se hace desde el portal, en WhatsApp, o con estas rutas (solo con la sesión del portal, no con API keys). El código de 6 dígitos vence a los 10 minutos y admite 5 intentos. Un número solo puede estar ligado a un usuario, y se factura con la empresa activa al vincular.
POST/v1/channels/whatsapp/link
POST/v1/channels/whatsapp/verify
# 1. Enviar el código (con la sesión del portal)
curl -X POST https://api.firmo.do/v1/channels/whatsapp/link \
-H "Authorization: Bearer $FIRMO_SESION" \
-H "Content-Type: application/json" \
-d '{ "telefono": "809-555-1234" }'
# 2. Confirmar con el código que llegó por WhatsApp
curl -X POST https://api.firmo.do/v1/channels/whatsapp/verify \
-H "Authorization: Bearer $FIRMO_SESION" \
-H "Content-Type: application/json" \
-d '{ "codigo": "482913" }'GET/v1/channels/whatsapp
{
"habilitado": true,
"motivo": null,
"numeroFirmo": "+14155238886",
"enlaceWhatsapp": "https://wa.me/14155238886",
"interprete": "claude",
"vinculo": {
"telefono": "+18095551234",
"estado": "vinculado",
"empresaId": "00000000-0000-4000-8000-000000000000",
"empresa": "Ferretería Ejemplo SRL",
"verificadoEn": "2026-10-06T14:20:00.000Z",
"codigoVenceEn": null,
"modoPrueba": false
}
}DELETE /v1/channels/whatsapp desvincula el número. Si un número desconocido le escribe a Firmo, el bot le explica cómo vincularlo desde el portal.
Cómo se interpreta el mensaje#
- Con
ANTHROPIC_API_KEY: Claude Haiku 4.5 recibe el mensaje, las reglas (E31 con RNC, E32 por defecto, precios sin ITBIS salvo «con ITBIS incluido», ITBIS 18%) y hasta 50 productos y 50 clientes guardados, y responde con la herramientacrear_factura. Firmo valida la salida y descarta cualquier RNC que no esté en el mensaje ni en tus clientes: nunca inventa un RNC. - Sin la llave: formato fijo,
factura 31 rnc 131000000; 10 x cemento @ 500; 2 x arena @ 300(conexentodespués del precio yitbis incluidoen la primera parte cuando aplique).
Comandos#
| Mensaje | Respuesta |
|---|---|
ayuda | Ejemplos y comandos. |
ultimas | Las últimas 5 facturas. |
estado E310000000001 | Estado del e-CF en la DGII. |
pdf E310000000001 | El PDF adjunto. |
cancelar | Descarta la factura pendiente. |
modo | Si estás en prueba o en producción (y modo prueba / modo producción para cambiar). |
PDF con enlace firmado#
GET/v1/public/ecf/{id}/pdf?exp=…&sig=…
La representación impresa en PDF, sin API key, para que Twilio la descargue y la adjunte. exp es el vencimiento (7 días) y sig un HMAC-SHA256 del id y exp. Un enlace vencido responde 410 y uno alterado 403.
Costos
Twilio cobra cada mensaje de WhatsApp más la tarifa de Meta por conversación. Interpretar un mensaje con Claude Haiku 4.5 cuesta alrededor de medio centavo de dólar.