In the previous step, we implemented a simple webhook handler. In this section, we’ll build on that and learn how to handle the full mandate lifecycle by listening for the relevant webhooks, as well as thinking about how to support mandates set up outside of our integration.

In the API reference, you’ll find a full list of all the events that can happen to mandates (and other resources too). We’ll take you through the most important cases here.

You don’t necessarily need to handle all of the possible events: for example, when a mandate is sent to the banks for processing, this doesn’t necessarily need to be reflected in your product, but your users will definitely want to know when a customer cancels. Below, we’ll cover the most important paths to be able to deal with.

Every partner integration is different, so this guide can’t tell you exactly what to do, but we can explain the cases you should handle and give you some ideas of how to provide the best experience for your users.

Mandate cancellations

Mandates can be cancelled at any time. For example, end customers can cancel their mandates by contacting their bank, or your user can cancel a mandate from their GoCardless Dashboard. When this happens, you’ll receive a webhook with a resource_type of “mandates”, and an action of “cancelled”.

We’ve already written some sample code in the previous step for handling mandate cancellations event.

You should build on that basic framework, thinking about what it makes sense to do in your product in response to a cancellation - for example, you might want to:

  • update your UI, so your user can see that the end customer no longer has an active mandate
  • deactivate the end customer’s access to the service being provided
  • email your user to let them know that no more payments will be collected until a new mandate is set-up
As discussed in the "Setting up Direct Debit mandates" section, you'll most likely want a dedicated table in your database for GoCardless mandates.

This will help you keep track of mandates and their statuses through the mandate lifecycle, and to link those mandates to an internal customer record, representing the person paying your user.

The details attribute on the event includes an origin, cause and description to help you understand why and how the mandate came to be cancelled - you can see a full list of possible attributes for this and other webhook events in the API reference.

You can also cancel mandates yourself via the API if an end customer should no longer be charged. You should only do this if it’s requested by your user:

<?php
require 'vendor/autoload.php';

$client = new \GoCardlessPro\Client([
    'access_token' => $currentUser->gocardlessAccessToken,
    'environment' => \GoCardlessPro\Environment::SANDBOX
]);

$mandate = $client->mandates()->cancel($mandate_id);
print("Status: " . $mandate->status);
import gocardless_pro

def cancel_mandate(current_user, mandate_id):
    client = gocardless_pro.Client(
        access_token=current_user.gocardless_access_token,
        environment='sandbox'
    )

    mandate = client.mandates.cancel(mandate_id)
    print("Status: {}".format(mandate.status))
require 'gocardless_pro'

def cancel_mandate(current_user, mandate_id)
  client = GoCardlessPro::Client.new(
    access_token: current_user.gocardless_access_token,
  	environment: :sandbox
  )

  mandate = client.mandates.cancel(mandate_id)
  puts "Status: #{mandate.status}"
end
package com.myInvoicingApp;


import com.gocardless.GoCardlessClient;
import com.gocardless.resources.Mandate;
import com.myInvoicingApp.User;

public class CancelMandate {
    public static void main(User currentUser, String mandateID) {
        GoCardlessClient client = GoCardlessClient
            .newBuilder(CurrentUser.gocardlessAccessToken)
            .withEnvironment(GoCardlessClient.Environment.SANDBOX)
            .build();

        Mandate mandate = client.mandates().cancel(mandateID).execute();
        System.out.printf("Status: %s%n", mandate.getStatus());
    }
}
const string mandateId = "MD0000123ABC";
Console.WriteLine($"Cancelling mandate: {mandateId}");

var cancelResponse = await client.Mandates.CancelAsync(mandateId);
var mandate = cancelResponse.Mandate;
Console.WriteLine($"Status: {mandate.Status}");

Mandates can also, in rare cases, expire: when you see the expired action, you’ll probably want to take similar steps as you would for a cancellation.

Mandate failures

Once you’ve created a mandate, it takes a couple of days to be submitted to the bank and fully set-up. In certain cases (for example if the customer’s bank account doesn’t support Direct Debit or the bank account has been closed), the mandate setup will fail, and you’ll receive an event with resource_type “mandates” and action “failed”.

