Skip to main content

Sending a Credit Note

Credit Notes are used to correct or cancel previously issued invoices. This guide covers when and how to send credit notes via Peppol.

When to Use a Credit Note

ScenarioUse Credit Note?Notes
Customer returned goods✅ YesFull or partial credit
Pricing error discovered✅ YesCorrect the amount
Quantity was wrong✅ YesCredit for difference
Discount applied after invoice✅ YesCredit the discount amount
Cancel entire invoice✅ YesCredit full amount
Customer disputes charge⚠️ MaybeDepends on outcome
Payment already received✅ YesCredit note for accounting

Credit Note Structure

A credit note must reference the original invoice:

<?xml version="1.0" encoding="UTF-8"?>
<CreditNote xmlns="urn:oasis:names:specification:ubl:schema:xsd:CreditNote-2"
xmlns:cac="urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2"
xmlns:cbc="urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2">

<!-- Peppol identifiers -->
<cbc:CustomizationID>urn:cen.eu:en16931:2017#compliant#urn:fdc:peppol.eu:2017:poacc:billing:3.0</cbc:CustomizationID>
<cbc:ProfileID>urn:fdc:peppol.eu:2017:poacc:billing:01:1.0</cbc:ProfileID>

<!-- Credit note identification -->
<cbc:ID>CN-2024-00045</cbc:ID>
<cbc:IssueDate>2024-01-20</cbc:IssueDate>
<cbc:CreditNoteTypeCode>381</cbc:CreditNoteTypeCode>
<cbc:DocumentCurrencyCode>EUR</cbc:DocumentCurrencyCode>

<!-- IMPORTANT: Reference to original invoice -->
<cac:BillingReference>
<cac:InvoiceDocumentReference>
<cbc:ID>INV-2024-00123</cbc:ID>
<cbc:IssueDate>2024-01-15</cbc:IssueDate>
</cac:InvoiceDocumentReference>
</cac:BillingReference>

<!-- Seller (same as original invoice) -->
<cac:AccountingSupplierParty>
<cac:Party>
<cbc:EndpointID schemeID="0106">12345678</cbc:EndpointID>
<cac:PartyLegalEntity>
<cbc:RegistrationName>Your Company BV</cbc:RegistrationName>
</cac:PartyLegalEntity>
</cac:Party>
</cac:AccountingSupplierParty>

<!-- Buyer (same as original invoice) -->
<cac:AccountingCustomerParty>
<cac:Party>
<cbc:EndpointID schemeID="0106">87654321</cbc:EndpointID>
<cac:PartyLegalEntity>
<cbc:RegistrationName>Customer Company BV</cbc:RegistrationName>
</cac:PartyLegalEntity>
</cac:Party>
</cac:AccountingCustomerParty>

<!-- Tax total (positive amounts) -->
<cac:TaxTotal>
<cbc:TaxAmount currencyID="EUR">10.50</cbc:TaxAmount>
<cac:TaxSubtotal>
<cbc:TaxableAmount currencyID="EUR">50.00</cbc:TaxableAmount>
<cbc:TaxAmount currencyID="EUR">10.50</cbc:TaxAmount>
<cac:TaxCategory>
<cbc:ID>S</cbc:ID>
<cbc:Percent>21</cbc:Percent>
<cac:TaxScheme>
<cbc:ID>VAT</cbc:ID>
</cac:TaxScheme>
</cac:TaxCategory>
</cac:TaxSubtotal>
</cac:TaxTotal>

<!-- Credit totals (all positive values) -->
<cac:LegalMonetaryTotal>
<cbc:LineExtensionAmount currencyID="EUR">50.00</cbc:LineExtensionAmount>
<cbc:TaxExclusiveAmount currencyID="EUR">50.00</cbc:TaxExclusiveAmount>
<cbc:TaxInclusiveAmount currencyID="EUR">60.50</cbc:TaxInclusiveAmount>
<cbc:PayableAmount currencyID="EUR">60.50</cbc:PayableAmount>
</cac:LegalMonetaryTotal>

<!-- Credit line (describe what is being credited) -->
<cac:CreditNoteLine>
<cbc:ID>1</cbc:ID>
<cbc:CreditedQuantity unitCode="HUR">5</cbc:CreditedQuantity>
<cbc:LineExtensionAmount currencyID="EUR">50.00</cbc:LineExtensionAmount>
<cac:Item>
<cbc:Name>Consulting Hours - Return Credit</cbc:Name>
<cac:ClassifiedTaxCategory>
<cbc:ID>S</cbc:ID>
<cbc:Percent>21</cbc:Percent>
<cac:TaxScheme>
<cbc:ID>VAT</cbc:ID>
</cac:TaxScheme>
</cac:ClassifiedTaxCategory>
</cac:Item>
<cac:Price>
<cbc:PriceAmount currencyID="EUR">10.00</cbc:PriceAmount>
</cac:Price>
</cac:CreditNoteLine>
</CreditNote>

Key Differences from Invoice

ElementInvoiceCredit Note
Root element<Invoice><CreditNote>
Type code380381
Line element<InvoiceLine><CreditNoteLine>
Quantity element<InvoicedQuantity><CreditedQuantity>
ReferenceOptionalRequired
Amounts Are Positive

Credit note amounts are always positive. The document type (381) indicates it's a credit, so the receiver knows to subtract these amounts.

Sending a Credit Note

import requests

# Load credit note XML
with open("credit-note.xml", "r") as f:
credit_note_xml = f.read()

# Send via the same endpoint as invoices
response = requests.post(
"https://app.goroute.ai/peppol-api/api/v1/send",
headers={
"X-API-Key": "your_api_key",
"Content-Type": "application/xml"
},
data=credit_note_xml
)

