GoCardlessDeveloper Docs
Create a sandbox account

Instant Payment with Direct Debit Setup#

View as Markdown

What are Instant Payment with Direct Debit Setup?#

Take an immediate payment via Instant Bank Pay while automatically setting up a Direct Debit mandate for future payments. This gives you the speed of instant confirmation for the first payment and the flexibility of Direct Debit for ongoing charges.

Integration options: This guide shows the API flow. You can also use Hosted Payment Pages for a no-code approach. Drop-in Flow does not support Instant Bank Pay.

Availability: GBP (Faster Payments), EUR (SEPA Instant)

When to use this combined flow

  • Subscription services requiring immediate activation
  • Sign-up fee plus ongoing billing (e.g., gym membership)
  • Trial conversions with instant first charge
  • Any service where you need payment confirmation before providing access

Comparison between other recurring payment options#

SubscriptionsInstalmentsVariable Recurring PaymentsInstant Bank Pay + Direct Debit
Mandate requiredYesYesYes (consent)Yes
Customer authorisationOnceOnceOnce (with agreed limits and frequency)Once
Payment amountsFixed (per subscription)Fixed per instalment, can vary across scheduleVariable within agreed constraintsFirst payment fixed; subsequent payments flexible
Payment scheduleMerchant-defined recurring (weekly, monthly, yearly)Merchant-defined schedule with a defined endFlexible - merchant triggers payments as neededFlexible after first payment
Confirmation speed2-x business days2-x business daysSecondsFirst payment: minutes; subsequent: 2-x business days
Chargeback riskYesYesNoneFirst payment: none; subsequent: yes
Missed payment retriesYes (with Success+)Yes (with Success+)YesSubsequent payments: yes
Best forSaaS, memberships, gym fees, insurancePayment plans, tuition, professional servicesUsage-based regulated billing and utilities, financial services, government servicesSubscription services needing immediate, initial payment
AvailabilityAll schemesAll schemesGBP onlyGBP and EUR

How it works#

  1. Customer authorises an instant payment and a Direct Debit mandate in a single flow
  2. The instant payment is confirmed within minutes
  3. The Direct Debit mandate is set up for future payments
  4. You can then collect recurring or one-off payments against the mandate

Step-by-step guide#

Create a billing request#

curl -X POST https://api.gocardless.com/billing_requests \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "billing_requests": {
      "payment_request": {
        "description": "First payment",
        "amount": 5000, "currency": "GBP", "scheme": "faster_payments"
      },
      "mandate_request": { "scheme": "bacs" }
    }
  }'
<?php
require 'vendor/autoload.php';
 
$client = new \GoCardlessPro\Client([
    'access_token' => getenv('GC_ACCESS_TOKEN'),
    'environment' => \GoCardlessPro\Environment::SANDBOX
]);
 
$response = $client->billingRequests()->create([
    'params' => [
        'payment_request' => [
            'description' => 'First payment',
            'amount'      => 5000,
            'currency'    => 'GBP',
            'scheme'      => 'faster_payments',
        ],
        'mandate_request' => [
            'scheme' => 'bacs',
        ],
    ],
]);
import os
import gocardless_pro
 
client = gocardless_pro.Client(
    access_token=os.environ['GC_ACCESS_TOKEN'],
    environment='sandbox'
)
 
response = client.billing_requests.create(
    params={
        'payment_request': {
            'description': 'First payment',
            'amount':      5000,
            'currency':    'GBP',
            'scheme':      'faster_payments',
        },
        'mandate_request': {
            'scheme': 'bacs',
        },
    }
)
  require 'gocardless_pro'
 
  client = GoCardlessPro::Client.new(
      access_token: ENV['GC_ACCESS_TOKEN'],
      environment: :sandbox
  )
 
  response = client.billing_requests.create(
    params: {
      payment_request: {
        description: 'First payment',
        amount:      5000,
        currency:    'GBP',
        scheme:      'faster_payments'
      },
      mandate_request: {
        scheme: 'bacs'
      }
    }
  )
import com.gocardless.GoCardlessClient;
 
GoCardlessClient client = GoCardlessClient
    .newBuilder(System.getenv("GC_ACCESS_TOKEN"))
    .withEnvironment(GoCardlessClient.Environment.SANDBOX)
    .build();
 
