GoCardlessDeveloper Docs
Create a sandbox account

Importing Mandates#

View as Markdown

Why import mandates?#

Importing mandates allows you to migrate existing Direct Debit authorisations from another provider into GoCardless without requiring your customers to re-authorise. Once imported, you can manage and collect against them alongside any mandates created directly through GoCardless.

How it works#

  1. You create a Mandate Import specifying the scheme
  2. You add an entry for each mandate you want to import
  3. You submit the import for review
  4. GoCardless reviews and approves the import
  5. Mandates are created, and you reconcile them with your internal records

Step-by-step guide#

Create a Mandate Import#

POST https://api.gocardless.com/mandate_imports HTTP/1.1
{
  "mandate_imports": {
    "scheme": "sepa_core"
  }
}
 
HTTP/1.1 201 (Created)
Location: /mandate_imports/IM000010790WX1
{
"mandate_imports": {
"id": "IM000010790WX1",
"scheme": "sepa_core",
"status": "created",
"created_at": "2018-03-12T14:03:04.000Z"
}
}
$client = new \GoCardlessPro\Client(array(
'access_token' => 'your_access_token_here',
'environment' => \GoCardlessPro\Environment::SANDBOX
));
 
$mandateImport = $client->mandateImports()->create([
"params" => ["scheme" => "sepa_core"]
]);
import gocardless_pro
client = gocardless_pro.Client(access_token="your_access_token_here", environment='sandbox')
 
mandate_import = client.mandate_imports.create(params={
"scheme": "sepa_core"
});
@client = GoCardlessPro::Client.new(
access_token: "your_access_token",
environment: :sandbox
)
 
mandate_import = @client.mandate_imports.create(params: {
scheme: "sepa_core"
})
import static com.gocardless.GoCardlessClient.Environment.SANDBOX;
import static com.gocardless.services.MandateImportService.MandateImportCreateRequest.Scheme.SEPA_CORE;
 
String accessToken = "your_access_token_here";
 
GoCardlessClient client = GoCardlessClient
.newBuilder(accessToken)
.withEnvironment(SANDBOX)
.build();
 
MandateImport mandateImport = client.mandateImports().create()
.withScheme(SEPA_CORE)
.execute();
const accessToken = "your_access_token";
const constants = require("gocardless-nodejs/constants");
const client = require("gocardless-nodejs")(accessToken, constants.Environments.Sandbox);
 
const mandateImport = await client.mandateImports.create({
  scheme: "sepa_core",
});
String accessToken = "your_access_token";
GoCardlessClient gocardless = GoCardlessClient.Create(accessToken, Environment.SANDBOX);
 
var importRequest = new GoCardless.Services.MandateImportCreateRequest()
{
    Scheme = MandateImportCreateRequest.MandateImportScheme.SepaCore
};
 
var importResponse = await gocardless.MandateImports.CreateAsync(importRequest);
GoCardless.Resources.MandateImport mandateImport = importResponse.MandateImport;
ctx := context.TODO()
mandateImportCreateParams := goCardless.MandateImportCreateParams{
	Scheme: "sepa_core",
}
client.MandateImports().Create(ctx, mandateImportCreateParams)

Hold onto the ID, you'll need it to add entries and submit.

Add Mandate Import Entries#

Add one entry per mandate. Each entry requires:

  • links.mandate_import: the ID from Step 1
  • customer: identifying details for the customer
  • bank_account: account holder name and either an IBAN or [local bank details]
  • record_identifier: Your own reference for this mandate (see below)

SEPA mandates only: You must also provide amendment details, the original mandate reference, creditor ID, and creditor name from your previous provider. These are required by the SEPA scheme to maintain a valid audit trail.

