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
| Scenario | Use Credit Note? | Notes |
|---|---|---|
| Customer returned goods | ✅ Yes | Full or partial credit |
| Pricing error discovered | ✅ Yes | Correct the amount |
| Quantity was wrong | ✅ Yes | Credit for difference |
| Discount applied after invoice | ✅ Yes | Credit the discount amount |
| Cancel entire invoice | ✅ Yes | Credit full amount |
| Customer disputes charge | ⚠️ Maybe | Depends on outcome |
| Payment already received | ✅ Yes | Credit 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
| Element | Invoice | Credit Note |
|---|---|---|
| Root element | <Invoice> | <CreditNote> |
| Type code | 380 | 381 |
| Line element | <InvoiceLine> | <CreditNoteLine> |
| Quantity element | <InvoicedQuantity> | <CreditedQuantity> |
| Reference | Optional | Required |
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
| Error | Cause | Fix |
|---|---|---|
| Missing BillingReference | No invoice reference | Add <cac:BillingReference> |
| Invalid CreditNoteTypeCode | Wrong type code | Use 381 for credit notes |
| Negative amounts | Amounts should be positive | Use positive values |
| Missing seller endpoint | Seller ID missing | Add <cbc:EndpointID> |
Best Practices
- Always Reference Original — Include the original invoice ID and date
- Clear Reason — Add a note explaining why the credit is issued
- Same Parties — Seller and buyer must match the original invoice
- Match Tax Rates — Use the same VAT rates as the original invoice
- 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')}")