BillingRequestCreateResponse response = client.billingRequests()
    .create()
    .withPaymentRequestDescription("First payment")
    .withPaymentRequestAmount(5000)
    .withPaymentRequestCurrency("GBP")
    .withPaymentRequestScheme("faster_payments")
    .withMandateRequestScheme("bacs")
    .execute();
const gocardless = require("gocardless-nodejs");
const constants = require("gocardless-nodejs/constants");
 
const client = gocardless(process.env.GC_ACCESS_TOKEN, constants.Environments.Sandbox);
 
const response = await client.billingRequests.create({
  payment_request: {
    description: "First payment",
    amount: 5000,
    currency: "GBP",
    scheme: "faster_payments",
  },
  mandate_request: {
    scheme: "bacs",
  },
});
using GoCardless;
 
var client = GoCardlessClient.Create(
    Environment.GetEnvironmentVariable("GC_ACCESS_TOKEN"),
    GoCardlessClient.Environment.SANDBOX
);
 
var response = await client.BillingRequests.CreateAsync(
    new BillingRequestCreateRequest
    {
        PaymentRequest = new BillingRequestCreateRequest.BillingRequestPaymentRequest
        {
            Description = "First payment",
            Amount      = 5000,
            Currency    = "GBP",
            Scheme      = "faster_payments"
        },
        MandateRequest = new BillingRequestCreateRequest.BillingRequestMandateRequest
        {
            Scheme = "bacs"
        }
    }
);
package main
 
import (
    "context"
    "fmt"
    "os"
 
    gocardless "github.com/gocardless/gocardless-pro-go/v6"
)
 
func main() {
    opts := gocardless.WithEndpoint(gocardless.SandboxEndpoint)
    client, err := gocardless.New(os.Getenv("GC_ACCESS_TOKEN"), opts)
    if err != nil {
        panic(err)
    }
 
    params := gocardless.BillingRequestCreateParams{
        PaymentRequest: &gocardless.BillingRequestCreateParamsPaymentRequest{
            Description: "First payment",
            Amount:      5000,
            Currency:    "GBP",
            Scheme:      "faster_payments",
        },
        MandateRequest: &gocardless.BillingRequestCreateParamsMandateRequest{
            Scheme: "bacs",
        },
    }
 
    result, err := client.BillingRequests.Create(context.TODO(), params)
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}

Response:

{
  "billing_requests": {
    "id": "BRQ0000303",
    "status": "pending",
    "payment_request": {
      "amount": 5000,
      "currency": "GBP",
      "scheme": "faster_payments"
    },
    "mandate_request": {
      "scheme": "bacs",
      "currency": "GBP"
    },
    "actions": [
      {
        "type": "collect_customer_details",
        "required": true,
        "completed": false
      },
      {
        "type": "collect_bank_account",
        "required": true,
        "completed": false
      },
      {
        "type": "confirm_payer_details",
        "required": true,
        "completed": false
      },
      {
        "type": "select_institution",
        "required": true,
        "completed": false
      },
      {
        "type": "bank_authorisation",
        "required": true,
        "completed": false
      }
    ]
  }
}

Confirm the mandate and payment#

After creating the Billing Request, direct the customer to authorise the mandate. How you do this depends on your integration type:

  • Hosted Pages - Create a Billing Request Flow and redirect the customer to the authorisation_url. See Hosted Payment Pages.
  • Custom API - Collect customer details and bank account via Billing Request actions, then confirm. See Custom Payment Pages.

Handle the outcome#

Once fulfilled, retrieve both mandate ID and payment ID from the billing request:

curl https://api.gocardless.com/billing_requests/BRQ000123 \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
<?php
require 'vendor/autoload.php';
 
$client = new \GoCardlessPro\Client([
    'access_token' => getenv('GC_ACCESS_TOKEN'),
    'environment' => \GoCardlessPro\Environment::SANDBOX
]);
 
$response = $client->billingRequests()->get('BRQ000123');
 
$paymentId = $response->payment_request->links->payment;
$mandateId = $response->mandate_request->links->mandate;
import os
import gocardless_pro
 
client = gocardless_pro.Client(
    access_token=os.environ['GC_ACCESS_TOKEN'],
    environment='sandbox'
)
 
response = client.billing_requests.get('BRQ000123')
 
payment_id = response.payment_request.links.payment
mandate_id = response.mandate_request.links.mandate
require 'gocardless_pro'
 