$mandateImportEntry = $client->mandateImportEntries()->create([
  "params" => [
    "links" => [
      "mandate_import" => $mandateImport->id
    ],
    "record_identifier" => "bank-file.xml/line-1",
    "customer" => [
      "company_name" => "Théâtre du Palais-Royal",
      "email" => "moliere@tdpr.fr"
    ],
    "bank_account" => [
      "account_holder_name" => "Jean-Baptiste Poquelin",
      "iban" => "FR14BARC20000055779911"
    ],
    // Amendment details are required for SEPA only
    "amendment" => [
      "original_mandate_reference" => "REFNMANDATE",
      "original_creditor_id" => "FR123OTHERBANK",
      "original_creditor_name" => "Amphitryon"
    ]
  ]
]);
mandate_import_entry = client.mandate_import_entries.create(params={
  "links": {
    "mandate_import": mandate_import.id
  },
  "record_identifier": "bank-file.xml/line-1",
  "customer": {
    "company_name": "Théâtre du Palais-Royal",
    "email": "moliere@tdpr.fr"
  },
  "bank_account": {
    "account_holder_name": "Jean-Baptiste Poquelin",
    "iban": "FR14BARC20000055779911"
  },
  # Amendment details are required for SEPA only
  "amendment": {
    "original_mandate_reference": "REFNMANDATE",
    "original_creditor_id": "FR123OTHERBANK",
    "original_creditor_name": "Amphitryon"
  }
})
mandate_import_entry = @client.mandate_import_entries.create(params: {
  links: {
    mandate_import: mandate_import.id
  },
  record_identifier: "bank-file.xml/line-1",
  customer: {
    company_name: "Théâtre du Palais-Royal",
    email: "moliere@tdpr.fr"
  },
  bank_account: {
    account_holder_name: "Jean-Baptiste Poquelin",
    iban: "FR14BARC20000055779911"
  },
  # Amendment details are required for SEPA only
  amendment: {
    original_mandate_reference: "REFNMANDATE",
    original_creditor_id: "FR123OTHERBANK",
    original_creditor_name: "Amphitryon"
  }
})
MandateImportEntry mandateImportEntry = client.mandateImportEntries().create()
  .withCustomerCompanyName("Théâtre du Palais-Royal")
  .withCustomerEmail("moliere@tdpr.fr")
  .withBankAccountAccountHolderName("Jean-Baptiste Poquelin")
  .withBankAccountIban("FR14BARC20000055779911")
  // Amendment details are required for SEPA only
  .withAmendmentOriginalMandateReference("REFNMANDATE")
  .withAmendmentOriginalCreditorId("FR123OTHERBANK")
  .withAmendmentOriginalCreditorName("Amphitryon")
  .withLinksMandateImport(mandateImport.getId())
  .execute();
const request = {
  customer: {
    company_name: "Théâtre du Palais-Royal",
    email: "moliere@tdpr.fr",
  },
  bank_account: {
    account_holder_name: "Jean-Baptiste Poquelin",
    iban: "FR14BARC20000055779911",
  },
  // Amendment details are required for SEPA only
  amendment: {
    original_mandate_reference: "REFNMANDATE",
    original_creditor_id: "FR123OTHERBANK",
    original_creditor_name: "Amphitryon",
  },
  links: {
    mandate_import: mandateImport.id,
  },
};
 
const mandateImportEntry = await client.mandateImportEntries.create(request);
var request = new GoCardless.Services.MandateImportEntryCreateRequest()
{
  Customer = new GoCardless.Services.MandateImportEntryCreateRequest.MandateImportEntryCustomer()
  {
    CompanyName = "Théâtre du Palais-Royal"
    Email = "moliere@tdpr.fr"
  }
  BankAccount = new GoCardless.Services.MandateImportEntryCreateRequest.MandateImportEntryBankAccount()
  {
    AccountHolderName = "Jean-Baptiste Poquelin"
    Iban = "FR14BARC20000055779911"
  }
  // Amendment details are required for SEPA only
  Amendment = new GoCardless.Services.MandateImportEntryCreateRequest.MandateImportEntryAmendment()
  {
    OriginalMandateReference = "REFNMANDATE"
    OriginalCreditorId = "FR123OTHERBANK"
    OriginalCreditorName = "Amphitryon"
  }
  Links = new GoCardless.Services.MandateImportEntryCreateRequest.MandateImportEntryLinks()
  {
    MandateImport = mandateImport.Id
  }
};
 
