NewThree filters returned the wrong rows
BeeL
Get StartedMulti-NIFVeriFactuStripeAPI ReferenceChangelog

Submission states

Every state a VeriFactu submission can be in, what triggers transitions, and when you need to act.


A VeriFactu submission has its own lifecycle, separate from the invoice's commercial state (DRAFTISSUEDPAID …). The invoice carries a verifactu object on the response that holds the submission state and AEAT response — this page is the canonical map.

Invoice status (commercial) vs VeriFactu status (fiscal)

These are two different state machines on the same invoice:

COMMERCIAL  (status)              FISCAL  (verifactu.submission_status)
─────────────                     ─────────────────────
DRAFT                              (none — verifactu.enabled = false)
  ↓ issue                            ↓ submission queued
ISSUED          ── submission ──> PENDING
  ↓ send                               ↓ AEAT registers (or never answers)
SENT                              ACCEPTED   or   REJECTED
  ↓ pay / overdue
PAID / OVERDUE
  ↓ correct / void                   ↓ on void (registro de anulación)
RECTIFIED / VOIDED                VOIDED

Subscribe to verifactu.status.updated to be notified of fiscal-status changes — see Webhook events.

The fiscal states

The invoice response exposes a nested verifactu object. Its submission_status field carries the current state. The vocabulary has exactly four values:

verifactu.submission_statusWhat it meansTerminalWhat to do
PENDINGQueued or submitted; AEAT has not answered yet.No — retried automaticallyWait — usually seconds. BeeL. retries on its own.
ACCEPTEDAccepted by AEAT, with or without non-blocking remarks.YesNothing.
VOIDEDA cancellation record (registro de anulación) was accepted by AEAT.YesNothing.
REJECTEDRejected by AEAT, or the record never made it there.YesIssue a corrective, or void and reissue.
NOT_SUBMITTEDYes

If the invoice was never submitted (verifactu.enabled is false), the submission_status is absent. This happens for drafts, for invoices issued when auto-submit was off, or when VeriFactu isn't configured for the account — see Auto-submit policy.

The list filter uses the very same four values. GET /invoices?verifactu_status=... accepts PENDING, ACCEPTED, VOIDED, REJECTED — a value read from an invoice can be fed straight back into the filter. Only invoices with VeriFactu enabled can match; to select the ones outside the axis (no VeriFactu record at all) use ?verifactu_enabled=false instead. There is no NO_VERIFACTU value: sending it returns 422.

Non-terminal states are retried automatically

States that represent in-flight or transient conditions (PENDING, transient AEAT errors) are retried by BeeL. on a backoff. There's no public API endpoint to force a resubmission — for transient AEAT errors BeeL. retries on its own; for permanent rejections (REJECTED) you fix the underlying data via a corrective or void + reissue. The dashboard exposes a manual resubmit action for the edge cases where BeeL. stops retrying on its own.

Reading the AEAT response

Every submission stores the AEAT response on the invoice resource under the verifactu object. The relevant fields:

{
  "id": "...",
  "invoice_number": "INV-2026/00042",
  "status": "ISSUED",
  "verifactu": {
    "enabled": true,
    "submission_status": "ACCEPTED",
    "registration_number": "EJEMPLOSI20260001CR1234567890",
    "registered_at": "2026-05-18T10:24:36Z",
    "invoice_hash": "F8B2A3...",
    "chaining_hash": "A1C4E7...",
    "qr_url": "https://aeat.es/verifactu?id=...",
    "qr_base64": "data:image/png;base64,iVBOR...",
    "error_code": null,
    "error_message": null
  }
}

