Direct debits (pain.008)
A direct debit is a pull payment: a creditor (payee) collects money from one or more debtors, each authorized by a signed mandate. The pain.008.001.08 message type is the modern ISO 20022 Direct Debit Initiation message used across the SEPA zone.
The model
Section titled “The model”import { euros, type DirectDebitDocument } from 'sepa-xml-ts'
const doc: DirectDebitDocument = { messageId: 'DD-2026-0001', createdAt: '2026-06-01T09:00:00Z', initiatingParty: 'ACME GmbH',
// The creditor is specified once at document level. // The writer fans it out into every PmtInf automatically. creditor: { name: 'ACME GmbH', iban: 'DE89370400440532013000', bic: 'COBADEFFXXX', creditorId: 'DE98ZZZ09999999999', // SEPA Creditor Identifier },
batches: [ { id: 'BATCH-001', collectionDate: '2026-06-10', // ReqdColltnDt/Dt: YYYY-MM-DD sequenceType: 'FRST', // FRST | RCUR | OOFF | FNAL localInstrument: 'CORE', // CORE | B2B (defaults to CORE)
collections: [ { endToEndId: 'SUB-1001', amount: euros('49.99'), debtor: { name: 'Kunde Eins', iban: 'NL91ABNA0417164300', }, mandate: { id: 'MND-001', signatureDate: '2026-01-15', // YYYY-MM-DD }, remittanceInfo: 'Subscription June', // optional }, ], }, ],}Model types
Section titled “Model types”| Type | XSD element |
|---|---|
DirectDebitDocument | Document/CstmrDrctDbtInitn |
Creditor | Cdtr + CdtrAcct + CdtrAgt + CdtrSchmeId |
DirectDebitBatch | PmtInf |
Collection | DrctDbtTxInf |
Mandate | DrctDbtTx/MndtRltdInf |
SequenceType | PmtTpInf/SeqTp |
LocalInstrument | PmtTpInf/LclInstrm/Cd |
Sequence types
Section titled “Sequence types”| Value | Meaning |
|---|---|
FRST | First collection under a mandate |
RCUR | Recurring collection |
OOFF | One-off collection (mandate used once) |
FNAL | Final collection, mandate will be cancelled |
SEPA Creditor Identifier
Section titled “SEPA Creditor Identifier”The creditorId field on Creditor is a SEPA Creditor Identifier (e.g. DE98ZZZ09999999999).
The library validates the check digits strictly using ISO 7064 MOD 97-10, the same algorithm as
IBAN validation. An incorrect check digit is caught at validation time before the file is written.
The structure is:
- 2-letter country code
- 2 check digits (ISO 7064 MOD 97-10)
- 3-char creditor business code (e.g.
ZZZ) - national identifier (1 to 28 alphanumeric characters)
Use buildCreditorId(country, businessCode, nationalId) from the library to compute a correct
identifier programmatically.
Writing to XML
Section titled “Writing to XML”import { writeDirectDebit } from 'sepa-xml-ts'
const xml = writeDirectDebit(doc)writeDirectDebit validates the model internally before writing. It throws if the model is
invalid.
Derived fields
Section titled “Derived fields”The writer derives the following fields. You do not supply them:
| Field | How it is derived |
|---|---|
GrpHdr/NbOfTxs | Total count of all collections across all batches |
GrpHdr/CtrlSum | Sum of all collection amounts (exact bigint addition) |
PmtInf/NbOfTxs | Count of collections in this batch |
PmtInf/CtrlSum | Sum of collection amounts in this batch |
PmtInf/PmtMtd | Always DD |
PmtInf/PmtTpInf/SvcLvl/Cd | Always SEPA |
PmtInf/ChrgBr | Always SLEV |
CdtrSchmeId/.../SchmeNm/Prtry | Always SEPA |
Structural gotchas
Section titled “Structural gotchas”CdtrAgt (at PmtInf level) and DbtrAgt (per transaction) are structurally required by the XSD
even when no BIC is present. The writer emits the correct empty-institution placeholder
automatically when bic is absent:
<CdtrAgt><FinInstnId/></CdtrAgt>This is handled transparently. You only need to supply bic when you know it.
Sequence-type and mandate rules
Section titled “Sequence-type and mandate rules”validateDirectDebit and writeDirectDebit enforce four cross-field SEPA rulebook constraints.
R1 to R3 apply to both CORE and B2B direct debits, and to both the ISO variant and the German DK
variant. R4 relates to mandate amendment (see below).
R1: signature before collection. mandate.signatureDate must not be after the batch
collectionDate. A collection cannot be initiated on a mandate that has not been signed yet.
Equal dates are allowed (signing and collecting on the same day is valid).
R2: OOFF is single-use. A mandate id used in any batch with sequenceType: 'OOFF' must
appear in exactly one collection across the whole document, and must not also appear under any
other sequence type. A one-off authorization covers one collection.
R3: consistent scheme per mandate. A given mandate id must not appear under both CORE and
B2B local instruments in the same document. A CORE mandate and a B2B mandate are distinct
authorizations. Note that localInstrument defaults to CORE when omitted, matching the writer
default.
R4: SMNDA requires FRST. If any collection in a batch uses
mandate.amendment.sameMandateNewDebtorAccount: true (the SMNDA flag), that batch’s sequenceType
must be FRST. A change of debtor account is presented as a first collection.
validateDirectDebit returns the violations as ruleIssues on the result. writeDirectDebit
throws before emitting any XML when a rule is violated.
Mandate amendment
Section titled “Mandate amendment”When an existing mandate’s details change, set mandate.amendment. The writer emits
MndtRltdInf/AmdmntInd=true and the matching AmdmntInfDtls. This first cut models the common cases:
const collection = { endToEndId: 'SDD-E2E-001', amount: euros('10.00'), debtor: { name: 'Customer', iban: 'NL91ABNA0417164300' }, mandate: { id: 'MAND-NEW-001', signatureDate: '2025-01-01', amendment: { originalMandateId: 'MAND-OLD-001', // AmdmntInfDtls/OrgnlMndtId, the mandate reference changed // OR a moved account, give the old IBAN: // originalDebtorAccount: 'DE89370400440532013000', // OR same bank, new account (SMNDA), which requires a FRST batch (rule R4): // sameMandateNewDebtorAccount: true, }, },}originalDebtorAccount (an old IBAN) and sameMandateNewDebtorAccount are mutually exclusive: SMNDA
means the new account is at the same bank and is not disclosed, so an explicit old account would
contradict it. An amendment object must set at least one detail. Supported for pain.008.001.08
only; the DK variant throws if an amendment is present. The richer AmdmntInfDtls fields (original
creditor scheme id, original debtor name and agent, frequency, final collection date, reason) are a
documented follow-up.
Ultimate parties
Section titled “Ultimate parties”Each collection accepts an optional ultimateCreditor and ultimateDebtor (name only), emitted as
UltmtCdtr/Nm and UltmtDbtr/Nm. These name the party that really collects or really owes, when it
differs from the account holder.
const collection = { endToEndId: 'SDD-E2E-001', amount: euros('10.00'), debtor: { name: 'Customer', iban: 'NL91ABNA0417164300' }, mandate: { id: 'MAND-001', signatureDate: '2025-01-01' }, ultimateDebtor: { name: 'Real Account Holder' },}Supported for pain.008.001.08 only. The DK variant throws if an ultimate party is present, rather
than dropping it silently.
Remittance information
Section titled “Remittance information”Each collection carries remittance information in one of two mutually exclusive forms: a free-text
remittanceInfo (unstructured RmtInf/Ustrd), or a structuredRemittance creditor reference
(RmtInf/Strd/CdtrRefInf).
const collection = { endToEndId: 'SDD-E2E-001', amount: euros('10.00'), debtor: { name: 'Customer', iban: 'NL91ABNA0417164300' }, mandate: { id: 'MAND-001', signatureDate: '2025-01-01' }, structuredRemittance: { creditorReference: 'RF18539007547034' },}referenceType defaults to SCOR and is restricted to the ISO 20022 code list. A reference starting
with the uppercase RF prefix has its ISO 11649 check digits validated; non-RF national references
pass through unchecked. Supported for pain.008.001.08 only; the DK variant throws if present. See
the credit transfers guide for the full validation policy.
Purpose codes
Section titled “Purpose codes”Each collection can carry a purpose code (Purp/Cd), and each batch a category purpose code
(PmtTpInf/CtgyPurp/Cd). Both are 4-letter ISO 20022 external codes.
const batch = { // ... categoryPurpose: 'SUPP', // batch level collections: [ { endToEndId: 'SDD-E2E-001', amount: euros('10.00'), debtor: { name: 'Customer', iban: 'NL91ABNA0417164300' }, mandate: { id: 'MAND-001', signatureDate: '2025-01-01' }, purpose: 'GDDS', // collection level }, ],}Validated for SEPA charset and length (1 to 4 characters) only, not against the ISO external code
list. Supported for pain.008.001.08 only; the DK variant throws if present.
Variants
Section titled “Variants”To write a German DK pain.008.003.02 file:
const xml = writeDirectDebit(doc, { variant: 'pain.008.003.02' })See National variants for the structural differences and XSD details.