Factuarea APIDevelopers

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} and GET /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 — always event.
  • 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 for invoice.paid). null for events without a backfilled aggregate. Distinct from id, which identifies the event itself.
  • api_version — date-based version the payload was serialized under, sealed at emission. Always present on events emitted today; null only for legacy events emitted before versions were sealed.
  • livemodetrue for events generated in production (live key, fact_live_); false for events generated in test mode (sandbox company, fact_test_ key). Test-mode events are recorded and queryable via GET /v1/events, but not delivered to webhook endpoints (see Test mode & sandbox), so any event your endpoint actually receives is always livemode: 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 on reversal_reason_text, which is a Spanish label for humans.
  • data.reversal.origingateway when the payment provider reported a return, dispute or chargeback (money already moved at the bank); manual when 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=50

Available 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 });
}

On this page

Need a hand?Contact support