result = response.json()
print(f"Credit Note Transaction ID: {result['transaction_id']}")

Credit Note Types

Full Credit (Cancel Invoice)

Credit the entire original invoice amount:

<!-- Reference the invoice being cancelled -->
<cac:BillingReference>
<cac:InvoiceDocumentReference>
<cbc:ID>INV-2024-00123</cbc:ID>
</cac:InvoiceDocumentReference>
</cac:BillingReference>

<!-- Note explaining the cancellation -->
<cbc:Note>Cancellation of invoice INV-2024-00123 due to order cancellation</cbc:Note>

<!-- Full amount credited -->
<cac:LegalMonetaryTotal>
<cbc:PayableAmount currencyID="EUR">121.00</cbc:PayableAmount>
</cac:LegalMonetaryTotal>

Partial Credit (Correction)

Credit only part of the original invoice:

<cac:BillingReference>
<cac:InvoiceDocumentReference>
<cbc:ID>INV-2024-00123</cbc:ID>
</cac:InvoiceDocumentReference>
</cac:BillingReference>

<cbc:Note>Partial credit for returned items</cbc:Note>

<!-- Only the returned portion -->
<cac:LegalMonetaryTotal>
<cbc:PayableAmount currencyID="EUR">30.25</cbc:PayableAmount>
</cac:LegalMonetaryTotal>

Price Correction

Correct a pricing error:

<cac:BillingReference>
<cac:InvoiceDocumentReference>
<cbc:ID>INV-2024-00123</cbc:ID>
</cac:InvoiceDocumentReference>
</cac:BillingReference>

<cbc:Note>Price correction: Original price €100, Correct price €90</cbc:Note>

<!-- Credit the difference -->
<cac:CreditNoteLine>
<cbc:ID>1</cbc:ID>
<cbc:CreditedQuantity unitCode="EA">1</cbc:CreditedQuantity>
<cbc:LineExtensionAmount currencyID="EUR">10.00</cbc:LineExtensionAmount>
<cac:Item>
<cbc:Name>Price adjustment - Consulting Services</cbc:Name>
</cac:Item>
<cac:Price>
<cbc:PriceAmount currencyID="EUR">10.00</cbc:PriceAmount>
</cac:Price>
</cac:CreditNoteLine>

Generating Credit Notes Programmatically

from lxml import etree

def generate_credit_note(
credit_note_id: str,
original_invoice_id: str,
original_invoice_date: str,
seller_id: str,
buyer_id: str,
lines: list,
reason: str
) -> str:
"""Generate a Peppol-compliant credit note."""

ns = {
None: "urn:oasis:names:specification:ubl:schema:xsd:CreditNote-2",
"cac": "urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2",
"cbc": "urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2"
}

root = etree.Element("CreditNote", nsmap=ns)

# Peppol identifiers
etree.SubElement(root, "{%s}CustomizationID" % ns["cbc"]).text = \
"urn:cen.eu:en16931:2017#compliant#urn:fdc:peppol.eu:2017:poacc:billing:3.0"
etree.SubElement(root, "{%s}ProfileID" % ns["cbc"]).text = \
"urn:fdc:peppol.eu:2017:poacc:billing:01:1.0"

# Credit note details
etree.SubElement(root, "{%s}ID" % ns["cbc"]).text = credit_note_id
etree.SubElement(root, "{%s}IssueDate" % ns["cbc"]).text = date.today().isoformat()
etree.SubElement(root, "{%s}CreditNoteTypeCode" % ns["cbc"]).text = "381"
etree.SubElement(root, "{%s}DocumentCurrencyCode" % ns["cbc"]).text = "EUR"
etree.SubElement(root, "{%s}Note" % ns["cbc"]).text = reason

# Billing reference
billing_ref = etree.SubElement(root, "{%s}BillingReference" % ns["cac"])
invoice_ref = etree.SubElement(billing_ref, "{%s}InvoiceDocumentReference" % ns["cac"])
etree.SubElement(invoice_ref, "{%s}ID" % ns["cbc"]).text = original_invoice_id
etree.SubElement(invoice_ref, "{%s}IssueDate" % ns["cbc"]).text = original_invoice_date

# ... add seller, buyer, tax totals, lines ...

return etree.tostring(root, pretty_print=True, xml_declaration=True, encoding="UTF-8")

Validation

Credit notes go through the same validation as invoices:

# Validate before sending
response = requests.post(
"https://app.goroute.ai/peppol-api/api/v1/validate",
headers={
"X-API-Key": "your_api_key",
"Content-Type": "application/xml"
},
data=credit_note_xml
)

result = response.json()
if not result["valid"]:
print("Credit note validation failed:")
for error in result["errors"]:
print(f" {error['message']}")

Common Credit Note Errors

ErrorCauseFix
Missing BillingReferenceNo invoice referenceAdd <cac:BillingReference>
Invalid CreditNoteTypeCodeWrong type codeUse 381 for credit notes
Negative amountsAmounts should be positiveUse positive values
Missing seller endpointSeller ID missingAdd <cbc:EndpointID>

Best Practices

  1. Always Reference Original — Include the original invoice ID and date
  2. Clear Reason — Add a note explaining why the credit is issued
  3. Same Parties — Seller and buyer must match the original invoice
  4. Match Tax Rates — Use the same VAT rates as the original invoice
  5. Timely Issuance — Issue credit notes promptly after the event

Tracking Credit Note Status

# Get credit note transaction status
response = requests.get(
f"https://app.goroute.ai/peppol-api/api/v1/transactions/{transaction_id}",
headers={"X-API-Key": "your_api_key"}
)

status = response.json()
print(f"Credit Note Status: {status['status']}")
print(f"Delivered At: {status.get('delivered_at', 'Pending')}")

Next Steps