client = GoCardlessPro::Client.new(
    access_token: ENV['GC_ACCESS_TOKEN'],
    environment: :sandbox
)
 
response = client.billing_requests.get('BRQ000123')
 
payment_id = response.payment_request.links.payment
mandate_id = response.mandate_request.links.mandate
import com.gocardless.GoCardlessClient;
 
GoCardlessClient client = GoCardlessClient
    .newBuilder(System.getenv("GC_ACCESS_TOKEN"))
    .withEnvironment(GoCardlessClient.Environment.SANDBOX)
    .build();
 
BillingRequest billingRequest = client.billingRequests()
    .get("BRQ000123")
    .execute();
 
String paymentId = billingRequest.getPaymentRequest().getLinks().getPayment();
String mandateId = billingRequest.getMandateRequest().getLinks().getMandate();
const gocardless = require("gocardless-nodejs");
const constants = require("gocardless-nodejs/constants");
 
const client = gocardless(process.env.GC_ACCESS_TOKEN, constants.Environments.Sandbox);
 
const billingRequest = await client.billingRequests.find("BRQ000123");
 
const paymentId = billingRequest.payment_request.links.payment;
const mandateId = billingRequest.mandate_request.links.mandate;
using GoCardless;
 
var client = GoCardlessClient.Create(
    Environment.GetEnvironmentVariable("GC_ACCESS_TOKEN"),
    GoCardlessClient.Environment.SANDBOX
);
 
var billingRequest = await client.BillingRequests.GetAsync("BRQ000123");
 
var paymentId = billingRequest.PaymentRequest.Links.Payment;
var mandateId = billingRequest.MandateRequest.Links.Mandate;
package main
 
import (
    "context"
    "fmt"
    "os"
 
    gocardless "github.com/gocardless/gocardless-pro-go/v6"
)
 
func main() {
    opts := gocardless.WithEndpoint(gocardless.SandboxEndpoint)
    client, err := gocardless.New(os.Getenv("GC_ACCESS_TOKEN"), opts)
    if err != nil {
        panic(err)
    }
 
    billingRequest, err := client.BillingRequests.Get(context.TODO(), "BRQ000123")
    if err != nil {
        panic(err)
    }
 
    paymentId := billingRequest.PaymentRequest.Links.Payment
    mandateId := billingRequest.MandateRequest.Links.Mandate
 
    fmt.Println(paymentId, mandateId)
}

Response:

{
  "billing_requests": {
    "id": "BRQ000123",
    "status": "fulfilled",
    "payment_request": {
      "links": { "payment": "PM123" }
    },
    "mandate_request": {
      "links": { "mandate": "MD123" }
    }
  }
}

Listen for webhooks to confirm the payment and mandate status:

EventMeaning
billing_request_fulfilledCustomer authorised mandate and payment is being processed
payments_confirmedPayment confirmed and settled
payments_failedPayment failed (e.g., insufficient funds)
mandates_activeMandate active and ready for recurring payments

Set up recurring payments#

Once the mandate is active, set up recurring payments against it:

curl -X POST https://api.gocardless.com/subscriptions \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "subscriptions": {
      "amount": 5000,
      "currency": "GBP",
      "name": "Monthly subscription",
      "interval_unit": "monthly",
      "interval": 1,
      "start_date": "2024-11-01",
      "links": {
        "mandate": "MD123"
      }
    }
  }'
<?php
require 'vendor/autoload.php';
 
$client = new \GoCardlessPro\Client([
    'access_token' => getenv('GC_ACCESS_TOKEN'),
    'environment' => \GoCardlessPro\Environment::SANDBOX
]);
 
$response = $client->subscriptions()->create([
    'params' => [
        'amount'        => 2500,
        'currency'      => 'GBP',
        'name'          => 'Monthly Magazine',
        'interval_unit' => 'monthly',
        'interval'      => 1,
        'day_of_month'  => 1,
        'start_date'    => '2024-11-01',
        'links'         => [
            'mandate' => 'MD123',
        ],
    ],
]);
import os
import gocardless_pro
 
client = gocardless_pro.Client(
    access_token=os.environ['GC_ACCESS_TOKEN'],
    environment='sandbox'
)
 
