Factuarea APIDevelopers

Bandeja de eventos de integración

Por qué un cobro no acabó en factura — los motivos de descarte tipados, cuáles te avisan, cuáles parquean el evento para que puedas reprocesarlo, y cómo funciona la ventana de retención de 30 días.

Esta bandeja también cubre pedidos y devoluciones de WooCommerce y Shopify. Filtra por proveedor, consulta discard_reason y reprocesa un evento retenido solo cuando is_replayable lo permita. Consulta el flujo de tiendas.

Una pasarela de pago envía a Factuarea un evento por todo lo que ocurre en tu cuenta: un cobro con éxito, una devolución emitida, un ciclo de suscripción cobrado, un payout que llega. La mayoría de esos eventos producen algo — una factura, una rectificativa, un registro de pago. Algunos no producen nada, y cuando eso pasa la pregunta interesante siempre es la misma: ¿por qué este cobro no acabó en factura?

La bandeja de eventos de integración la contesta. Cada evento que Factuarea recibe queda registrado con lo que produjo y, cuando no produjo nada, con un motivo de descarte tipado salido de un catálogo cerrado. Sin adivinar en los logs, sin abrir un ticket de soporte: el motivo es un valor por el que puedes filtrar y, en los motivos sobre los que puedes actuar, viene con el siguiente paso y, a veces, con la posibilidad de reprocesar el evento.

La bandeja es agnóstica de la pasarela. Registra eventos de cualquier integración que escriba historial — incluidas las pasarelas que todavía no están liberadas y los eventos históricos de una que se retire — porque ocultar esas filas te dejaría sin explicación para cobros que nunca se facturaron.

La exponen tres endpoints:

OperaciónEndpointScope
Listar eventosGET /v1/integrations/eventsintegration_events:read
Obtener un eventoGET /v1/integrations/events/{event}integration_events:read
Reprocesar un evento parqueadoPOST /v1/integrations/events/{event}/replayintegration_events:write

La misma superficie existe como tools MCP — list_integrations_events, get_integrations_event y replay_integrations_event — con los mismos scopes.

Recorrer la bandeja

Del más reciente al más antiguo, acotado a la empresa autenticada. Paginación por cursor con limit (de 1 a 100; 25 por defecto) y starting_after:

curl -G https://api.factuarea.com/v1/integrations/events \
  -H "Authorization: Bearer $FACTUAREA_API_KEY" \
  --data-urlencode "provider=stripe" \
  --data-urlencode "status=skipped" \
  --data-urlencode "limit=50"
{
  "data": [
    {
      "id": "0192f3a4-7b2c-7c1d-9e8f-1a2b3c4d5e6f",
      "object": "integration_event",
      "provider": "stripe",
      "event_type": "invoice.paid",
      "direction": "inbound",
      "status": "skipped",
      "discard_reason": "subscription_autoinvoicing_disabled",
      "discard_reason_label": "Auto-facturación de suscripciones desactivada",
      "is_actionable": true,
      "is_replayable": true,
      "error_message": null,
      "duration_ms": 412,
      "created_at": "2026-07-14T09:31:07Z"
    }
  ],
  "has_more": true,
  "next_cursor": "84120"
}

Trata next_cursor como opaco: en este listado es una cadena numérica, no un UUID v7 como los cursores de los listados de documentos. Devuélvelo tal cual en starting_after.

discard_reason_label llega siempre en español, el idioma de la interfaz del producto, sea cual sea el idioma de tu integración. Si construyes un panel en otro idioma, apoya tus propios textos en discard_reason — ese valor es el identificador estable y cerrado.

Filtros

FiltroValoresNotas
providerstripe, gocardless, monei, slack, teams, a3, norma43, norma19, ubl, woocommerce, shopify, prestashopConjunto cerrado
statussuccess, skipped, failureConjunto cerrado
event_typetexto libre, coincidencia exacta, hasta 100 caracteresNo es un enum — ver abajo
discard_reasonuno de los motivos de descarte documentadosConjunto cerrado
is_parkedtrue / falseVer la nota de abajo
created_at[gte], created_at[lte]ISO 8601Ventana inclusiva

discard_reason es el eje cerrado; event_type no es un enum. La columna event_type mezcla a propósito dos convenciones: las ramas instrumentadas más tarde guardan el tipo crudo de la pasarela (charge.refunded), mientras que las preexistentes conservan su propio valor semántico (autoinvoice.*). Búscalo por coincidencia exacta cuando sepas qué persigues, pero no lo modeles nunca como un conjunto cerrado — estarías modelando algo que la columna no garantiza.