var importResponse = await gocardless.MandateImportEntries.CreateAsync(request);
GoCardless.Resources.MandateImportEntry entry = importResponse.MandateImportEntry;
ctx := context.TODO()
mandateImportEntryCreateParams := gocardless.MandateImportEntryCreateParams{
	Customer: gocardless.MandateImportEntryCreateParamsCustomer{
		CompanyName: "Théâtre du Palais-Royal",
		Email:       "moliere@tdpr.fr",
	},
	BankAccount: gocardless.MandateImportEntryCreateParamsBankAccount{
		AccountHolderName: "Jean-Baptiste Poquelin",
		Iban:              "FR14BARC20000055779911",
	},
	Amendment: &gocardless.MandateImportEntryCreateParamsAmendment{
		OriginalMandateReference: "REFMANDATE",
		OriginalCreditorId:       "FR123OTHERBANK",
		OriginalCreditorName:     "Amphitryon",
	},
	Links: gocardless.MandateImportEntryCreateParamsLinks{
		MandateImport: "MandateImportId",
	},
}
client.MandateImportEntries().Create(ctx, mandateImportEntryCreateParams)
POST https://api.gocardless.com/mandate_import_entries HTTP/1.1
{
  "mandate_import_entries": {
    "links": {
      "mandate_import": "IM000010790WX1"
    },
    "record_identifier": "bank-file.xml/line-1",
    "customer": {
      "company_name": "Théâtre du Palais-Royal",
      "email": "moliere@tdpr.fr"
    },
    "bank_account": {
      "account_holder_name": "Jean-Baptiste Poquelin",
      "iban": "FR14BARC20000055779911"
    },
    "amendment": {
      "original_mandate_reference": "REFNMANDATE",
      "original_creditor_id": "FR123OTHERBANK",
      "original_creditor_name": "Amphitryon"
    }
  }
}
 
HTTP/1.1 201 (Created)
{
  "mandate_import_entries": {
    "record_identifier": "bank-file.xml/line-1",
    "created_at": "2018-03-03T00:00:00Z",
    "links": {
      "mandate_import": "IM000010790WX1"
    }
  }
}

Repeat for each mandate. If you provide invalid data, the API returns a descriptive error for that entry — fix it and retry before submitting.

SEPA example with amendment details:

  curl -X POST https://api.gocardless.com/mandate_import_entries \
    -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
    -H "Content-Type: application/json" \
    -d '{
      "mandate_import_entries": {
        "links": {
          "mandate_import": "IM123"
        },
        "record_identifier": "your-internal-customer-id-456",
        "customer": {
          "company_name": "Théâtre du Palais-Royal",
          "email": "moliere@tdpr.fr"
        },
        "bank_account": {
          "account_holder_name": "Jean-Baptiste Poquelin",
          "iban": "FR14BARC20000055779911"
        },
        "amendment": {
          "original_mandate_reference": "REFNMANDATE",
          "original_creditor_id": "FR123OTHERBANK",
          "original_creditor_name": "Amphitryon"
        }
      }
    }

Submit for Review#

Once all entries are added, submit the import. All imports are reviewed by GoCardless before processing to prevent fraudulent use.

$client->mandateImports()->submit($mandateImport->id);
client.mandate_imports.submit(mandate_import.id)
@client.mandate_imports.submit(mandate_import.id)
client.mandateImports().submit(mandateImport.getId()).execute();
await client.mandateImports.submit(mandateImport.id);
await gocardless.MandateImports.SubmitAsync(mandateImport.Id);
ctx := context.TODO()
mandateImportSubmitParams := goCardless.MandateImportSubmitParams{}
client.MandateImports().Submit(ctx, mandateImport.Id, mandateImportSubmitParams)
POST https://api.gocardless.com/mandate_imports/IM000010790WX1/actions/submit HTTP/1.1
 
HTTP/1.1 200 (OK)
{
  "mandate_imports": {
    "id": "IM000010790WX1",
    "scheme": "bacs",
    "status": "submitted",
    "created_at": "2018-03-12T14:03:04.000Z"
  }
}

Await Approval#

GoCardless reviews all submitted imports before processing. During this time the import status will be submitted.

Common reasons for rejection:

  • Entries contain invalid or mismatched bank details
  • SEPA amendment details are missing or incorrect
  • The scheme specified doesn't match the bank accounts provided

