Invopop Integration

Invopop is the third-party e-invoicing platform that handles GOBL validation, Verifactu XML generation, and submission to the Spanish Tax Authority (AEAT). This page documents the Invopop API as it relates to our integration, the GOBL document format, and common error scenarios.

For general context on what e-invoicing is and how it fits into Lavanda, see the E-Invoicing.

External documentation:

API Endpoints

Service
Base URL
Purpose

Silo

https://api.invopop.com/silo/v1

Document storage

Transform

https://api.invopop.com/transform/v1

Workflow processing

All requests require a Bearer token:

Authorization: Bearer <api_token>

Each workspace that uses e-invoicing has its own Invopop API token, stored encrypted in the workspace configuration.

Core Flow

The submission process has two steps, both using idempotent PUT requests with client-generated UUID v7 identifiers.

1. PUT /silo/v1/entries/{uuid}     -> Store GOBL document
2. PUT /transform/v1/jobs/{uuid}   -> Submit to Verifactu workflow
3. Webhook                         -> Receive result (accepted/rejected)

Step 1: Store the Document

Upload a GOBL invoice to Invopop's Silo:

The entry UUID must be a UUID v7 (timestamp-based). Invopop rejects standard v4 UUIDs for the invoices folder.

Step 2: Trigger the Workflow

Submit the stored document to a Verifactu workflow:

The ?wait=30 parameter makes the call synchronous (up to 30 seconds), returning the result inline instead of requiring a separate poll.

Step 3: Receive Results

After a successful submission, the Silo entry is updated with stamps and metadata:

Field
Location in response
Description

QR code URL

data.head.stamps[] where prv == "verifactu-qr"

A link customers can use to verify the invoice on AEAT's website

Verifactu hash

data.head.stamps[] where prv == "verifactu-hash"

Cryptographic proof of submission

Submission status

meta[] where src == "verifactu"

e.g. "Correcto"

Fetch the updated entry to read these fields:

Idempotency and Entry Updates

PUT is create-only. Once a Silo entry exists, a subsequent PUT returns 409 Conflict with the message "entry already exists with same id". This makes PUT safe to retry on network failures.

To update an existing entry's data (e.g. to fix an error before resubmission), use PATCH:

Method
Entry exists?
Result

PUT

No

Creates entry

PUT

Yes

409 Conflict

PATCH

Yes

Updates entry data (even if signed or in error state)

DELETE

--

Not supported (returns 404)

Webhooks

Invopop sends webhook notifications when a workflow job completes. The payload contains:

The webhook only contains the silo_entry_id, not the full document. To get the result details (QR code, hash, status), fetch the entry from the Silo API using the silo_entry_id.

GOBL Document Format

GOBL (Go Business Language) is an open-source standard for structured financial documents. All invoices submitted through Invopop must be in GOBL JSON format.

Regular Invoice (F1)

Credit Note (R4)

Credit notes reference the original invoice via the preceding field. They use "type": "credit-note" and do not include the $addons field.

Multi-line Invoice with Mixed VAT Rates

When an invoice contains line items at different VAT rates, each rate appears as a separate entry in totals.taxes.categories[].rates[]:

Customer Identity Mapping

How a customer is represented in GOBL depends on their ID type and country.

Spanish Customers

Spanish customers with a tax ID (CIF for companies, NIF/DNI for individuals) use the tax_id field:

Foreign Customers

Foreign customers without a Spanish tax ID use the identities field with a Verifactu identity type extension:

Passport:

EU VAT number:

Verifactu Identity Type Codes

Code
Description
When to use

02

NIF-IVA

EU VAT number (non-Spanish)

03

Passport

Passport number

04

Official ID

National ID card from country of residence

05

Residence certificate

Spanish residence certificate

06

Other document