is_parked=false no es lo mismo que omitir el parámetro. El primero excluye los eventos parqueados; el segundo no excluye nada.

Un valor fuera de su catálogo devuelve 422, y un parámetro de consulta desconocido devuelve 400 parameter_unknown en lugar de ignorarse en silencio — un filtro que se cae sin avisar te entrega una página que crees acotada y no lo está.

El catálogo de motivos de descarte

Los motivos tipados cubren el procesamiento de pasarelas y tiendas. las pasarelas. Cada uno declara dos decisiones de negocio que no son banderas decorativas:

  • Accionable — ¿puede hacer algo el titular de la cuenta? Solo los motivos accionables avisan. Avisar a alguien de un descarte que no puede resolver le enseña a ignorar la bandeja, y así es como se pierde el aviso que sí importaba.
  • Parqueado — ¿reprocesar el mismo contenido podría dar otro resultado? Solo los eventos parqueados guardan su contenido cifrado y admiten un reproceso.

La regla detrás de la columna de parqueo: un evento se parquea cuando el descarte lo causó un estado externo que puedes cambiar (un ajuste apagado, una cuenta conectada que se desvinculó, una moneda todavía sin tipo de cambio). No se parquea cuando la causa es el contenido del propio evento (malformado, duplicado, de tipo no cubierto, importe cero, ciclo ya facturado) — reprocesarlo tomaría exactamente la misma rama y solo escribiría una segunda fila. De ahí el invariante: todo motivo parqueado es accionable..