The block is part of the invoice resource, so its fields are documented with it. Four of them are routinely misread:

  • enabled: false is not a failure. It means VeriFactu never applied to this invoice — a draft, an invoice issued while auto-submit was off, or an unconfigured account. There is no submission_status to inspect in that case.
  • invoice_hash and chaining_hash are not interchangeable. The first fingerprints this invoice; the second links this registro to the previous one in your chain. A broken chain is a chaining_hash problem and is never fixed by reissuing one invoice.
  • error_code / error_message are single values, not arrays, and they are also populated on submissions AEAT accepted with remarks — their presence does not by itself mean the invoice was rejected. Read submission_status first.

What triggers each transition

EventEffect on verifactu.submission_status
You issue an invoice and auto-submit applies(none) → PENDING
AEAT completes registrationPENDINGACCEPTED or REJECTED
AEAT returns a transient server errorPENDING → (retry) → PENDING
BeeL. exhausts its retries and gives upPENDINGREJECTED (the record never reached AEAT)
You issue a corrective on an accepted invoiceNew corrective enters its own PENDING cycle; the original stays ACCEPTED (the commercial status flips to RECTIFIED / VOIDED)
You call the void endpointA registro de anulación enters PENDING until AEAT acknowledges, then → VOIDED

REJECTED is the only state that requires you to act. It means the invoice is not in AEAT's registry — either because AEAT refused it, or because the submission was abandoned after exhausting retries. The contract does not distinguish the two: an error_code / error_message coming from AEAT points at a refusal, their absence at a submission that never landed. Fix the underlying problem (often a NIF mismatch, malformed amounts, or a chain-integrity issue), then issue a corrective or void + reissue.

Recovering from a rejection

There's no public API endpoint to retry a submission. Recovery paths depend on the reason:

  • Transient AEAT errors — BeeL. retries on its own with backoff. You don't need to do anything.
  • Any error in the invoice's own data (wrong totals, wrong descripcion, wrong receptor NIF, wrong serie code, or any other content of the invoice) — issue a corrective (POST /invoices/{id}/corrective) to supersede the data, or void + reissue (POST /invoices/{id}/void followed by a new POST /invoices). A registered invoice is immutable, so BeeL. does not silently edit and resubmit it; the fix always produces a new fiscal document. Subsanación (resubmitting the same, unchanged record) cannot correct invoice content — it only ever addresses causes external to the invoice's data, such as an issuing NIF that was not yet censado or a representation that was not signed at submission time.
  • Edge cases where BeeL. stops retrying — the dashboard exposes a manual resubmit action.

See Cancel vs amend for the full decision.

Common AEAT error codes you'll see

AEAT returns a single error_code + error_message on REJECTED submissions. Common codes:

CodeMeaningWhat to do
1101NIF emisor not registered for VeriFactuActivate VeriFactu in your AEAT census
3001NIF receptor not found in censusValidate via NIF validation before issuing
4101Importe total mismatchLines don't sum to importe_total — BeeL. normally prevents this
4106Simplificada over recommended thresholdSwitch to F1
5104Chain integrity errorInternal — open a support ticket
2001Duplicate registroNo action needed — AEAT already has the record

The full code list is in the AEAT VeriFactu documentation; BeeL. surfaces them on invoice.verifactu.error_code and invoice.verifactu.error_message.

Webhooks for fiscal status changes

Subscribe to verifactu.status.updated to be notified of every transition:

{
  "type": "verifactu.status.updated",
  "data": {
    "invoice_id": "...",
    "invoice_number": "INV-2026/00042",
    "verifactu_registration_id": "...",
    "previous_status": "PENDING",
    "new_status": "ACCEPTED",
    "qr_url": "https://aeat.es/verifactu?id=...",
    "qr_base64": "data:image/png;base64,iVBOR...",
    "invoice_hash": "F8B2A3...",
    "error_code": null,
    "error_message": null
  }
}

Webhook payloads use the same enum. previous_status and new_status are typed as VeriFactuSubmissionStatus, so they carry any of the four values — PENDING, ACCEPTED, VOIDED, REJECTED. No polling needed to see a cancellation land.

See Webhook events for the full payload.