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 acknowledges receipt
SENT SENT
↓ pay / overdue ↓ AEAT registers
PAID / OVERDUE ACCEPTED or REJECTED
↓ correct ↓ on void
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. Valid values on the response:
verifactu.submission_status | What it means | What to do |
|---|---|---|
PENDING | Submission queued / in-flight to AEAT. | Wait — usually seconds. |
SENT | AEAT acknowledged receipt; awaiting final registry decision. | Wait. |
ACCEPTED | AEAT registered the invoice. | Nothing. |
REJECTED | AEAT refused the registration. | Issue a corrective or void + reissue — see Cancel vs amend. |
VOIDED | The 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
}
}| Field | Meaning |
|---|---|
enabled | Whether VeriFactu applies to this invoice. false for drafts and invoices issued under skipped auto-submit. |
submission_status | The lifecycle state (table above). |
registration_number | AEAT's unique identifier for the registro (Spanish: código de registro). |
registration_date | When AEAT acknowledged the submission. |
invoice_hash | SHA-256 fingerprint of this invoice. |
chaining_hash | SHA-256 that links this registro to the previous one in your chain. |
qr_url / qr_base64 | AEAT-mandated QR code on the printed invoice. |
error_code / error_message | Set when submission_status is REJECTED or for accepted-with-warnings responses. Single values, not arrays. |
What triggers each transition
| Event | Effect on verifactu.submission_status |
|---|---|
| You issue an invoice and auto-submit applies | (none) → PENDING |
| AEAT acknowledges receipt | PENDING → SENT |
| AEAT completes registration | SENT → ACCEPTED or REJECTED |
| AEAT returns a transient server error | PENDING → (retry) → PENDING |
| 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 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}/voidfollowed by a newPOST /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:
| 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 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.
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