MotivoQué lo provocaAccionableParqueadoQué hacer
event_not_normalizableEvento malformado, o de un tipo que no se puede interpretarNoNoNada — no puedes arreglar el payload de la pasarela
duplicate_redeliveryEl evento ya se procesó; su efecto existeNoNoNada — reprocesarlo sería un no-op por deduplicación
connected_account_missingEl webhook está mal configurado en la pasarela: el evento no dice a qué cuenta perteneceNoRevisa en la pasarela que el webhook se envía desde la cuenta que tienes vinculada en Factuarea
connected_account_unknownLa cuenta existe en la pasarela pero no está vinculada en FactuareaVuelve a vincular esa cuenta de la pasarela y reprocesa el evento
spontaneous_payment_missing_idEl cobro no trae id, así que no hay clave de idempotenciaNoNoNada — reprocesarlo duplicaría o volvería a fallar
autoinvoicing_disabledLa auto-facturación está desactivada para esa integraciónActiva la auto-facturación y reprocesa, o crea la factura a mano — nunca las dos cosas
unsupported_currencyEl tipo de cambio del Banco Central Europeo del día todavía no está disponibleReprocesa el evento más tarde, cuando el tipo oficial del día esté publicado
refund_without_itemsLa devolución no trae reembolsos individuales que rectificarNoNoNada — no hay nada que emitir
refund_autoinvoicing_disabledLa rectificativa automática está desactivada para esa integraciónActiva la rectificativa automática y reprocesa, o emite la rectificativa a mano — nunca las dos cosas
subscription_missing_invoice_idEl ciclo cobrado no tiene identificador de facturaNoCrea a mano la factura de este ciclo; reprocesar daría el mismo resultado
subscription_proration_reviewSe cobró un prorrateo suelto y exige una decisión humanaNoComprueba el importe del prorrateo en la pasarela y emite la factura a mano
subscription_not_a_cycleLa factura de la pasarela no corresponde a un ciclo de suscripción facturableNoNoNada — el descarte es correcto
subscription_trial_skippedImporte cero o negativo (prueba o crédito): no hay base imponibleNoNoNada — no hay nada que facturar
subscription_autoinvoicing_disabledLa auto-facturación de suscripciones está desactivadaActiva la auto-facturación de suscripciones y reprocesa el evento
subscription_already_invoicedEl ciclo ya tiene su facturaNoNoNada — reprocesarlo sería un no-op por idempotencia
payout_missing_idEl payout no trae identificadorNoNoNada — no se puede conciliar ni reprocesar con seguridad
payout_connected_account_missingLa cuenta conectada del payout no está vinculadaVincula la cuenta conectada y reprocesa el evento
payment_failedEl cobro falló en la pasarelaNoNoNada — no hay nada que emitir ni que reintentar
event_type_not_coveredTipo de evento fuera del alcance del productoNoNoNada — reprocesarlo volvería a no hacer nada
checkout_lines_retrieve_failedDegradación, no descarte: la factura se emitió, con una línea únicaNoNoNada que reprocesar; revisa las líneas de la factura si te importa el desglose
reversal_payment_not_foundNo hay un cobro que anular. Revisa el cobro original y corrígelo manualmente si corresponde.NoSigue la actuación indicada en esta fila.
reversal_already_appliedEl cobro ya estaba anulado; no requiere otra acción.NoNoSin reproceso manual.
dispute_in_progressLa disputa sigue en curso; todavía no hay actuación monetaria.NoNoSin reproceso manual.
dispute_resolvedSi el dinero vuelve, registra un cobro nuevo; el cobro anulado no se reabre.NoSigue la actuación indicada en esta fila.
test_mode_eventEl payload del proveedor declara modo test; no corresponde una factura real.NoNoSin reproceso manual.
order_event_not_coveredTipo de evento de pedido no admitido; reprocesar el mismo payload no añade soporte.NoNoSin reproceso manual.
store_not_foundNo se resuelve la tienda del evento; revisa la conexión del proveedor y la asociación de tienda.NoNoSin reproceso manual.
store_environment_testLa tienda está configurada como test; es el resultado previsto.NoNoSin reproceso manual.
refund_before_orderLa devolución llegó antes. La gestiona el búfer de orden; no crees un reproceso manual paralelo.NoNoSin reproceso manual.
refund_reason_unmappedElige el motivo de rectificación y emite manualmente.NoSigue la actuación indicada en esta fila.
recurring_invoice_overlapElige una fuente de facturación. Para facturar el pedido, pausa la recurrente solapada antes de reprocesar.Sigue la actuación indicada en esta fila.
series_date_clampedLa factura se emitió con la fecha ajustada a la serie; no hay nada que reprocesar.NoNoSin reproceso manual.
store_autoinvoicing_disabledActiva la facturación automática de la tienda y reprocesa, o factura manualmente; elige una vía.Sigue la actuación indicada en esta fila.
vat_residual_out_of_toleranceLos importes del pedido no cuadran con el cobro; el mismo contenido volvería a fallar.NoNoSin reproceso manual.
simplified_absolute_limit_exceededPide el NIF al comprador y emite una factura completa manualmente.NoSigue la actuación indicada en esta fila.
simplified_threshold_exceeded_without_recipientRevisa el umbral de tienda dentro del límite permitido para tu actividad y reprocesa si corresponde.Sigue la actuación indicada en esta fila.
store_requires_tax_idRevisa la exigencia de NIF de la tienda u obtén el identificador para los próximos pedidos.Sigue la actuación indicada en esta fila.
order_line_amount_exceeds_columnEl importe de línea supera los límites admitidos; el mismo payload no puede reprocesarse con éxito.NoNoSin reproceso manual.
order_status_unknownRevisa el complemento que produce ese estado y factura manualmente cuando corresponda.NoSigue la actuación indicada en esta fila.
refund_not_settledLa devolución no está liquidada; espera el estado definitivo del proveedor.NoNoSin reproceso manual.
protected_customer_data_unavailableNo estaban disponibles los campos del comprador porque Shopify no ha concedido el permiso necesario a la app de Factuarea.NoNo reproceses los campos vacíos guardados. Cuando Shopify conceda el acceso, una nueva lectura del proveedor recuperará los pedidos pendientes. Si un pedido no puede esperar, emite su factura a mano.

Los motivos informativos devuelven recommended_action: null; la API no inventa una actuación cuando no hace falta.

«O una, o la otra» significa una, no las dos. Dos motivos te ofrecen dos salidas — activar el ajuste y reprocesar, o emitir el documento a mano. Son excluyentes. La idempotencia del reproceso va por la identidad del cobro y solo reconoce los documentos emitidos por esa misma vía automática, así que una factura que hayas creado a mano no lo frena. Hacer las dos cosas deja el mismo cobro con dos facturas, cada una numerada en su serie y dada de alta en VeriFactu — un daño fiscal que solo se deshace con una rectificativa.

Avisos: solo lo que puedes arreglar

Un descarte accionable avisa a los administradores de la cuenta. Uno informativo no avisa nunca.

El aviso lleva throttling: si ya existe un aviso sin leer de la misma empresa, la misma pasarela y el mismo motivo dentro de las últimas 24 horas, no se crea un segundo — un webhook mal configurado dispara cientos de eventos idénticos. La condición es sin leer a propósito: una vez lo has leído, si siguen llegando descartes, el siguiente avisa. Eso no es ruido, significa que la incidencia sigue viva.

El parqueo y la ventana de 30 días

Cuando un motivo es parqueable, Factuarea guarda el evento crudo cifrado en reposo, para poder reprocesarlo más tarde. Ese contenido nunca se devuelve por la API — ni en el listado, ni en el detalle. Contiene datos personales de tus clientes finales y datos de pago, y existe para exactamente un propósito: hacer posible el reproceso.