When a mandate fails to be set up, you could consider:

  • emailing the end customer to tell them what went wrong, and suggesting they try again with a different bank account (the cause under the event’s details includes an explanation of what went wrong - either invalid_bank_details, bank_account_closed, bank_account_transferred, direct_debit_not_enabled or other)
  • updating your UI, so your user can see that the end customer no longer has an active mandate

Read more about reporting the status of mandates to your users in our user experience guide.

Mandate replacements

If one of your users switches between the GoCardless Standard, Plus and Pro packages, they’ll get their own “scheme identifier” - this means that their own name appears on their customers’ bank statements instead of “GoCardless.com”.

When this happens, GoCardless will move their existing mandates from the GoCardless shared scheme identifier to the user’s own personal one.

For Bacs (UK) mandates, this leads to a change in the mandates’ IDs. You will be alerted by being sent a webhook event with resource_type “mandates” and action “replaced”. The links[new_mandate] attribute will include the ID of the new mandate. If you try to create a payment or subscription against the mandate, you’ll get an error with the “invalid_state” type and the “mandate_replaced” reason, again with a links[new_mandate] attribute.

You should update the mandate ID recorded in your database, as you’ll need to use the new mandate’s ID to charge the customer in the future.

Supporting mandates set up outside of your product

We’ve already looked at setting up new Direct Debits through your product.

To provide the best experience, you should consider allowing your users to “import” mandates set up outside your product so they can bill their customers within your product. There are three reasons why your users might have mandates in this situation:

  • Existing user of GoCardless: It’s likely that some of your users will have already been using GoCardless before connecting it to your product, so they may already have Direct Debit mandates set up with their customers.
  • Bulk changes: If your user was previously using Direct Debit through another provider, they, subject to availability, can “bulk change” their existing mandates from their existing provider to GoCardless.
  • Mixing and matching ways of using GoCardless: Your users might choose to add customers using our Dashboard, our API (for example on their website) or using another integration to better match with their workflow.

You can support importing mandates in two main ways:

  • Automated imports: Fetch a list of the user’s mandates using the Mandates API, then match the customers’ personal details (given_name, family_name, company_number and email) against your application’s internal records to automatically suggest matches, allowing the user to approve these pairings or manually correct them if incorrect (see below for more detailed instructions)
  • Manual imports: Allow users to export a list of customers from your product as a spreadsheet, fill in the mandate IDs from GoCardless, and then upload the completed spreadsheet or email it back to you so you can update your internal records

We recommend supporting automated imports - this makes it as easy as possible for your customers to get set up and then get the best value out of your integration, whilst keeping manual administrative work for you to a minimum.

Building automatic mandate matching

Automatically loading and matching existing Direct Debit mandates provides the best customer experience, making importing your users’ existing customers an easy and integrated part of the process of connecting GoCardless to your product.

We’d suggest building a flow like this:

  1. Prepare a database table which links GoCardless mandates to one of your users, and includes columns for the customer’s given name, family name, company name, email, the GoCardless mandate ID and the GoCardless customer ID.
  2. Load all mandates using the API, filtering for those in statuses “active”, “submitted” and “pending_submission”.
  3. For each of them, load the customer using the customer ID in links[customer], so you have the details for both the mandate and the customer it’s attached to.
  4. Automatically try to match the GoCardless mandates against customer records in your product, for example using the names or email, and record these suggested matches in your database. You’ll need some way of storing in your database suggested matches between GoCardless mandates and your product’s internal customer records, and what match, if any, your user has picked.
  5. Provide a UI where your user can confirm the suggested pairings between mandates and customers, or override the suggestions where they’re incorrect, picking another mandate
  6. Use webhooks to track when new mandates are created or existing ones become inactive and then, following steps 3-5 above, record them. This will allow you to support users “bulk changing” mandates to GoCardless from another Direct Debit provider or adding customers outside of your product throughout the time they’re using your software, rather than this just being a one-off process when the user first connects their GoCardless account to your product.

In addition to importing existing mandates, you may want to consider querying your user’s GoCardless account for existing subscriptions, payments, refunds, and payouts to ensure that your product can reflect a complete record of your user’s ongoing Direct Debit activity.

If your user has previously used automatically recurring subscriptions and your integration uses single payment requests (we’ll explain the difference on the next page), you may want to silently cancel the existing subscriptions - for help with this, get in touch with our API support team.