GoCardlessDeveloper Docs
Create a sandbox account

Mandate Events#

View as Markdown

Mandate events are webhook notifications that GoCardless sends whenever a mandate's status changes. This guide covers the key events you should handle and what to do when they occur.

A webhook is a request that GoCardless sends to your server to alert you of an event. Adding support for webhooks allows you to receive real-time notifications from GoCardless when things happen in your account, so you can take automated actions in response.

Mandate lifecycle#

A mandate moves through the following statuses:

StatusMeaning
pending.submissionMandate created, not yet sent to the banks
submittedSent to the banks for processing
activeConfirmed by the banks, ready to collect against
failedCould not be set up (e.g. invalid bank details)
cancelledCancelled by the customer, merchant, or GoCardless
expiredExpired due to inactivity (scheme-dependent)
consumedUsed for a one-off payment and can no longer be reused

Events to handle#

Mandate cancelled#

Mandates can be cancelled by the customer via their bank, by you via the API, or by GoCardless. When this happens you'll receive:

{
  "events": {
    "id": "EV123",
    "resource_type": "mandates",
    "action": "cancelled",
    "links": {
      "mandate": "MD123"
    },
    "details": {
      "origin": "bank",
      "cause": "bank_account_closed",
      "description": "The bank account for this mandate has been closed."
    }
  }
}

The details.cause field tells you why the mandate was cancelled. Common values:

CauseMeaning
bank_account_closedCustomer's bank account has been closed
invalid_bank_detailsBank details were incorrect
direct_debit_not_enabledAccount doesn't support Direct Debit
customer_approval_deniedCustomer cancelled at their bank
mandate_cancelledCancelled via the GoCardless dashboard or API

When you receive this event you should:

  • Update the mandate status in your database
  • Prevent further payment attempts against this mandate
  • Notify the customer that their Direct Debit has been cancelled and prompt them to set up a new one if needed

Cancelling a mandate yourself:

If you need to cancel a mandate programmatically:

<?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 mandateId = "MD0000123ABC";
console.log(`Cancelling mandate: ${mandateId}`);

const mandate = await client.mandate.cancel(mandateId);
console.log(`Status: ${mandate.status}`);
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}");
ctx := context.TODO()
mandateCancelParams := gocardless.MandateCancelParams{}
mandate, err := client.Mandates().Cancel(ctx, mandateId, mandateCancelParams)
fmt.Printf("Status: %s%n", mandate.Status)

Only cancel a mandate if the customer has explicitly requested it.

Mandate failed#

If the mandate cannot be set up; for example because the bank account doesn't support Direct Debit or has been closed, you'll receive:

{
  "events": {
    "id": "EV124",
    "resource_type": "mandates",
    "action": "failed",
    "links": {
      "mandate": "MD123"
    },
    "details": {
      "origin": "bank",
      "cause": "bank_account_closed",
      "description": "The bank account for this mandate has been closed."
    }
  }
}

When you receive this event you should:

  • Update the mandate status in your database
  • Notify the customer and prompt them to set up a new mandate with a different bank account

Use details.cause to tailor your error message, the same cause values apply as for cancellations above

Mandate expired#

Mandates can expire after a period of inactivity under certain schemes. Treat an expired event the same way as a cancelled event, update your records and prompt the customer to set up a new mandate if needed.

{
  "events": {
    "resource_type": "mandates",
    "action": "expired",
    "links": {
      "mandate": "MD123"
    }
  }
}

Mandate replaced#

When a merchant enables the Merchant name on bank statement add-on, GoCardless moves their mandates from the GoCardless shared scheme identifier to the merchant's own identifier. For BACS mandates this results in a new mandate ID.

You'll receive:

{
  "events": {
    "resource_type": "mandates",
    "action": "replaced",
    "links": {
      "mandate": "MD123",
      "new_mandate": "MD456"
    }
  }
}

This requires immediate action. If you attempt to collect a payment against the old mandate ID you'll receive an invalid_state error with reason mandate_replaced. Update the mandate ID in your database to links.new_mandate as soon as you receive this event.

Full list of mandate events#

For all possible mandate events and their details see the API reference - mandate event types.

What's next?#