El contenido se purga a los 30 días de parquearse el evento. La fila sobrevive: su motivo, su estado, su fecha y su marca is_parked siguen en tu bandeja indefinidamente, porque el registro de que un cobro no produjo factura es historial que puedes necesitar mucho después de que el contenido caduque.

Un evento que sigue con is_parked: true pero ya no es is_replayable significa exactamente una cosa: la ventana de retención se agotó. La marca se deriva de si el contenido sigue ahí, así que cambia sola el día que corre la purga. Un reproceso intentado después devuelve 422 con el subcódigo integration_event_payload_purged.

El detalle: qué hacer a continuación

El endpoint de detalle devuelve todo lo del listado, más recommended_action: una frase en imperativo con el siguiente paso para ese motivo concreto, o null cuando el motivo es informativo.

curl https://api.factuarea.com/v1/integrations/events/0192f3a4-7b2c-7c1d-9e8f-1a2b3c4d5e6f \
  -H "Authorization: Bearer $FACTUAREA_API_KEY"

La frase distingue a propósito los motivos reproducibles («… y reprocesa el evento») de los que no lo son («… emítela a mano»), de modo que nunca te apunta a una operación que respondería 422.

Un evento de otra empresa y un evento que no existe devuelven el mismo 404 resource_not_found. El endpoint nunca revela si un id existe en otro sitio.

Reprocesar un evento parqueado

Vuelve a procesar un evento de la pasarela que quedó parqueado, una vez que ya no está la causa que le impidió producir su efecto — has reactivado la auto-facturación, has vuelto a vincular la cuenta conectada, ya está disponible el tipo de cambio oficial del día.

Esta acción puede tener consecuencias fiscales reales. Si la causa del descarte ya está resuelta, el reproceso puede emitir una factura real, con su número de serie y su alta en VeriFactu. No es un reintento inocuo: confírmalo con el titular de la cuenta antes de llamarlo. Por eso lleva su propio scope de escritura, integration_events:write, en lugar del scope de lectura de la bandeja — una credencial de solo lectura no debe poder facturar jamás.

curl -X POST https://api.factuarea.com/v1/integrations/events/0192f3a4-7b2c-7c1d-9e8f-1a2b3c4d5e6f/replay \
  -H "Authorization: Bearer $FACTUAREA_API_KEY"

Cuatro propiedades de esta operación importan más que su firma:

  • No duplica facturas. El reproceso pasa por el mismísimo control de idempotencia que el intento original, así que si ese cobro ya produjo una factura, el job se detiene solo y no crea nada.
  • Es asíncrono. 202 significa aceptado y encolado, no completado. El cuerpo devuelve el evento tal como está ahora — su is_replayable sigue siendo true —, no el resultado del reintento. El resultado aparece como un evento nuevo en la bandeja, así que consulta GET /v1/integrations/events para ver cómo acabó.
  • Si la causa sigue presente, el evento se descarta otra vez y se registra de nuevo. Es correcto, y es observable.
  • No admite entrada. Cualquier parámetro de consulta o clave del cuerpo devuelve 400 parameter_unknown en lugar de ignorarse. Enviar uno significa que crees estar configurando algo del reintento — un modo, una serie, una fecha — que esta operación no soporta, y aceptarlo en silencio confirmaría esa expectativa falsa sobre una acción que puede emitir una factura. Un cuerpo vacío, o directamente ningún cuerpo, es el caso normal.

Cuando se rechaza un reproceso

is_replayable: true es el contrato: cuando vale true, el reproceso no responde 422. Es la conjunción de tres condiciones — el evento está parqueado, todavía conserva su contenido y su motivo admite reproceso —, evaluadas en ese mismo orden por el propio handler que guarda el reproceso. Eso es lo que te permite ofrecer un botón de reintento sin adivinar.

Los tres rechazos devuelven 422 business_rule_violation y te dicen cuál es a través del subcode:

subcodeQué significa¿Hay salida?
integration_event_not_parkedEl evento nunca se parqueó — o tuvo éxito, o su motivo no guarda el contenidoNo, y nunca la habrá
integration_event_payload_purgedSe parqueó, pero su contenido se borró al agotarse la ventana de 30 díasNo — resuélvelo a mano
integration_event_reason_not_replayableEstá parqueado y conserva su contenido, pero su motivo volvería a tomar exactamente la misma ramaNo — sigue en su lugar la acción recomendada

En esta página

¿Te echamos una mano?Contactar con soporte