If your import is rejected, GoCardless will contact you with details. You'll need to create a new import with corrected entries.

Reconcile your records#

When the import is approved, GoCardless processes the mandates. You'll receive webhooks as each mandate is created, the same events as mandates created through the standard API:

EventMeaning
mandate.createdMandate successfully imported
mandate.activeMandate active and ready to collect against
mandate.failedMandate could not be created (e.g. invalid bank details)

To map your internal records to the new GoCardless IDs, retrieve the entries once the import status is processed. Match each entry using the record_identifier you set in Step 2:

$entries = $client->mandateImportEntries()->all([
  "params" => ["mandate_import" => "IM000010790WX1"]
]);
 
foreach ($entries as $i => $entry) {
echo $entry->record_identifier;
echo $entry->links->customer;
echo $entry->links->customer_bank_account;
echo $entry->links->mandate;
}
response = client.mandate_import_entries.all(
params={ "mandate_import": mandate_import.id }
).records
 
for entry in records:
print(entry.record_identifier)
print(entry.links.customer)
print(entry.links.customer_bank_account)
print(entry.links.mandate)
@client.mandate_import_entries.all(
params: {
"mandate_import" => mandate_import.id
}
).each do |entry|
puts entry.record_identifier
puts entry.links.customer
puts entry.links.customer_bank_account
puts entry.links.mandate
end
for (MandateImportEntry entry : client.mandateImportEntries().all().withMandateImport("IM000010790WX1").execute()) {
System.out.println(entry.getRecordIdentifier());
System.out.println(entry.getLinks().getCustomer());
System.out.println(entry.getLinks().getCustomerBankAccount());
System.out.println(entry.getLinks().getMandate());
}
const request = {
  mandate_import: mandateImport.id,
};
 
for await (let entry of client.mandateImportEntries.all(request)) {
  console.log(entry.record_identifier);
  console.log(entry.links.customer);
  console.log(entry.links.customer_bank_account);
  console.log(entry.links.mandate);
}
var request = new GoCardless.Services.MandateImportEntryListRequest()
{
    MandateImport = mandateImport.Id
};
 
var response = gocardless.MandateImportEntries.All(request);
foreach (GoCardless.Resources.MandateImportEntry entry in response)
{
    Console.WriteLine(entry.RecordIdentifier);
    Console.WriteLine(entry.Links.Customer);
    Console.WriteLine(entry.Links.CustomerBankAccount);
    Console.WriteLine(entry.Links.Mandate);
}
ctx := context.TODO()
mandateImportEntryListParams := goCardless.MandateImportEntryListParams{
	MandateImport: "IM000010790WX1",
}
mandateImportEntries, err := client.MandateImportEntries().List(ctx, mandateImportEntryListParams)
if err != nil {
	fmt.Println("err is %s", err.Error())
}
for entry := range mandateImportEntries {
	fmt.Println(entry.RecordIdentifier)
	fmt.Println(entry.Links.Customer)
	fmt.Println(entry.Links.CustomerBankAccount)
	fmt.Println(entry.Links.Mandate)
}
GET https://api.gocardless.com/mandate_import_entries?mandate_import=IM000010790WX1 HTTP/1.1
 
HTTP/1.1 200 (OK)
{
  "mandate_import_entries": [
    {
      "record_identifier": "bank-file.xml/line-2",
      "created_at": "2018-03-03T00:00:01Z",
      "links": {
        "mandate_import": "IM000010790WX1",
        "customer": "CU456",
        "customer_bank_account": "BA456",
        "mandate": "MD456"
      }
    },
    {
      "record_identifier": "bank-file.xml/line-1",
      "created_at": "2018-03-03T00:00:00Z",
      "links": {
        "mandate_import": "IM000010790WX1",
        "customer": "CU123",
        "customer_bank_account": "BA123",
        "mandate": "MD123"
      }
    }
  ],
  "meta": {
    "cursors": {
      "before": null,
      "after": null
    },
    "limit": 50
  }
}

Use the mandate ID from each entry to start collecting payments. See One-off payments and Recurring payments.

What's next?#