GoCardlessDeveloper Docs
Create a sandbox account

Supporting mandates set up outside of your product#

View as Markdown

Some of your users will already have Direct Debit mandates set up with their payers. Let them link those mandates to customer records in your platform, so they can collect payments straight away.

Existing GoCardless users

Some of your users already use GoCardless, and have Direct Debit mandates set up with their payers before connecting to your platform.

Bulk changes

Your users can move their existing mandates from another Direct Debit provider to GoCardless in a bulk change (subject to availability).

Mixed usage

Your users might set up some mandates in the GoCardless dashboard, through the API or through another integration, to suit how they work.

Choose how to import mandates#

  • Automatic matching (recommended): your platform lists your user's mandates and links them to your customer records. Your user only reviews what can't be matched
  • Manual import: your user exports their customers from your platform as a spreadsheet, adds the GoCardless mandate IDs and uploads it back
Tip

We recommend automatic matching. It makes it as easy as possible for your users to get set up and get the most from your integration, while keeping manual admin to a minimum.

Build automatic mandate matching#

1. Retrieve your user's mandates#

When your user clicks "Import mandates" after connecting their GoCardless account, your platform should immediately fetch their mandates using their saved access token. Make a List mandates request, filter by the statuses you want to import, and set the include parameter to customer,bank_account so that the linked customers and bank accounts are returned in the same response.

GET /mandates?status=active,submitted,pending_submission&include=customer,bank_account&limit=500

Importing mandates with the active, submitted and pending_submission statuses covers every mandate that is, or is about to be, usable for collecting payments.

The linked customers and bank accounts are returned in top-level linked.customers and linked.bank_accounts arrays, alongside the mandates array. Look up each mandate's customer by matching mandates[].links.customer against linked.customers[].id, and its bank account by matching mandates[].links.customer_bank_account against linked.bank_accounts[].id:

{
  "mandates": [
    {
      "id": "MD123",
      "status": "active",
      "links": {
        "customer_bank_account": "BA123",
        "creditor": "CR123",
        "customer": "CU123"
      },
      ...
    }
  ],
  "meta": {
    "cursors": {
      "before": null,
      "after": null
    },
    "limit": 500
  },
  "linked": {
    "customers": [
      {
        "id": "CU123",
        "email": "janedoe@email.com",
        "given_name": "Jane",
        "family_name": "Doe",
        "postal_code": "123 ABC",
        "metadata": {},
        ...
      }
    ],
    "bank_accounts": [
      {
        "id": "BA123",
        "account_holder_name": "Jane Doe",
        "account_number_ending": "11",
        "bank_name": "BARCLAYS BANK PLC"
      }
    ]
  }
}
Tip

include saves you a request per mandate, and each resource helps at a different stage. customer gives you the payer's email, metadata and name, so you can match them to your records. bank_account gives you the account holder name, bank name and account ending, so your user can tell payers apart when they review matches, for example two payers with the same name.

The response is paginated. Follow the after cursor in meta.cursors until there are no more pages, so that you retrieve every mandate. See cursor pagination.

2. Match mandates to your customer records#

Once the data is retrieved, run automated matching. The matching logic is tiered: each tier is attempted in order, and the first successful match wins. Unresolved records fall through to the next tier.

Skip any mandate that is already linked to a customer in your platform, so that running the import again never creates duplicate links. Users with a lot of mandates can take a while to process, so run matching as a background job rather than within a single web request, to avoid request timeouts.

Tier 1: Exact email match#

Compare customer.email (GoCardless) against the customer's email in your platform. This is the highest-confidence match and will resolve the majority of records. Treat an email that matches exactly one record in your platform as confirmed, without requiring your user to review it.

GC customer.email == your customer's email (exactly one record)
 
→  Matched automatically āœ“

Don't match automatically when the email is blank, or when it matches more than one record in your platform, as it isn't possible to tell which record is correct. Move on to the next tier instead.

Tier 2: Metadata match#

Prerequisite

This tier only works if the GoCardless customer has your customer ID in its metadata, under the key you chose (we've used partner_id for example). It's saved when you set up mandates or in an earlier import.

If no email match is found, check whether the GoCardless customer record contains your metadata key (for example partner_id) with a value that corresponds to a customer ID in your platform.

GC customer.metadata['partner_id'] == your customer's ID   # or the key you chose
 
→  Matched automatically āœ“

Tier 3: Fuzzy name and postal code match (optional)#

If neither of the above tiers produces a match, attempt a fuzzy match on the combination of the customer's full name and postal code.

GC (given_name + family_name + postal_code) ā‰ˆ your customer's (name + postal_code)
 
→  Suggested match, flagged for review

Show fuzzy matches in the summary as "Suggested matches" rather than automatic matches. Your user should review and confirm these before they're linked.

Records that fail all tiers are marked "Unmatched" and need your user to match them manually.

3. Let your user review the results#

Once matching completes, show your user a summary of what was matched automatically and what needs their attention:

StatusCountAction required
Matched automatically85None. Show them for reference, so your user can unmatch any that are wrong.
Suggested matches6Your user confirms the match or picks a different customer record
Unmatched5Your user matches the mandate to a customer record

You'll only have suggested matches if you use name and postcode matching.

For each suggested or unmatched mandate, show the payer's name, email, bank name and account ending, and the mandate reference, alongside a search or dropdown of your customer records. This helps your user pick the right record.

Your user should be able to:

  • Confirm a suggested match
  • Pick a different customer record for a suggested match
  • Match an unmatched mandate to a customer record
  • Skip a mandate that shouldn't be linked, for example for a payer who's no longer a customer
  • Unmatch a mandate linked to the wrong record, and match it again

When a mandate is linked, automatically or by your user:

  1. Save the mandate ID, the GoCardless customer ID and how it was matched against your customer record. You'll use the mandate ID to collect payments
  2. Add your customer ID to the GoCardless customer's metadata, using the same key every time (these docs use partner_id). The next import then matches it straight away

Updating metadata replaces the whole object. To keep keys your user or other integrations set, get the customer first, add your key to its metadata, then update the customer. A customer can have up to 3 metadata keys.

You only need the bank details from linked.bank_accounts for display. Don't store full bank details.

When your user unmatches a mandate, remove the link from your record and your key (partner_id in this example) from the customer's metadata, in the same way, so the next import doesn't link it again.

5. Keep matching new mandates#

Your user can set up mandates outside your product after their first import. When you receive a mandate webhook for a mandate you haven't saved:

  1. Get the mandate and its customer. Don't rely on the webhook body
  2. Skip it if its status is failed, cancelled, expired, consumed or blocked
  3. Match it as in step 2, then link it or add it to review

Webhooks can arrive in any order. If a mandate you set up through a Billing Request matched by email first, let the link from the fulfilled webhook take over.

Edge cases#

CaseWhat to do
Your user has no mandatesShow an empty state. Don't show the review list.
You hit the rate limitBack off and retry from the last after cursor. See rate limiting.
Your access token stops workingAsk your user to reconnect their account.
A linked mandate is replacedUpdate the saved mandate ID to links.new_mandate from the replaced webhook.
A linked mandate is cancelled, fails or expiresKeep the link, update the state and show the new status. Let your user ask the payer for a new mandate. See mandate webhooks.

What's next?#