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 sent
ISSUED ── submission ──> PENDING
↓ send ↓ AEAT answers (or the record never gets there)
SENT ACCEPTED or REJECTED
↓ pay
PAID
↓ correct / void ↓ on void (cancellation record)
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 five values:
| verifactu.submission_status | What it means | Terminal | What to do |
|---|---|---|---|
PENDING | Submitted; AEAT has not answered yet. A temporary AEAT server error also stays PENDING while BeeL. retries it. | No — retried automatically | Wait — usually seconds. BeeL. keeps asking AEAT on its own. |
ACCEPTED | Accepted by AEAT. With an error_code alongside, it was accepted with errors: registered, but AEAT flagged some data. | Yes | Nothing — unless error_code is set: then review the flagged data and, if it is wrong, issue a corrective. |
VOIDED | A cancellation record (registro de anulación) was accepted by AEAT. | Yes | Nothing. |
REJECTED | Not in AEAT's registry: AEAT refused it, the submission was refused before reaching AEAT, or BeeL. gave up waiting (including when the retries after a temporary AEAT error ran out). | Yes | Read error_code (AEAT's code, or null when AEAT gave none). Fix the data with a corrective (void only an invoice that should never have been issued); for a cause outside the invoice, contact BeeL. |
NOT_SUBMITTED | Issued with VeriFactu enabled but with no live record: AEAT does not know the invoice exists. | No — but not retried | Right after issuing it can still be in flight, so re-read it. If it persists, contact BeeL. with the invoice ID. |
If there is no submission to describe yet, the submission_status is absent: drafts, scheduled invoices, proformas, and invoices whose verifactu.enabled is false (those are outside this axis entirely) — see Auto-submit policy. An issued invoice with verifactu.enabled: true always reports a state: if no live record exists — the registration fell through and AEAT does not know the invoice exists — it reports NOT_SUBMITTED, which is transient right after issuing while the submission is still in flight.
The list filter uses the very same vocabulary. GET /invoices?verifactu_status=... accepts PENDING, ACCEPTED, VOIDED, REJECTED and NOT_SUBMITTED — 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.
What moves on its own, and what does not
While a record is PENDING, BeeL. keeps asking AEAT for the outcome, on a backoff. A temporary AEAT server error keeps the record PENDING, without error_code or error_message, while BeeL. retries it; it becomes REJECTED only if the retries run out. Once AEAT gives a real verdict, BeeL. stops asking, and nothing resubmits the record for you. What to do then is in Handling AEAT rejections.
Is ACCEPTED final?
Yes. Once a record reads ACCEPTED — with or without an error_code — BeeL. does not ask AEAT about it again, and nothing in BeeL. can move it back to PENDING or on to REJECTED. There is no late rejection to wait for, so there is no later verifactu.status.updated turning an accepted invoice into a rejected one.
What can still happen to an accepted invoice is something you do, and it arrives as its own record with its own status:
- you void it: a cancellation record is sent, and the invoice's
submission_statusbecomesVOIDEDonce AEAT accepts it — see Voiding; - you issue a corrective: the corrective has its own registration, and the original stays
ACCEPTED.
What this means for reconciliation: you never need to re-check accepted invoices. The window worth sweeping is the one where a state can still change or still needs you — invoices PENDING, REJECTED or NOT_SUBMITTED. A sweep over the last few days, run daily, covers an outage of your own webhook endpoint; see Reconcile, do not only listen and pace it with Rate limits.
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": "094365f5-9637-40e9-851a-4e964a939b0b",
"registered_at": "2026-05-18T10:24:36Z",
"invoice_hash": "1863E77D540083E4259F4AF44322E16B962CB5D72C2F949AB2B30FA83E93FF84",
"qr_url": "https://www2.agenciatributaria.gob.es/wlpl/TIKE-CONT/ValidarQR?nif=B27534239&numserie=INV-2026%2F00042&fecha=18-05-2026&importe=121.00",
"qr_base64": "iVBORw0KGgoAAAANSUhEUgAAAMgAAADI...",
"skip_reason": null
}
}The block is part of the invoice resource, so its fields are documented with it. Several of them are routinely misread:
enabled: falseis not a failure. It means VeriFactu never applied to this invoice: it was issued by a NIF outside the VeriFactu regime. There is nosubmission_statusto inspect in that case.registration_numberis a BeeL. identifier, not a code issued by AEAT. It identifies the submitted record, and staysnullwhen the record never got that far.registered_atis when the record was submitted, not when AEAT accepted it.invoice_hash,qr_urlandqr_base64are there fromPENDING, and their presence does not mean AEAT accepted anything — see QR code and the invoice PDF.invoice_hashfingerprints this invoice, not the chain. It is the hash of this record as submitted. The link to the previous record is kept on the submission record and is not published on the invoice resource, so chain integrity is not something you can verify from the API.error_code/error_messageare single values, not arrays, and they are also populated on submissions AEAT accepted with errors — their presence does not by itself mean the invoice was rejected. Readsubmission_statusfirst.error_codeis AEAT's code when AEAT gave one;error_messageis human-readable text only — its wording and language are not stable, so show it, log it, never parse it. When there is nothing to report, both are absent from the block rather thannull.- Right after issuing, the block is almost empty. The issue response, and a read in
the next few seconds, carry only
enabledandsubmission_status: NOT_SUBMITTED. The hash, QR,registration_numberandregistered_atappear together withPENDING.
What triggers each transition
| Event | Effect on verifactu.submission_status |
|---|---|
| You issue an invoice from a NIF under the regime | NOT_SUBMITTED for a few seconds, while the record is built → PENDING |
| AEAT accepts the record | PENDING → ACCEPTED (with an error_code if accepted with errors) |
| AEAT rejects the record | PENDING → REJECTED, with error_code and error_message |
| AEAT answers with a temporary server-side error | Stays PENDING, with no error_code or error_message, while BeeL. retries; → REJECTED only if the retries run out |
| The submission is refused before it reaches AEAT | NOT_SUBMITTED → REJECTED, no error_code, an explanatory error_message |
| AEAT already holds a different invoice with the same number and issue date | NOT_SUBMITTED → REJECTED, no error_code, an error_message that says the number is taken (what to do) |
| BeeL. stops waiting for an answer that never comes | PENDING → REJECTED; error_message says the submission was abandoned |
| 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 void an invoice whose registration AEAT accepted | A cancellation record is sent; see Voiding below |
The verifactu block summarises the registration, so after a void its hash, number and date are still the registration record's. Each record — registration and cancellation — carries its own submission_status in List the VeriFactu records of an invoice; see Did AEAT accept the cancellation?.
REJECTED is not one situation. It means the invoice is not in AEAT's registry right now — because AEAT refused it, because the submission was refused before reaching AEAT, or because BeeL. gave up waiting. An error_code means AEAT gave a coded answer; no code means it did not — either the record never reached AEAT, or it was abandoned while waiting. How to tell them apart and what to do with each is in Handling AEAT rejections.
Voiding
The verifactu block summarises the registration, so after a void its hash, number and date are still the registration record's. What submission_status shows depends on the cancellation:
| Situation | status | verifactu.submission_status |
|---|---|---|
| Cancellation sent, AEAT has not answered yet | VOIDED | Unchanged — still the registration's (typically ACCEPTED). It never goes back to PENDING |
| AEAT accepted the cancellation | VOIDED | VOIDED |
| AEAT rejected the cancellation | VOIDED | REJECTED, with the cancellation's error_code / error_message |
You voided while the registration was still PENDING | VOIDED | The registration's status. The cancellation is sent only after AEAT accepts the registration |
| The registration never reached AEAT, or AEAT rejected it | VOIDED | Unchanged (REJECTED), or absent if the invoice never had a record (NOT_SUBMITTED before the void). Nothing is sent to AEAT — there is no record there to cancel |
The invoice's own status flips to VOIDED immediately in every case; see Did AEAT accept the cancellation?.
Recovering from a rejection
There is no public endpoint to retry a submission. Wait on a temporary AEAT error, fix the invoice's own data with a new fiscal document, and ask support for anything outside the invoice — each case, per AEAT code, in Handling AEAT rejections.
AEAT error codes
error_code carries the code AEAT returned, unchanged. The codes you are most likely to meet — grouped by what you should do about each — are listed in Handling AEAT rejections. The complete list is in the AEAT VeriFactu documentation.
Webhooks for fiscal status changes
Subscribe to verifactu.status.updated to be notified each time the public status changes:
{
"type": "verifactu.status.updated",
"data": {
"invoice_id": "...",
"invoice_number": "INV-2026/00042",
"verifactu_registration_id": "...",
"previous_status": "PENDING",
"new_status": "ACCEPTED",
"qr_url": "https://www2.agenciatributaria.gob.es/wlpl/TIKE-CONT/ValidarQR?nif=B27534239&numserie=INV-2026%2F00042&fecha=18-05-2026&importe=121.00",
"qr_base64": "iVBORw0KGgoAAAANSUhEUgAAAMgAAADI...",
"invoice_hash": "1863E77D540083E4259F4AF44322E16B962CB5D72C2F949AB2B30FA83E93FF84"
}
}Four of the five values travel by webhook. previous_status and new_status carry PENDING, ACCEPTED, VOIDED or REJECTED. NOT_SUBMITTED never does: it describes an invoice with no record at all, so there is no record whose status could change. The first notification for a record has no previous_status.
A webhook is sent when the public status changes, and only then. A submission refused before it reached AEAT, or one BeeL. stopped waiting for, arrives as REJECTED like any other rejection. A temporary AEAT error keeps the record PENDING, so it sends nothing. Your endpoint can still miss deliveries, so reconcile periodically by listing with ?verifactu_status=REJECTED and ?verifactu_status=NOT_SUBMITTED; see Reconciliation.
See Webhook events for the full payload.
Related
Auto-submit policy
Whether an issued invoice reaches AEAT is decided by the taxpayer, not by the request — what that means when you issue, and how to read the result.
Handling AEAT rejections
How to read a REJECTED invoice (and an accepted one that carries an error code), what to do for each family of AEAT codes, and how to make sure you never miss one.