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 (DRAFT → ISSUED → PAID …). 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 VOIDEDSubscribe 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_status | What it means | Terminal | What to do |
|---|---|---|---|
PENDING | Queued or submitted; AEAT has not answered yet. | No — retried automatically | Wait — usually seconds. BeeL. retries on its own. |
ACCEPTED | Accepted by AEAT, with or without non-blocking remarks. | Yes | Nothing. |
VOIDED | A cancellation record (registro de anulación) was accepted by AEAT. | Yes | Nothing. |
REJECTED | Rejected by AEAT, or the record never made it there. | Yes | Issue a corrective, or void and reissue. |
NOT_SUBMITTED | Yes |
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: falseis 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 nosubmission_statusto inspect in that case.invoice_hashandchaining_hashare not interchangeable. The first fingerprints this invoice; the second links this registro to the previous one in your chain. A broken chain is achaining_hashproblem and is never fixed by reissuing one invoice.error_code/error_messageare 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. Readsubmission_statusfirst.
What triggers each transition
| Event | Effect on verifactu.submission_status |
|---|---|
| You issue an invoice and auto-submit applies | (none) → PENDING |
| AEAT completes registration | PENDING → ACCEPTED or REJECTED |
| AEAT returns a transient server error | PENDING → (retry) → PENDING |
| BeeL. exhausts its retries and gives up | PENDING → REJECTED (the record never reached AEAT) |
| You issue a corrective on an accepted invoice | New corrective enters its own PENDING cycle; the original stays ACCEPTED (the commercial status flips to RECTIFIED / VOIDED) |
| You call the void endpoint | A 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}/voidfollowed by a newPOST /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:
| Code | Meaning | What to do |
|---|---|---|
1101 | NIF emisor not registered for VeriFactu | Activate VeriFactu in your AEAT census |
3001 | NIF receptor not found in census | Validate via NIF validation before issuing |
4101 | Importe total mismatch | Lines don't sum to importe_total — BeeL. normally prevents this |
4106 | Simplificada over recommended threshold | Switch to F1 |
5104 | Chain integrity error | Internal — open a support ticket |
2001 | Duplicate registro | No 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.
Related
- Auto-submit policy — when BeeL. submits in the first place
- Cancel vs amend — what to do when AEAT rejects
- Corrective invoices — issuing R1–R5