Events
Read-only event objects with an opaque id. Historical query via the API and webhook delivery.
Every event published in Factuarea is persisted as a read-only
event object with an opaque id (a UUID v7, e.g.
01931b3e-7c4a-7f2e-9a8b-3c5d6e7f8a0d), consistent with the id of
every other v1 resource. This lets you:
- Query it via API:
GET /v1/events/{id}andGET /v1/events?type=invoice.paid. - Deliver it to subscribed webhook endpoints (the same object is sent in the delivery body — see Webhooks).
- Replay a delivery from the dashboard
(
Developers > Webhooks > Deliveries).
Payload shape
Every event shares this structure:
{
"id": "01931b3e-7c4a-7f2e-9a8b-3c5d6e7f8a0d",
"object": "event",
"type": "invoice.paid",
"aggregate_id": "01931b3e-7c4a-7f2e-9a8b-3c5d6e7f8a03",
"api_version": "2026-05-22",
"livemode": true,
"data": {
"invoice": { "id": "01931b3e-7c4a-7f2e-9a8b-3c5d6e7f8a03" }
},
"created": "2026-05-15T10:23:18Z"
}Fields:
id— opaque identifier of the event (UUID v7). Use it as the idempotency key on your side.object— alwaysevent.type— event name as<category>.<action>(e.g.invoice.paid,quote.approved).aggregate_id— UUID v7 of the resource that produced the event (e.g. the invoice forinvoice.paid).nullfor events without a backfilled aggregate. Distinct fromid, which identifies the event itself.api_version— date-based version the payload was serialized under, sealed at emission. Always present on events emitted today;nullonly for legacy events emitted before versions were sealed.livemode—truefor events generated in production (live key,fact_live_);falsefor events generated in test mode (sandbox company,fact_test_key). Test-mode events are recorded and queryable viaGET /v1/events, but not delivered to webhook endpoints (see Test mode & sandbox), so any event your endpoint actually receives is alwayslivemode: true.data— a thin reference to the affected resource, keyed by its type — e.g.{ "invoice": { "id": "..." } }. Fetch the resource from its own endpoint to get the full, current representation.created— ISO 8601 UTC timestamp of when the event was created.
Idempotency
Each event has a unique id. Webhooks redeliver the same id to the
same endpoint on every retry. In your handler:
event_id = event['id']
if seen_in_db(event_id):
return '', 200
process(event)
mark_seen_in_db(event_id)Event catalog
The full, authoritative catalog of subscribable event types is returned
by GET /v1/event-catalog. Each entry carries a name, a category, a
human-readable description, and a status (available or
coming_soon):
curl https://api.factuarea.com/v1/event-catalog \
-H "Authorization: Bearer $FACTUAREA_API_KEY"{
"data": [
{
"name": "invoice.paid",
"category": "invoice",
"description": "Factura pagada",
"status": "available"
}
],
"has_more": false,
"next_cursor": null
}Representative event types by category (query the catalog for the complete, up-to-date list):
Invoices
invoice.created, invoice.auto_created,
invoice.corrective_auto_created, invoice.subscription_auto_created,
invoice.updated, invoice.sent, invoice.paid,
invoice.cancelled, invoice.annulled, invoice.overdue,
invoice.deleted, invoice.number_assigned, invoice.rectified,
invoice.email_sent, invoice.email_failed,
invoice.payment_reminder_sent, invoice.simplified_created,
invoice.simplified_substituted, invoice.substituted_by_complete,
invoice.verifactu_submitted, invoice.verifactu_failed,
invoice.metadata_changed.
invoice.auto_created / invoice.corrective_auto_created /
invoice.subscription_auto_created are emitted by the
payment-gateway auto-invoicing flows when a
charge, refund or subscription cycle produces an invoice automatically.
Quotes
quote.created, quote.updated, quote.deleted, quote.approved,
quote.rejected, quote.converted, quote.expired,
quote.marked_as_pending, quote.cancelled, quote.number_assigned,
quote.metadata_changed, quote.email_sent, quote.email_failed.
Pro-forma invoices
proforma.created, proforma.updated, proforma.deleted,
proforma.accepted, proforma.rejected, proforma.cancelled,
proforma.expired, proforma.converted_to_invoice,
proforma.number_assigned, proforma.metadata_changed,
proforma.email_sent, proforma.email_failed.
Delivery notes
delivery_note.created, delivery_note.updated,
delivery_note.status_changed, delivery_note.signed,
delivery_note.converted, delivery_note.email_sent,
delivery_note.email_failed.
Purchase invoices
purchase_invoice.created, purchase_invoice.updated,
purchase_invoice.paid, purchase_invoice.payment_registered,
purchase_invoice.cancelled, purchase_invoice.metadata_changed.
Recurring invoices
recurring_invoice.created, recurring_invoice.activated,
recurring_invoice.paused, recurring_invoice.updated,
recurring_invoice.deleted, recurring_invoice.completed,
recurring_invoice.executed, recurring_invoice.failed,
recurring_invoice.cancelled, recurring_invoice.metadata_changed.
Clients & products
client.created, client.updated, client.deleted,
client.metadata_changed, product.created, product.updated.
Series & taxes
series.created, series.updated, series.deleted,
series.archived, series.unarchived, series.marked_as_default,
series.demoted_from_default, series.number_consumed,
series.year_reset, series.month_reset, tax.metadata_changed,
tax.validity_changed, tax.external_reference_changed.
FacturaE (FACe)
facturae.face_submitted, facturae.face_status_changed,
facturae.face_cancellation_requested.
Payments & gateways
payment.received, payment.reversed, payout.reconciled.
payment.received and payment.reversed are the two halves of one
cycle: the first fires when a payment is registered against an invoice,
the second when that payment is voided and the invoice goes back into the
collection circuit. Subscribe to both — an invoice you recorded as
collected can stop being collected, and reacting only to the first half
means trusting a figure that expired. See
payment.reversed below.
payout.reconciled fires when a Stripe payout is reconciled against your bank
statement (see Payouts & reconciliation).
Automations
automation_rule.activated, automation_rule.paused,
automation_rule.auto_paused, automation_run.started,
automation_run.completed, automation_run.failed,
automation_run.step_dead_lettered.
The engine reports its own life cycle. The three automation_rule.* events
carry the rule in the delivery body, under data.object: activated when a
rule goes live and starts listening to its trigger, paused when a person stops
it, and auto_paused when the platform stops it after a streak of failed runs.
The four automation_run.* events carry the run in the delivery body, under
data.object: started when a matched trigger clears the engine limits and the
run is admitted,
completed when every step applied its effect, failed when the run finished
without producing its effect and cannot be reprocessed, and
step_dead_lettered when a single step is parked awaiting a manual replay. As
everywhere else, data.object is the snapshot taken when the event was
emitted, not the current state of the resource. See the
run reference.
Two of them carry extra keys beside the snapshot.
automation_rule.auto_paused adds consecutive_failures, the number of
consecutive failed runs that tripped the automatic pause — the platform paused
the rule, and automation_rule.paused is its human counterpart.
automation_run.step_dead_lettered adds step_index and discard_reason:
step_index is the zero-based position of the parked step, resolved against
the snapshot itself (the element of data.object.steps whose step_index
matches, because a step is not a public resource of its own), and
discard_reason is a typed value from a closed catalog, never free-form text.
In practice it is always a replayable reason: an outcome that does not admit
reprocessing closes the run as failed instead of parking the step.
These seven names live in the same closed catalog as every other event, so they
are valid rule triggers too: an automation can react to
automation_run.failed exactly as it reacts to invoice.paid, and no
exclusion list hides them from the trigger catalog. Like the rest of the
engine, they are gated by the automations module.
Chaining them is bounded, not forbidden. Every hop "action → event → rule"
consumes the engine's chain-depth cap — three hops by default — so a rule that
reacts to automation_run.failed and fails again cannot loop forever: once the
cap is spent the run is not executed, it is recorded as blocked. An event
produced by a person or by an integration enters at depth zero, so only what an
automation itself caused spends that budget.
Payload examples
invoice.paid
{
"id": "01931b3e-7c4a-7f2e-9a8b-3c5d6e7f8a0d",
"object": "event",
"type": "invoice.paid",
"aggregate_id": "01931b3e-7c4a-7f2e-9a8b-3c5d6e7f8a03",
"api_version": "2026-05-22",
"livemode": true,
"data": {
"invoice": { "id": "01931b3e-7c4a-7f2e-9a8b-3c5d6e7f8a03" }
},
"created": "2026-05-15T11:42:08Z"
}client.updated
{
"id": "01931b3e-7c4a-7f2e-9a8b-3c5d6e7f8a1a",
"object": "event",
"type": "client.updated",
"aggregate_id": "01931b3e-7c4a-7f2e-9a8b-3c5d6e7f8a2b",
"api_version": "2026-05-22",
"livemode": true,
"data": {
"client": { "id": "01931b3e-7c4a-7f2e-9a8b-3c5d6e7f8a2b" }
},
"created": "2026-05-15T11:50:12Z"
}quote.converted
{
"id": "01931b3e-7c4a-7f2e-9a8b-3c5d6e7f8a3c",
"object": "event",
"type": "quote.converted",
"aggregate_id": "01931b3e-7c4a-7f2e-9a8b-3c5d6e7f8a4d",
"api_version": "2026-05-22",
"livemode": true,
"data": {
"quote": { "id": "01931b3e-7c4a-7f2e-9a8b-3c5d6e7f8a4d" }
},
"created": "2026-05-15T12:01:55Z"
}The event carries only a thin reference to the affected resource. Fetch
the resource from its own endpoint (e.g. GET /v1/quotes/{id}) to read
the converted invoice it links to.
payment.reversed
Emitted when a payment registered against an invoice is voided. Until it
existed, an integration that had heard payment.received had no way
of finding out that the same payment had come undone.
The delivery body of this event carries the affected resource in full
under data.object — the payment as the v1 API serialises it, already
flagged as reverted — plus a data.reversal block describing the
operation itself:
{
"id": "01931b3e-7c4a-7f2e-9a8b-3c5d6e7f8a5e",
"type": "payment.reversed",
"api_version": "2026-05-22",
"created": 1780646100,
"livemode": true,
"test": false,
"correlation_id": "01931b3e-7c4a-7f2e-9a8b-3c5d6e7f8a6f",
"data": {
"type": "payment.reversed",
"object": {
"id": "01931b3e-7c4a-7f2e-9a8b-3c5d6e7f8a0b",
"object": "payment",
"invoice_id": "01931b3e-7c4a-7f2e-9a8b-3c5d6e7f8a01",
"amount": 500.00,
"payment_date": "2026-05-20",
"payment_method": "direct_debit",
"payment_method_text": "Domiciliación bancaria",
"reference": "TRF-2026-0042",
"notes": null,
"is_reversed": true,
"reversed_at": "2026-06-02T08:15:00Z",
"reversal_reason": "direct_debit_return",
"reversal_reason_text": "Devolución de adeudo SEPA",
"reversal_note": "Devuelto por el banco con motivo MD01.",
"created_at": "2026-05-20T10:30:00Z",
"updated_at": "2026-06-02T08:15:00Z"
},
"reversal": {
"reason": "direct_debit_return",
"origin": "gateway"
}
}
}data.reversal.reason— one of the five values of the closed catalog:direct_debit_return,card_dispute,misapplied_payment,bounced_effect,recording_error. Branch on this, never onreversal_reason_text, which is a Spanish label for humans.data.reversal.origin—gatewaywhen the payment provider reported a return, dispute or chargeback (money already moved at the bank);manualwhen a person recorded it. Useful to decide whether to chase the customer or wait for the bank file.
What to do when it arrives: the invoice is back in the collection circuit
(sent, or overdue if its due date has passed) and its paid_amount
no longer includes this entry, so refresh whatever you cached about that
invoice. Do not issue a corrective invoice as a reaction — a
reversal is not a reduction of revenue. See
Reversal or corrective invoice?.
Subscribe to events
Via API:
curl -X POST https://api.factuarea.com/v1/webhook_endpoints \
-H "Authorization: Bearer $FACTUAREA_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://app.mycompany.com/factuarea/webhook",
"enabled_events": ["invoice.paid", "quote.approved"]
}'To subscribe to all events (not recommended in production except for internal dashboards):
{ "enabled_events": ["*"] }To subscribe to entire families (all invoice.*):
{ "enabled_events": ["invoice.*", "quote.*"] }List events via API
GET /v1/events?type=invoice.paid&limit=50Available filters: type, type[in], created[gte], created[lte],
created[gt], created[lt]. Standard cursor pagination
(limit, starting_after, ending_before) — see
Pagination.
Order events
Subscribe to order.invoiced and order.refunded to receive full invoice or corrective-invoice snapshots under data.object. Both include data.store.uuid, data.store.provider and data.order.external_id; refunds also include data.refund.scope and data.refund.reason_code. Use the event id for delivery deduplication and correlation_id for related operations. See store processing and the n8n trigger.
if (event.type === "order.invoiced" || event.type === "order.refunded") {
const invoice = event.data.object;
const storeId = event.data.store.uuid;
const orderId = event.data.order.external_id;
await saveOrderDocument({ eventId: event.id, storeId, orderId, invoice });
}