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โ€‹