Other supporting identification (e.g. driver's license)

07

Not registered

Customer has no identification

Spain / Verifactu Reference

Document Types

Verifactu Code
Type
GOBL representation

F1

Regular invoice

Default (no type field)

R4

Credit note

"type": "credit-note"

Tax Extensions

Both line-level taxes and totals-level rates must include these extensions:

Extension
Values

es-verifactu-op-class

S1 (standard taxable), S2 (exempt)

es-verifactu-regime

01 (general regime), 02 (simplified regime)

Bypass Mode

Bypass mode tells GOBL to preserve pre-calculated totals instead of recalculating them. It is activated by adding "$tags": ["bypass"] to the document.

Without bypass mode, GOBL recalculates VAT by grouping line items by rate, summing their net amounts, and applying the tax percentage to the aggregated total. This can cause small rounding differences (typically plus or minus one cent) compared to per-line-item calculations.

Note: We are planning to remove bypass mode from our integration by aligning our internal VAT calculations with GOBL's aggregation method. See the Reference for details on the rounding issue.

Known Issue: Bypass Mode Fails for Credit Notes

Document Type
Bypass Mode
Result

Regular invoice (F1)

Yes

Works

Credit note (R4)

Yes

Fails with verifactu.generate error

Credit note (R4)

No

Works

This is a known Invopop bug reported in January 2026. Credit notes submitted with $tags: ["bypass"] fail at the verifactu.generate step with an "unexpected data error". The workaround is to submit credit notes without bypass mode.

Error Recovery

When a workflow fails (e.g. rejected at verifactu.generate or verifactu.send), entries can be corrected and resubmitted:

Each resubmission requires a new job UUID. The entry UUID stays the same.

Constraint: Hash Chain Integrity

Once Verifactu has accepted a record, it becomes part of a cryptographic hash chain. Changing the invoice code and resubmitting fails with error 3002 ("No existe el registro de facturacion"). To correct an accepted invoice, issue a credit note instead.

Scenario
Can change code?
Can change other fields?

Failed at verifactu.generate

Yes

Yes

Rejected by verifactu.send

Yes (likely)

Yes (likely)

Accepted by verifactu.send

No (error 3002)

Not tested

PATCH Behaviour on Signed Entries

PATCH updates the GOBL document and digest even on signed or error-state entries. The original signature in sigs is retained but stale. On resubmission, Invopop skips the "Sign Envelope" step ("already signed"), which does not block Verifactu processing.

Verifactu Cancellation

  • Rejected invoices are not counted as reported to Verifactu, so no cancellation is needed

  • To cancel an accepted invoice, a credit note must be issued (there is no "cancel" API)

  • Invopop workflows can be configured to auto-cancel rejected invoices in Verifactu

Supplier Registration

New suppliers must be registered with Invopop before submitting invoices:

  1. Create the supplier as a Silo entry with the org/party GOBL schema

  2. Run a registration workflow to register the supplier with the tax authority

This is typically a one-time setup step per workspace.

Common Errors

Error
Cause
Solution

invalid check digit

Invalid Spanish CIF/NIF

Use a valid tax ID

invalid format

Malformed tax ID

Use the correct CIF/NIF format

request duplicated

Same supplier + invoice code combination

Use unique invoice codes per supplier

party is not registered

Supplier not registered with Invopop

Complete the supplier registration flow

unexpected data error

Bypass mode used with a credit note

Remove the bypass tag for credit notes

schema validation

Missing required fields in bypass mode

Include all ext fields on lines and totals rates

No existe el registro de facturacion (3002)

Resubmitting with a changed code after Verifactu accepted the original

Cannot change the code after acceptance; issue a credit note instead

entry already exists with same id

PUT on an existing Silo entry

Use PATCH to update, or use a new UUID

Best Practices

  1. Generate UUID v7 identifiers client-side for idempotent retries (Invopop rejects v4 UUIDs)

  2. Use ?wait=30 on job creation to get the result synchronously

  3. Check the faults array in the job response even when the top-level status is "OK"

  4. Use unique invoice codes per supplier (Verifactu rejects duplicates)

  5. Include tax extensions on both line-level taxes and totals-level rates

  6. Use PATCH for corrections, not PUT (PUT cannot update existing entries)

  7. Generate a new job UUID per retry while reusing the same entry UUID

Last updated