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 acknowledges receipt
SENT                              SENT
  ↓ pay / overdue                       ↓ AEAT registers
PAID / OVERDUE                    ACCEPTED   or   REJECTED
  ↓ correct                                       ↓ on void
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. Valid values on the response:

verifactu.submission_statusWhat it meansWhat to do
PENDINGSubmission queued / in-flight to AEAT.Wait — usually seconds.
SENTAEAT acknowledged receipt; awaiting final registry decision.Wait.
ACCEPTEDAEAT registered the invoice.Nothing.
REJECTEDAEAT refused the registration.Issue a corrective or void + reissue — see Cancel vs amend.
VOIDEDThe invoice was cancelled (registro de anulación accepted by AEAT).Nothing.

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 endpoint accepts a wider enum. When filtering invoices with GET /invoices?verifactu_status=..., the values are NO_VERIFACTU, PENDING, ACCEPTED, REJECTED (a simplified four-bucket view that maps the response statuses to user-actionable filters).

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",
    "registration_date": "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
  }
}
FieldMeaning
enabledWhether VeriFactu applies to this invoice. false for drafts and invoices issued under skipped auto-submit.
submission_statusThe lifecycle state (table above).
registration_numberAEAT's unique identifier for the registro (Spanish: código de registro).
registration_dateWhen AEAT acknowledged the submission.
invoice_hashSHA-256 fingerprint of this invoice.
chaining_hashSHA-256 that links this registro to the previous one in your chain.
qr_url / qr_base64AEAT-mandated QR code on the printed invoice.
error_code / error_messageSet when submission_status is REJECTED or for accepted-with-warnings responses. Single values, not arrays.

What triggers each transition

EventEffect on verifactu.submission_status
You issue an invoice and auto-submit applies(none) → PENDING
AEAT acknowledges receiptPENDINGSENT
AEAT completes registrationSENTACCEPTED or REJECTED
AEAT returns a transient server errorPENDING → (retry) → PENDING
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 original issuance didn't reach AEAT's registry. 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.
  • Non-fiscal metadata error (typo in descripcion, wrong serie code) — BeeL handles subsanación for you on rejection: no new invoice required.
  • Fiscal data error (wrong totals, wrong NIF, wrong customer) — issue a corrective (POST /invoices/{id}/corrective) to fix the data, or void + reissue (POST /invoices/{id}/void followed by a new POST /invoices).
  • 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 a narrower enum. previous_status and new_status are limited to PENDING, ACCEPTED, REJECTED — the intermediate SENT and VOIDED aren't surfaced through webhooks. If you need to react to those, poll the invoice resource.

See Webhook events for the full payload.