response = client.subscriptions.create(
    params={
        'amount':        2500,
        'currency':      'GBP',
        'name':          'Monthly Magazine',
        'interval_unit': 'monthly',
        'interval':      1,
        'day_of_month':  1,
        'start_date':    '2024-11-01',
        'links': {
            'mandate': 'MD123',
        },
    }
)
  require 'gocardless_pro'
 
  client = GoCardlessPro::Client.new(
      access_token: ENV['GC_ACCESS_TOKEN'],
      environment: :sandbox
  )
 
  response = client.subscriptions.create(
    params: {
      amount:        2500,
      currency:      'GBP',
      name:          'Monthly Magazine',
      interval_unit: 'monthly',
      interval:      1,
      day_of_month:  1,
      start_date:    '2024-11-01',
      links: {
        mandate: 'MD123'
      }
    }
  )
import com.gocardless.GoCardlessClient;
 
GoCardlessClient client = GoCardlessClient
    .newBuilder(System.getenv("GC_ACCESS_TOKEN"))
    .withEnvironment(GoCardlessClient.Environment.SANDBOX)
    .build();
 
Subscription subscription = client.subscriptions()
    .create()
    .withAmount(2500)
    .withCurrency("GBP")
    .withName("Monthly Magazine")
    .withIntervalUnit(Subscription.IntervalUnit.MONTHLY)
    .withInterval(1)
    .withDayOfMonth(1)
    .withStartDate("2024-11-01")
    .withLinksMandate("MD123")
    .execute();
const gocardless = require("gocardless-nodejs");
const constants = require("gocardless-nodejs/constants");
 
const client = gocardless(process.env.GC_ACCESS_TOKEN, constants.Environments.Sandbox);
 
const subscription = await client.subscriptions.create({
  amount: 2500,
  currency: "GBP",
  name: "Monthly Magazine",
  interval_unit: "monthly",
  interval: 1,
  day_of_month: 1,
  start_date: "2024-11-01",
  links: {
    mandate: "MD123",
  },
});
using GoCardless;
 
var client = GoCardlessClient.Create(
    Environment.GetEnvironmentVariable("GC_ACCESS_TOKEN"),
    GoCardlessClient.Environment.SANDBOX
);
 
var subscription = await client.Subscriptions.CreateAsync(
    new SubscriptionCreateRequest
    {
        Amount       = 2500,
        Currency     = "GBP",
        Name         = "Monthly Magazine",
        IntervalUnit = SubscriptionCreateRequest.SubscriptionIntervalUnit.Monthly,
        Interval     = 1,
        DayOfMonth   = 1,
        StartDate    = "2024-11-01",
        Links = new SubscriptionCreateRequest.SubscriptionLinks
        {
            Mandate = "MD123"
        }
    }
);
package main
 
import (
    "context"
    "fmt"
    "os"
 
    gocardless "github.com/gocardless/gocardless-pro-go/v6"
)
 
func main() {
    opts := gocardless.WithEndpoint(gocardless.SandboxEndpoint)
    client, err := gocardless.New(os.Getenv("GC_ACCESS_TOKEN"), opts)
    if err != nil {
        panic(err)
    }
 
    params := gocardless.SubscriptionCreateParams{
        Amount:       2500,
        Currency:     "GBP",
        Name:         "Monthly Magazine",
        IntervalUnit: "monthly",
        Interval:     1,
        DayOfMonth:   1,
        StartDate:    "2024-11-01",
        Links: gocardless.SubscriptionCreateParamsLinks{
            Mandate: "MD123",
        },
    }
 
    subscription, err := client.Subscriptions.Create(context.TODO(), params)
    if err != nil {
        panic(err)
    }
    fmt.Println(subscription)
}

Response:

{
  "subscriptions": {
    "id": "SB123",
    "status": "active",
    "amount": 2500,
    "currency": "GBP",
    "interval": 1,
    "interval_unit": "monthly",
    "start_date": "2024-11-01",
    "upcoming_payments": [
      { "charge_date": "2024-11-01", "amount": 2500 },
      { "charge_date": "2024-12-01", "amount": 2500 }
    ],
    "links": {
      "mandate": "MD123"
    }
  }
}

Listen for webhooks to confirm the mandate status and notifications about future payments:

EventMeaning
subscriptions_createdSubscription set up successfully
subscriptions_payment_createdA scheduled payment was created
payments_confirmedRecurring payment collected successfully
payments_failedRecurring payment failed
subscriptions_cancelledSubscription cancelled

What's next?#