Direct Debit
Direct Debit is a payment method where an authorised creditor collects funds directly from a payer's account, based on a standing authorisation called a mandate. On the IF Platform your client is the payer — a creditor initiates the collection through the underlying service provider (ClearBank for Bacs, Banking Circle for Bacs and SEPA), and the IF Platform represents it as an Outgoing Transfer that decreases the balance of the related account.
Direct Debit is built on two entities:
1. Direct Debit Mandate : the standing authorisation that allows a creditor to collect payments from a payer account. A mandate must exist and be active before collections can be honoured.
2. Direct Debit Payment : an individual collection initiated by the creditor against the mandate. Direct Debit Payments are not created through the API — they are driven by the service provider and surfaced to you through webhooks. Each Direct Debit Payment spawns an Outgoing Transfer that carries out the accounting.
Relation with an Outgoing Transfer
When a Direct Debit Payment is received, the IF Platform automatically creates an Outgoing Transfer on the payer account and links the two over the outgoingTransferId field on the Direct Debit Payment. The Outgoing Transfer follows the standard outgoing transfer lifecycle (please refer to the Outgoing Transfers page). If a payment that charged the payer account is later returned, the Incoming Transfer that credits the funds back is linked over the incomingTransferId field once the returned funds are booked. This happens after the direct-debit-payment-returned webhook, so retrieve the payment to read it. The field stays empty when the payer account was never charged.
Supported Schemes
Direct Debit is supported for the following schemes. The scheme field reflects the scheme of both the mandate and its payments:
- bacs (GBP) — ClearBank, Banking Circle
- sepa (EUR) — Banking Circle
Workflows
| endpoint | workflow.code |
|---|---|
| POST /direct-debit-mandates | client.direct, organization.direct |
| PATCH /direct-debit-mandates/{id} | client.direct, organization.direct |
| PATCH /direct-debit-payments/{id} | client.direct, direct |
client.direct creates a mandate owned by a client (the clientId is required); organization.direct creates one owned by the organization.
Direct Debit Mandate
Mandate Lifecycle
The status field of a mandate reflects its lifecycle:
- pending : the mandate has been created and is awaiting activation by the service provider.
- active : the mandate is activated; collections can be honoured.
- cancelled : the mandate has been cancelled and no further collections will be honoured.
- rejected : the mandate has been rejected, by you or by the service provider, and no further collections will be honoured.
A collection arriving for a mandate that is not active is returned automatically with the reason mandate-not-active.
Create a Direct Debit Mandate
POST /direct-debit-mandates
Depending on the underlying service provider, a mandate is either created on the provider side or registered directly on the IF Platform. The flowType is payment for payer-side mandates. The scheme must be one of the supported schemes. A successful request returns 201 Created.
{
"workflow": {
"code": "client.direct"
},
"data": {
"directDebitMandate": {
"clientId": "00000000-0000-0000-0000-000000000000",
"accountId": "00000000-0000-0000-0000-000000000000",
"scheme": "sepa",
"flowType": "payment",
"creditorIdentifier": "DE98ZZZ09999999999",
"creditorAccount": {
"accountHolderName": "NETFLIX INTERNATIONAL B.V.",
"country": "NL",
"currency": "EUR",
"iban": "NL91ABNA0417164300",
"routingCodes": {}
},
"reference": "MNDT-2026-000123"
}
},
"connect": {},
"metadata": {}
}{
"workflow": {
"code": "client.direct"
},
"data": {
"directDebitMandate": {
"id": "00000000-0000-0000-0000-000000000000",
"owner": "client",
"clientId": "00000000-0000-0000-0000-000000000000",
"accountId": "00000000-0000-0000-0000-000000000000",
"scheme": "sepa",
"flowType": "payment",
"status": "pending",
"creditorIdentifier": "DE98ZZZ09999999999",
"creditorAccount": {
"accountHolderName": "NETFLIX INTERNATIONAL B.V.",
"alias": null,
"currency": "EUR",
"routingCodes": {},
"accountNumber": null,
"iban": "NL91ABNA0417164300",
"bankName": null,
"country": "NL"
},
"reference": "MNDT-2026-000123"
}
},
"connect": {},
"metadata": {}
}Update a Direct Debit Mandate
PATCH /direct-debit-mandates/{id}
An active mandate can be cancelled or rejected; status is required and must be cancelled or rejected. Setting it to cancelled stops further collections from being honoured and requires a cancellationReason; rejected requires a rejectionReason. A pending mandate is activated by the service provider and cannot be updated, and a cancelled or rejected mandate is final. Only status, cancellationReason and rejectionReason can be updated. A successful request returns 202 Accepted with the updated mandate.
cancellationReason is one of user-requested, account-closed, organization-action. rejectionReason is one of user-requested, account-closed, other.
{
"workflow": {
"code": "client.direct"
},
"data": {
"directDebitMandate": {
"status": "cancelled",
"cancellationReason": "user-requested"
}
},
"connect": {},
"metadata": {}
}Get a Direct Debit Mandate
GET /direct-debit-mandates/{id}
The connect.connection object shows the mandate's state at the service provider.
{
"workflow": {},
"data": {
"directDebitMandate": {
"id": "00000000-0000-0000-0000-000000000000",
"owner": "client",
"clientId": "00000000-0000-0000-0000-000000000000",
"accountId": "00000000-0000-0000-0000-000000000000",
"scheme": "sepa",
"flowType": "payment",
"status": "active",
"creditorIdentifier": "DE98ZZZ09999999999",
"creditorAccount": {
"accountHolderName": "NETFLIX INTERNATIONAL B.V.",
"alias": null,
"currency": "EUR",
"routingCodes": {},
"accountNumber": null,
"iban": "NL91ABNA0417164300",
"bankName": null,
"country": "NL"
},
"reference": "MNDT-2026-000123"
}
},
"connect": {
"connection": {
"id": "6654321",
"serviceProvider": "banking-circle",
"status": "active",
"state": "completed-up-to-date",
"method": "built-in"
}
},
"metadata": {}
}List Direct Debit Mandates
GET /direct-debit-mandates
Supported query parameters:
| parameter | description |
|---|---|
| metadata.page.number | 0-indexed, default=0 |
| metadata.page.size | default=20, max=100 |
| data.directDebitMandate.status | one or more of pending, active, cancelled, rejected |
| data.directDebitMandate.clientId | UUID of client |
| data.directDebitMandate.accountId | UUID of account |
| data.directDebitMandate.owner | owner of mandate. one of client, organization |
Mandate Webhooks
Direct Debit webhooks have "direct-debits" in the module field. The following mandate types are sent:
| type | sent when | status |
|---|---|---|
| direct-debit-mandate-created | a mandate is created. A mandate created through the API is pending; a mandate set up at the service provider (for example a Bacs instruction received by ClearBank) is created already active | pending or active |
| direct-debit-mandate-activated | the service provider activates a pending mandate | active |
| direct-debit-mandate-cancelled | the mandate is cancelled through the API or by the service provider | cancelled |
| direct-debit-mandate-rejected | the mandate is rejected through the API or by the service provider | rejected |
Changes of the mandate's connection at the service provider are sent as connection-created and connection-updated, with the same payload and the connection in connect.connection. The connect object is empty until the mandate has a connection.
Fields without a value are not included in the payload.
{
"webhook": {
"module": "direct-debits",
"type": "direct-debit-mandate-created"
},
"data": {
"directDebitMandate": {
"id": "00000000-0000-0000-0000-000000000000",
"owner": "client",
"clientId": "00000000-0000-0000-0000-000000000000",
"accountId": "00000000-0000-0000-0000-000000000000",
"scheme": "sepa",
"flowType": "payment",
"status": "pending",
"creditorIdentifier": "DE98ZZZ09999999999",
"creditorAccount": {
"accountHolderName": "NETFLIX INTERNATIONAL B.V.",
"alias": null,
"currency": "EUR",
"routingCodes": {},
"accountNumber": null,
"iban": "NL91ABNA0417164300",
"bankName": null,
"country": "NL"
},
"reference": "MNDT-2026-000123"
}
},
"connect": {},
"metadata": {}
}{
"webhook": {
"module": "direct-debits",
"type": "direct-debit-mandate-activated"
},
"data": {
"directDebitMandate": {
"id": "00000000-0000-0000-0000-000000000000",
"owner": "client",
"clientId": "00000000-0000-0000-0000-000000000000",
"accountId": "00000000-0000-0000-0000-000000000000",
"scheme": "sepa",
"flowType": "payment",
"status": "active",
"creditorIdentifier": "DE98ZZZ09999999999",
"creditorAccount": {
"accountHolderName": "NETFLIX INTERNATIONAL B.V.",
"alias": null,
"currency": "EUR",
"routingCodes": {},
"accountNumber": null,
"iban": "NL91ABNA0417164300",
"bankName": null,
"country": "NL"
},
"reference": "MNDT-2026-000123"
}
},
"connect": {
"connection": {
"id": "6654321",
"serviceProvider": "banking-circle",
"status": "active",
"state": "completed-up-to-date",
"method": "built-in"
}
},
"metadata": {}
}{
"webhook": {
"module": "direct-debits",
"type": "direct-debit-mandate-cancelled"
},
"data": {
"directDebitMandate": {
"id": "00000000-0000-0000-0000-000000000000",
"owner": "client",
"clientId": "00000000-0000-0000-0000-000000000000",
"accountId": "00000000-0000-0000-0000-000000000000",
"scheme": "sepa",
"flowType": "payment",
"status": "cancelled",
"creditorIdentifier": "DE98ZZZ09999999999",
"creditorAccount": {
"accountHolderName": "NETFLIX INTERNATIONAL B.V.",
"alias": null,
"currency": "EUR",
"routingCodes": {},
"accountNumber": null,
"iban": "NL91ABNA0417164300",
"bankName": null,
"country": "NL"
},
"reference": "MNDT-2026-000123",
"cancellationReason": "user-requested"
}
},
"connect": {
"connection": {
"id": "6654321",
"serviceProvider": "banking-circle",
"status": "cancelled",
"state": "completed-up-to-date",
"method": "built-in"
}
},
"metadata": {}
}{
"webhook": {
"module": "direct-debits",
"type": "direct-debit-mandate-rejected"
},
"data": {
"directDebitMandate": {
"id": "00000000-0000-0000-0000-000000000000",
"owner": "client",
"clientId": "00000000-0000-0000-0000-000000000000",
"accountId": "00000000-0000-0000-0000-000000000000",
"scheme": "sepa",
"flowType": "payment",
"status": "rejected",
"creditorIdentifier": "DE98ZZZ09999999999",
"creditorAccount": {
"accountHolderName": "NETFLIX INTERNATIONAL B.V.",
"alias": null,
"currency": "EUR",
"routingCodes": {},
"accountNumber": null,
"iban": "NL91ABNA0417164300",
"bankName": null,
"country": "NL"
},
"reference": "MNDT-2026-000123",
"rejectionReason": "account-closed"
}
},
"connect": {
"connection": {
"id": "6654321",
"serviceProvider": "banking-circle",
"status": "rejected",
"state": "completed-up-to-date",
"method": "built-in"
}
},
"metadata": {}
}{
"webhook": {
"module": "direct-debits",
"type": "connection-updated"
},
"data": {
"directDebitMandate": {
"id": "00000000-0000-0000-0000-000000000000",
"owner": "client",
"clientId": "00000000-0000-0000-0000-000000000000",
"accountId": "00000000-0000-0000-0000-000000000000",
"scheme": "sepa",
"flowType": "payment",
"status": "active",
"creditorIdentifier": "DE98ZZZ09999999999",
"creditorAccount": {
"accountHolderName": "NETFLIX INTERNATIONAL B.V.",
"alias": null,
"currency": "EUR",
"routingCodes": {},
"accountNumber": null,
"iban": "NL91ABNA0417164300",
"bankName": null,
"country": "NL"
},
"reference": "MNDT-2026-000123"
}
},
"connect": {
"connection": {
"id": "6654321",
"serviceProvider": "banking-circle",
"status": "active",
"state": "completed-up-to-date",
"method": "built-in"
}
},
"metadata": {}
}Direct Debit Payment
Payment Lifecycle
Direct Debit Payments are driven by the service provider and follow the Bacs/SEPA collection cycle. The status field reflects the collection itself:
- pending : the collection has been announced (advance notice) and is awaiting the settlement date.
- completed : the collection settled and the payer account was charged; the linked Outgoing Transfer is
completed. - failed : the payer account was not charged — the collection could not be settled (e.g. insufficient funds) or was returned before the account was charged; the linked Outgoing Transfer is
failed.
A return or refund does not change the status on its own; it is tracked on returnContext.
Return Context
The returnContext object describes whether the payment can be returned and what happened to a return:
| field | description |
|---|---|
| enabled | true when the service provider accepts a return at this stage |
| cutOffDateTime | latest date-time (UTC) a return can be requested, according to the scheme rules |
| reason | the return reason, whether requested by you or raised by the service provider |
| status | processing (return in flight), completed (funds returned) or failed (the return did not go through and can be retried) |
reason is one of account-closed, advance-notice-disputed, amount-not-matched, amount-not-yet-due, insufficient-balance, mandate-not-active, no-account, no-mandate, presentation-overdue, service-user-not-match.
Initiate a Return / Refund
PATCH /direct-debit-payments/{id}
A Direct Debit Payment can be returned or refunded by setting returnContext.status to processing together with a returnContext.reason. Only returnContext can be updated. A successful request returns 202 Accepted with the payment; the IF Platform forwards the return to the service provider and returnContext.status moves to completed or failed once it settles. When the funds are credited back, an Incoming Transfer brings them back to the account.
A return is accepted only when:
- returnContext.enabled is
true, - returnContext.cutOffDateTime has not passed,
- return is not
processingorcompleted(afailedreturn can be retried), - the payment is
pendingorcompleted, orfailedwith afailedreturn.
{
"workflow": {
"code": "client.direct"
},
"data": {
"directDebitPayment": {
"returnContext": {
"status": "processing",
"reason": "advance-notice-disputed"
}
}
},
"connect": {},
"metadata": {}
}Get a Direct Debit Payment
GET /direct-debit-payments/{id}
The connect.connection object shows the payment's state at the service provider.
{
"workflow": {},
"data": {
"directDebitPayment": {
"id": "00000000-0000-0000-0000-000000000000",
"owner": "client",
"clientId": "00000000-0000-0000-0000-000000000000",
"mandateId": "00000000-0000-0000-0000-000000000000",
"accountId": "00000000-0000-0000-0000-000000000000",
"scheme": "sepa",
"status": "completed",
"amount": 10.55,
"currency": "EUR",
"transferDate": "2026-07-22",
"returnContext": {
"enabled": true,
"cutOffDateTime": "2026-09-16T12:00:00",
"reason": null,
"status": null
},
"outgoingTransferId": "00000000-0000-0000-0000-000000000000"
}
},
"connect": {
"connection": {
"id": "130341d8-0000-0000-0000-000000000000",
"serviceProvider": "banking-circle",
"status": "completed",
"state": "completed-up-to-date",
"method": "built-in"
}
},
"metadata": {}
}List Direct Debit Payments
GET /direct-debit-payments
Supported query parameters:
| parameter | description |
|---|---|
| metadata.page.number | 0-indexed, default=0 |
| metadata.page.size | default=20, max=100 |
| data.directDebitPayment.statuses | one or more of pending, completed, failed |
| data.directDebitPayment.clientId | UUID of client |
| data.directDebitPayment.accountId | UUID of account |
| data.directDebitPayment.mandateId | UUID of mandate |
| data.directDebitPayment.owner | owner of payment. one of client, organization |
Payment Webhooks
Direct Debit webhooks have "direct-debits" in the module field. The following payment types are sent:
| type | sent when | status |
|---|---|---|
| direct-debit-payment-created | the service provider announces a collection | pending |
| direct-debit-payment-completed | the collection settles and the payer account is charged | completed |
| direct-debit-payment-failed | the collection fails, or it is returned for insufficient-balance before the payer account is charged | failed |
| direct-debit-payment-returned | a return completes; returnContext.status is completed and returnContext.reason holds the reason | completed if the payer account was charged, otherwise failed |
returnContext.enabled and returnContext.cutOffDateTime depend on the service provider. Banking Circle allows a return from the moment the collection is announced. ClearBank allows it only after settlement, so its direct-debit-payment-created webhook carries enabled: false and no cutOffDateTime.
When a return credits the funds back, incomingTransferId is filled after the direct-debit-payment-returned webhook, once the Incoming Transfer is booked; retrieve the payment to read it.
Changes of the payment's connection at the service provider are sent as connection-created and connection-updated, with the same payload and the connection in connect.connection.
{
"webhook": {
"module": "direct-debits",
"type": "direct-debit-payment-created"
},
"data": {
"directDebitPayment": {
"id": "00000000-0000-0000-0000-000000000000",
"owner": "client",
"clientId": "00000000-0000-0000-0000-000000000000",
"mandateId": "00000000-0000-0000-0000-000000000000",
"accountId": "00000000-0000-0000-0000-000000000000",
"scheme": "sepa",
"status": "pending",
"amount": 10.55,
"currency": "EUR",
"transferDate": "2026-07-22",
"returnContext": {
"enabled": true,
"cutOffDateTime": "2026-09-16T12:00:00",
"reason": null,
"status": null
},
"outgoingTransferId": "00000000-0000-0000-0000-000000000000"
}
},
"connect": {
"connection": {
"id": "130341d8-0000-0000-0000-000000000000",
"serviceProvider": "banking-circle",
"status": "pending",
"state": "completed-up-to-date",
"method": "built-in"
}
},
"metadata": {}
}{
"webhook": {
"module": "direct-debits",
"type": "direct-debit-payment-completed"
},
"data": {
"directDebitPayment": {
"id": "00000000-0000-0000-0000-000000000000",
"owner": "client",
"clientId": "00000000-0000-0000-0000-000000000000",
"mandateId": "00000000-0000-0000-0000-000000000000",
"accountId": "00000000-0000-0000-0000-000000000000",
"scheme": "sepa",
"status": "completed",
"amount": 10.55,
"currency": "EUR",
"transferDate": "2026-07-22",
"returnContext": {
"enabled": true,
"cutOffDateTime": "2026-09-16T12:00:00",
"reason": null,
"status": null
},
"outgoingTransferId": "00000000-0000-0000-0000-000000000000"
}
},
"connect": {
"connection": {
"id": "130341d8-0000-0000-0000-000000000000",
"serviceProvider": "banking-circle",
"status": "completed",
"state": "completed-up-to-date",
"method": "built-in"
}
},
"metadata": {}
}{
"webhook": {
"module": "direct-debits",
"type": "direct-debit-payment-failed"
},
"data": {
"directDebitPayment": {
"id": "00000000-0000-0000-0000-000000000000",
"owner": "client",
"clientId": "00000000-0000-0000-0000-000000000000",
"mandateId": "00000000-0000-0000-0000-000000000000",
"accountId": "00000000-0000-0000-0000-000000000000",
"scheme": "sepa",
"status": "failed",
"amount": 10.55,
"currency": "EUR",
"transferDate": "2026-07-22",
"returnContext": {
"enabled": true,
"cutOffDateTime": "2026-09-16T12:00:00",
"reason": "insufficient-balance",
"status": "completed"
},
"outgoingTransferId": "00000000-0000-0000-0000-000000000000"
}
},
"connect": {
"connection": {
"id": "130341d8-0000-0000-0000-000000000000",
"serviceProvider": "banking-circle",
"status": "returned",
"state": "completed-up-to-date",
"method": "built-in"
}
},
"metadata": {}
}{
"webhook": {
"module": "direct-debits",
"type": "direct-debit-payment-returned"
},
"data": {
"directDebitPayment": {
"id": "00000000-0000-0000-0000-000000000000",
"owner": "client",
"clientId": "00000000-0000-0000-0000-000000000000",
"mandateId": "00000000-0000-0000-0000-000000000000",
"accountId": "00000000-0000-0000-0000-000000000000",
"scheme": "sepa",
"status": "completed",
"amount": 10.55,
"currency": "EUR",
"transferDate": "2026-07-22",
"returnContext": {
"enabled": true,
"cutOffDateTime": "2026-09-16T12:00:00",
"reason": "advance-notice-disputed",
"status": "completed"
},
"outgoingTransferId": "00000000-0000-0000-0000-000000000000"
}
},
"connect": {
"connection": {
"id": "130341d8-0000-0000-0000-000000000000",
"serviceProvider": "banking-circle",
"status": "returned",
"state": "completed-up-to-date",
"method": "built-in"
}
},
"metadata": {}
}{
"webhook": {
"module": "direct-debits",
"type": "connection-updated"
},
"data": {
"directDebitPayment": {
"id": "00000000-0000-0000-0000-000000000000",
"owner": "client",
"clientId": "00000000-0000-0000-0000-000000000000",
"mandateId": "00000000-0000-0000-0000-000000000000",
"accountId": "00000000-0000-0000-0000-000000000000",
"scheme": "sepa",
"status": "completed",
"amount": 10.55,
"currency": "EUR",
"transferDate": "2026-07-22",
"returnContext": {
"enabled": true,
"cutOffDateTime": "2026-09-16T12:00:00",
"reason": null,
"status": null
},
"outgoingTransferId": "00000000-0000-0000-0000-000000000000"
}
},
"connect": {
"connection": {
"id": "130341d8-0000-0000-0000-000000000000",
"serviceProvider": "banking-circle",
"status": "completed",
"state": "completed-up-to-date",
"method": "built-in"
}
},
"metadata": {}
}Updated about 7 hours ago