GoCardlessDeveloper Docs
Create a sandbox account

One-off Direct Debit#

View as Markdown

One-off Direct Debits allow you to collect fixed or variable amounts from your customers on a flexible schedule. The customer sets up a Direct Debit mandate once; after that, you can collect payments of any amount at any time without requiring re-authorisation.

Best for: Usage-based billing, professional services with variable charges, invoice payments.

One-off Direct Debit vs Pay By Bank#

One-off via Direct DebitPay By Bank
Mandate requiredYesNo
Customer authorisationOnce (mandate setup)Per payment
Confirmation speed2-5 business daysMinutes
Re-use for future paymentsYes, collect again without re-authorisationNo, new authorisation each time
ProtectionScheme-specificNone
Customer experienceOne-time setup, then hands-offThe bank app redirects each time

For time-sensitive payments, consider Pay By Bank, which confirms within seconds. If you need immediate confirmation for the first payment but also want a mandate for future charges, use Instant Payment + DD Setup instead.

How it works#

  1. The customer sets up a mandate via your chosen integration
  2. You create a payment against the mandate via the API
  3. GoCardless collects the payment (typically 2-5 business days)
  4. You receive a webhook confirming the payment status
  5. Funds are included in your next payout

Key differences from recurring payments: You control when and how much to charge. There's no automatic schedule; you create each payment individually via the API.

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": {
    "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' => [
'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={
'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: {
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()
.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({
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
{
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{
      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": "BRQ000123",
    "status": "pending",
    "mandate_request": {
      "currency": "GBP",
      "scheme": "bacs"
    }
  }
}

Confirm the mandate#

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 Pages.
  • Custom API — Collect customer details and bank account via Billing Request actions, then confirm. See Custom Payment Pages.

Handle the outcome#

Listen for webhooks to confirm the payment status:

EventMeaning
billing_request_fulfilledCustomer authorised the mandate
mandates_createdMandate created and ready to use
mandates_activeMandate active

Collect a payment#

Retrieve the mandate ID from the fulfilled 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');

$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')

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')

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 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 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 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)
  }

  mandateId := billingRequest.MandateRequest.Links.Mandate
  fmt.Println(mandateId)

}

Response:

{
  "billing_requests": {
    "id": "BRQ000123",
    "status": "fulfilled",
    "mandate_request": {
      "scheme": "bacs",
      "links": {
        "mandate": "MD123"
      }
    }
  }
}

Then collect whenever you need to:

curl -X POST https://api.gocardless.com/payments \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
  "payments": {
    "amount": 5000,
    "currency": "GBP",
    "description": "Order #1234",
    "links": {
      "mandate": "MD123"
    }
  }
}'
$response = $client->payments()->create([
  'params' => [
      'amount'      => 5000,
      'currency'    => 'GBP',
      'description' => 'Order #1234',
      'links'       => [
          'mandate' => 'MD123',
      ],
  ],
]);
response = client.payments.create(
  params={
      'amount':      5000,
      'currency':    'GBP',
      'description': 'Order #1234',
      'links': {
          'mandate': 'MD123',
      },
  }
)
response = client.payments.create(
params: {
  amount:      5000,
  currency:    'GBP',
  description: 'Order #1234',
  links: {
    mandate: 'MD123'
  }
}
)
Payment payment = client.payments()
  .create()
  .withAmount(5000)
  .withCurrency("GBP")
  .withDescription("Order #1234")
  .withLinksMandate("MD123")
  .execute();
const payment = await client.payments.create({
amount: 5000,
currency: "GBP",
description: "Order #1234",
links: {
  mandate: "MD123",
},
});
var payment = await client.Payments.CreateAsync(
  new PaymentCreateRequest
  {
      Amount      = 5000,
      Currency    = "GBP",
      Description = "Order #1234",
      Links = new PaymentCreateRequest.PaymentLinks
      {
          Mandate = "MD123"
      }
  }
);
params := gocardless.PaymentCreateParams{
  Amount:      5000,
  Currency:    "GBP",
  Description: "Order #1234",
  Links: gocardless.PaymentCreateParamsLinks{
      Mandate: "MD123",
  },
}

payment, err := client.Payments.Create(context.TODO(), params)

What's next?#