Safata d'esdeveniments d'integració
Per què un cobrament no va acabar en factura — els motius de descart tipats, quins t'avisen, quins aparquen l'esdeveniment perquè el puguis reprocessar, i com funciona la finestra de retenció de 30 dies.
Una passarel·la de pagament envia a Factuarea un esdeveniment per tot el que passa al teu compte: un cobrament amb èxit, una devolució emesa, un cicle de subscripció cobrat, un payout que arriba. La majoria d'aquests esdeveniments produeixen alguna cosa — una factura, una rectificativa, un registre de pagament. Alguns no produeixen res, i quan això passa la pregunta interessant sempre és la mateixa: per què aquest cobrament no va acabar en factura?
La safata d'esdeveniments d'integració la respon. Cada esdeveniment que Factuarea rep queda registrat amb allò que va produir i, quan no va produir res, amb un motiu de descart tipat sortit d'un catàleg tancat. Sense endevinar als logs, sense obrir un tiquet de suport: el motiu és un valor pel qual pots filtrar i, en els motius sobre els quals pots actuar, ve amb el pas següent i, de vegades, amb la possibilitat de reprocessar l'esdeveniment.
La safata és agnòstica de la passarel·la. Registra esdeveniments de qualsevol integració que escrigui historial — incloses les passarel·les que encara no estan alliberades i els esdeveniments històrics d'una que es retiri — perquè amagar aquestes files et deixaria sense explicació per a cobraments que no es van facturar mai.
L'exposen tres endpoints:
| Operació | Endpoint | Scope |
|---|---|---|
| Llistar esdeveniments | GET /v1/integrations/events | integration_events:read |
| Obtenir un esdeveniment | GET /v1/integrations/events/{event} | integration_events:read |
| Reprocessar un esdeveniment aparcat | POST /v1/integrations/events/{event}/replay | integration_events:write |
La mateixa superfície existeix com a tools MCP — list_integrations_events,
get_integrations_event i replay_integrations_event — amb els mateixos scopes.
Recórrer la safata
Del més recent al més antic, acotat a l'empresa autenticada. Paginació per cursor
amb limit (d'1 a 100; 25 per defecte) i 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"
}Tracta next_cursor com a opac: en aquest llistat és una cadena numèrica, no
un UUID v7 com els cursors dels llistats de documents. Torna'l tal qual a
starting_after.
discard_reason_label arriba sempre en castellà, l'idioma de la interfície
del producte, sigui quin sigui l'idioma de la teva integració. Si construeixes un
panell en un altre idioma, basa els teus propis textos en discard_reason —
aquest valor és l'identificador estable i tancat.
Filtres
| Filtre | Valors | Notes |
|---|---|---|
provider | stripe, gocardless, monei, slack, teams, a3, norma43, norma19, ubl | Conjunt tancat |
status | success, skipped, failure | Conjunt tancat |
event_type | text lliure, coincidència exacta, fins a 100 caràcters | No és un enum — vegeu més avall |
discard_reason | un dels vint motius del catàleg | Conjunt tancat |
is_parked | true / false | Vegeu la nota de més avall |
created_at[gte], created_at[lte] | ISO 8601 | Finestra inclusiva |
discard_reason és l'eix tancat; event_type no és un enum. La columna
event_type barreja a propòsit dues convencions: les branques instrumentades més
tard guarden el tipus cru de la passarel·la (charge.refunded), mentre que les
preexistents conserven el seu propi valor semàntic (autoinvoice.*). Busca'l per
coincidència exacta quan sàpigues què persegueixes, però no el modelis mai com un
conjunt tancat — estaries modelant una cosa que la columna no garanteix.
is_parked=false no és el mateix que ometre el paràmetre. El primer exclou
els esdeveniments aparcats; el segon no exclou res.
Un valor fora del seu catàleg retorna 422, i un paràmetre de consulta
desconegut retorna 400 parameter_unknown en comptes d'ignorar-se en silenci
— un filtre que cau sense avisar et lliura una pàgina que creus acotada i no ho
està.
El catàleg de motius de descart
Vint motius tipats, un per cada branca de descart del pipeline de webhooks de les passarel·les. Cadascun declara dues decisions de negoci que no són banderes decoratives:
- Accionable — pot fer-hi alguna cosa el titular del compte? Només els motius accionables avisen. Avisar algú d'un descart que no pot resoldre li ensenya a ignorar la safata, i així és com es perd l'avís que sí que importava.
- Aparcat — reprocessar el mateix contingut podria donar un altre resultat? Només els esdeveniments aparcats guarden el seu contingut xifrat i admeten un reprocessament.
La regla que hi ha darrere de la columna d'aparcament: un esdeveniment s'aparca quan el descart el va causar un estat extern que pots canviar (un ajust apagat, un compte connectat que es va desvincular, una moneda encara sense tipus de canvi). No s'aparca quan la causa és el contingut del mateix esdeveniment (mal format, duplicat, de tipus no cobert, import zero, cicle ja facturat) — reprocessar-lo prendria exactament la mateixa branca i només escriuria una segona fila. D'aquí l'invariant: tot motiu aparcat és accionable, i sis dels nou accionables s'aparquen.
| Motiu | Què el provoca | Accionable | Aparcat | Què fer |
|---|---|---|---|---|
event_not_normalizable | Esdeveniment mal format, o d'un tipus que no es pot interpretar | No | No | Res — no pots arreglar el payload de la passarel·la |
duplicate_redelivery | L'esdeveniment ja es va processar; el seu efecte existeix | No | No | Res — reprocessar-lo seria un no-op per deduplicació |
connected_account_missing | El webhook està mal configurat a la passarel·la: l'esdeveniment no diu a quin compte pertany | Sí | No | Revisa a la passarel·la que el webhook s'envia des del compte que tens vinculat a Factuarea |
connected_account_unknown | El compte existeix a la passarel·la però no està vinculat a Factuarea | Sí | Sí | Torna a vincular aquest compte de la passarel·la i reprocessa l'esdeveniment |
spontaneous_payment_missing_id | El cobrament no porta id, així que no hi ha clau d'idempotència | No | No | Res — reprocessar-lo duplicaria o tornaria a fallar |
autoinvoicing_disabled | L'auto-facturació està desactivada per a aquesta integració | Sí | Sí | Activa l'auto-facturació i reprocessa, o crea la factura a mà — mai les dues coses |
unsupported_currency | El tipus de canvi del Banc Central Europeu del dia encara no està disponible | Sí | Sí | Reprocessa l'esdeveniment més tard, quan el tipus oficial del dia estigui publicat |
refund_without_items | La devolució no porta reemborsaments individuals que rectificar | No | No | Res — no hi ha res a emetre |
refund_autoinvoicing_disabled | La rectificativa automàtica està desactivada per a aquesta integració | Sí | Sí | Activa la rectificativa automàtica i reprocessa, o emet la rectificativa a mà — mai les dues coses |
subscription_missing_invoice_id | El cicle cobrat no té identificador de factura | Sí | No | Crea a mà la factura d'aquest cicle; reprocessar donaria el mateix resultat |
subscription_proration_review | S'ha cobrat un prorrateig solt i exigeix una decisió humana | Sí | No | Comprova l'import del prorrateig a la passarel·la i emet la factura a mà |
subscription_not_a_cycle | La factura de la passarel·la no correspon a un cicle de subscripció facturable | No | No | Res — el descart és correcte |
subscription_trial_skipped | Import zero o negatiu (prova o crèdit): no hi ha base imposable | No | No | Res — no hi ha res a facturar |
subscription_autoinvoicing_disabled | L'auto-facturació de subscripcions està desactivada | Sí | Sí | Activa l'auto-facturació de subscripcions i reprocessa l'esdeveniment |
subscription_already_invoiced | El cicle ja té la seva factura | No | No | Res — reprocessar-lo seria un no-op per idempotència |
payout_missing_id | El payout no porta identificador | No | No | Res — no es pot conciliar ni reprocessar amb seguretat |
payout_connected_account_missing | El compte connectat del payout no està vinculat | Sí | Sí | Vincula el compte connectat i reprocessa l'esdeveniment |
payment_failed | El cobrament ha fallat a la passarel·la | No | No | Res — no hi ha res a emetre ni a reintentar |
event_type_not_covered | Tipus d'esdeveniment fora de l'abast del producte | No | No | Res — reprocessar-lo tornaria a no fer res |
checkout_lines_retrieve_failed | Degradació, no descart: la factura sí que es va emetre, amb una única línia | No | No | Res a reprocessar; revisa les línies de la factura si t'importa el desglossament |
Un motiu sense res a fer ho diu explícitament. Onze dels vint són informatius, i
el contracte no s'inventa una instrucció per a ells: l'endpoint de detall retorna
recommended_action: null en comptes d'una frase fabricada per omplir el camp.
«O l'una, o l'altra» vol dir una, no les dues. Dos motius t'ofereixen dues sortides — activar l'ajust i reprocessar, o emetre el document a mà. Són excloents. La idempotència del reprocessament va per la identitat del cobrament i només reconeix els documents emesos per aquesta mateixa via automàtica, així que una factura que hagis creat a mà no el frena. Fer les dues coses deixa el mateix cobrament amb dues factures, cadascuna numerada a la seva sèrie i donada d'alta a VeriFactu — un dany fiscal que només es desfà amb una factura rectificativa.
Avisos: només el que pots arreglar
Un descart accionable avisa els administradors del compte. Un d'informatiu no avisa mai.
L'avís porta throttling: si ja existeix un avís sense llegir de la mateixa empresa, la mateixa passarel·la i el mateix motiu dins de les últimes 24 hores, no se'n crea un segon — un webhook mal configurat dispara centenars d'esdeveniments idèntics. La condició és sense llegir a propòsit: un cop l'has llegit, si continuen arribant descarts, el següent sí que avisa. Això no és soroll, vol dir que la incidència segueix viva.
L'aparcament i la finestra de 30 dies
Quan un motiu és aparcable, Factuarea guarda l'esdeveniment cru xifrat en repòs, per poder reprocessar-lo més tard. Aquest contingut no es retorna mai per l'API — ni al llistat, ni al detall. Conté dades personals dels teus clients finals i dades de pagament, i existeix per a exactament un propòsit: fer possible el reprocessament.
El contingut es purga als 30 dies d'aparcar-se l'esdeveniment. La fila
sobreviu: el seu motiu, el seu estat, la seva data i la seva marca is_parked
segueixen a la teva safata indefinidament, perquè el registre que un cobrament no
va produir factura és historial que pots necessitar molt després que el contingut
caduqui.
Un esdeveniment que segueix amb is_parked: true però ja no és
is_replayable vol dir exactament una cosa: la finestra de retenció s'ha
esgotat. La marca es deriva de si el contingut encara hi és, així que canvia
sola el dia que corre la purga. Un reprocessament intentat després retorna 422
amb el subcodi integration_event_payload_purged.
El detall: què fer a continuació
L'endpoint de detall retorna tot el del llistat, més recommended_action: una
frase en imperatiu amb el pas següent per a aquell motiu concret, o null quan el
motiu és informatiu.
curl https://api.factuarea.com/v1/integrations/events/0192f3a4-7b2c-7c1d-9e8f-1a2b3c4d5e6f \
-H "Authorization: Bearer $FACTUAREA_API_KEY"La frase distingeix a propòsit els motius reproduïbles («… i reprocessa l'esdeveniment») dels que no ho són («… emet-la a mà»), de manera que mai no t'apunta a una operació que respondria 422.
Un esdeveniment d'una altra empresa i un esdeveniment que no existeix retornen el
mateix 404 resource_not_found. L'endpoint no revela mai si un id existeix en
un altre lloc.
Reprocessar un esdeveniment aparcat
Torna a processar un esdeveniment de la passarel·la que va quedar aparcat, un cop ja no hi és la causa que li va impedir produir el seu efecte — has tornat a activar l'auto-facturació, has tornat a vincular el compte connectat, ja hi ha disponible el tipus de canvi oficial del dia.
Aquesta acció pot tenir conseqüències fiscals reals. Si la causa del descart
ja està resolta, el reprocessament pot emetre una factura real, amb el seu
número de sèrie i la seva alta a VeriFactu. No és un reintent innocu:
confirma-ho amb el titular del compte abans de cridar-lo. Per això porta el seu
propi scope d'escriptura, integration_events:write, en comptes del scope de
lectura de la safata — una credencial de només lectura no ha de poder facturar
mai.
curl -X POST https://api.factuarea.com/v1/integrations/events/0192f3a4-7b2c-7c1d-9e8f-1a2b3c4d5e6f/replay \
-H "Authorization: Bearer $FACTUAREA_API_KEY"Quatre propietats d'aquesta operació importen més que la seva firma:
- No duplica factures. El reprocessament passa pel mateixíssim control d'idempotència que l'intent original, així que si aquell cobrament ja va produir una factura, el job s'atura sol i no crea res.
- És asíncron.
202vol dir acceptat i encuat, no completat. El cos retorna l'esdeveniment tal com està ara — el seuis_replayablesegueix senttrue—, no el resultat del reintent. El resultat apareix com un esdeveniment nou a la safata, així que consultaGET /v1/integrations/eventsper veure com ha acabat. - Si la causa segueix present, l'esdeveniment es descarta un altre cop i es registra de nou. És correcte, i és observable.
- No admet entrada. Qualsevol paràmetre de consulta o clau del cos retorna
400
parameter_unknownen comptes d'ignorar-se. Enviar-ne un vol dir que et penses que estàs configurant alguna cosa del reintent — un mode, una sèrie, una data — que aquesta operació no suporta, i acceptar-ho en silenci confirmaria aquella expectativa falsa sobre una acció que pot emetre una factura. Un cos buit, o directament cap cos, és el cas normal.
Quan es rebutja un reprocessament
is_replayable: true és el contracte: quan val true, el reprocessament no
respon 422. És la conjunció de tres condicions — l'esdeveniment està aparcat,
encara conserva el seu contingut i el seu motiu admet reprocessament —, avaluades
en aquest mateix ordre pel mateix handler que guarda el reprocessament. Això és el
que et permet oferir un botó de reintent sense endevinar.
Els tres rebutjos retornen 422 business_rule_violation i et diuen quin és a
través del subcode:
subcode | Què significa | Hi ha sortida? |
|---|---|---|
integration_event_not_parked | L'esdeveniment no es va aparcar mai — o va tenir èxit, o el seu motiu no guarda el contingut | No, i mai no n'hi haurà |
integration_event_payload_purged | Es va aparcar, però el seu contingut es va esborrar en esgotar-se la finestra de 30 dies | No — resol-ho a mà |
integration_event_reason_not_replayable | Està aparcat i conserva el seu contingut, però el seu motiu tornaria a prendre exactament la mateixa branca | No — segueix en el seu lloc l'acció recomanada |
On encaixa això
- Auto-facturació amb Stripe — el flux que
produeix la majoria dels esdeveniments que trobaràs aquí, inclosos els
cicles de subscripció, l'ajust
dels quals és darrere de
subscription_autoinvoicing_disabled. - Payouts i conciliació bancària — la ingesta
de payouts que hi ha darrere de
payout_missing_idipayout_connected_account_missing. - Mode de prova i sandbox — valida el teu tractament de la
safata amb una clau
fact_test_abans de connectar un botó de reprocessament a una credencial de producció. - Gestió d'errors — l'envelope de les respostes 400, 404 i 422 citades més amunt.