Webhooks — e-CF recibidos
Cuando un proveedor emite una factura electrónica (e-CF) a nombre del RNC de tu empresa, la recibimos automáticamente. Si configuras un webhook, te reenviamos ese e-CF a tu propio endpoint en cuanto llega — así puedes integrarlo con tu ERP/contabilidad sin hacer polling.
- Asíncrono: el reenvío ocurre en segundo plano; no bloquea la recepción.
- Con reintentos: si tu endpoint falla, reintentamos automáticamente.
- Firmado: cada request lleva una firma HMAC-SHA256 para que verifiques que proviene de nosotros.
Configuración
Desde el portal → Webhook:
- Activa el webhook y coloca la URL de tu endpoint (
https://…). - (Opcional) Agrega headers personalizados que enviaremos en cada
llamada — por ejemplo tu propia API key:
Authorization: Bearer …. - Copia el secreto de firma (lo necesitas para verificar la firma).
El reenvío se activa solo para e-CF recibidos (facturas de tus proveedores). No aplica a los comprobantes que tú emites vía el API.
El request que enviamos
POST a tu URL con Content-Type: application/json:
POST https://tu-servidor.com/webhooks/ecf
Content-Type: application/json
X-Appwork-Signature: sha256=<hmac-hex>
X-Appwork-Event: received_ecf
X-Appwork-Delivery: <id-de-entrega>
Authorization: Bearer <tu-header-personalizado> # si lo configurasteCuerpo (JSON)
{
"event": "received_ecf",
"sentAt": "2026-07-23T15:04:05.000Z",
"receivedEcf": {
"id": "b3f1c2d4-…",
"encf": "E310000000123",
"tipoEcf": "E31",
"rncEmisor": "101672919",
"rncComprador": "101672919",
"montoTotal": 11800.0,
"fechaEmision": "2026-07-20",
"approvalStatus": "pending",
"receivedAt": "2026-07-23T15:04:04.000Z"
},
"xml": "<?xml version=\"1.0\"?><ECF>…</ECF>"
}El campo xml contiene el comprobante completo (firmado). Los campos de
receivedEcf son un resumen para que no tengas que parsear el XML si solo
necesitas los montos y el estado.
Verificar la firma
Calcula el HMAC-SHA256 del cuerpo crudo (bytes exactos que recibiste) con
tu secreto, en hexadecimal, y compáralo (en tiempo constante) con el valor
del header X-Appwork-Signature (sin el prefijo sha256=).
import crypto from 'crypto'
function verify(rawBody, signatureHeader, secret) {
const expected =
'sha256=' +
crypto.createHmac('sha256', secret).update(rawBody, 'utf8').digest('hex')
const a = Buffer.from(signatureHeader)
const b = Buffer.from(expected)
return a.length === b.length && crypto.timingSafeEqual(a, b)
}
// Express (usa el cuerpo crudo, NO el ya parseado por JSON):
app.post('/webhooks/ecf', express.raw({ type: 'application/json' }), (req, res) => {
const sig = req.header('X-Appwork-Signature')
if (!verify(req.body, sig, process.env.APPWORK_WEBHOOK_SECRET)) {
return res.status(401).send('bad signature')
}
const payload = JSON.parse(req.body.toString('utf8'))
// … procesa payload.receivedEcf / payload.xml
res.sendStatus(200) // responde 2xx para confirmar la entrega
})Verifica siempre la firma sobre el cuerpo crudo. Si tu framework ya parseó el JSON, re-serializar puede cambiar bytes (orden/espacios) y la firma no coincidirá. Usa el raw body.
Respuesta esperada y reintentos
- Responde con un código 2xx (o 3xx) para confirmar que recibiste el e-CF.
- Cualquier otro código, timeout o error de red se considera fallo y lo reintentamos automáticamente con backoff, hasta agotar los intentos.
- Puedes ver el estado de cada entrega e reintentar manualmente desde el portal → Webhook → Historial de entregas.
Tu endpoint debe ser idempotente: ante un reintento podrías recibir el
mismo e-CF más de una vez. Usa el encf (o X-Appwork-Delivery) para
deduplicar.
Buenas prácticas
- Responde rápido (≤ 20s). Si tu procesamiento es pesado, encola y responde 2xx de inmediato.
- Rota el secreto desde el portal si sospechas que se filtró.
- Restringe por IP/headers si tu endpoint es sensible.