GoCardlessDeveloper Docs
Create a sandbox account

Variable Recurring Payments#

View as Markdown

What are Variable Recurring Payments?#

Collect real-time recurring payments of varying amounts from a single customer authorisation. The customer consents once via their banking app; you can then collect multiple payments within agreed parameters - no re-authorisations required for each charge.

When to use Variable Recurring Payments#

Commercial VRP (cVRP)Sweeping VRP (sVRP)
Transfer typeMe-to-BusinessMe-to-me
When to useUsage-based billing where amounts vary each period (metered services, such as electricity, gas, water and telecoms providers) Local and central government Rail fares Charitable donationsLending repayments Savings automation and round-ups Automated investment contributions
Who initiatesBusinessCustomer's bank
Payer account types supportPersonalPersonal and business

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. The customer authorised a VRP consent via their banking app, setting agreed payment parameters (maximum amount, frequency)
  2. You collect payments within those parameters via GoCardless API - no further customer action needed per payment
  3. Payment is confirmed within seconds
  4. You receive a webhook confirming the payment status
  5. Funds arrive same day or next business day

cVRP: 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": {
      "fallback_enabled": true,
      "purpose_code": "loan",
      "payment_context_code": "billing_goods_and_services_in_arrears",
      "payment_purpose_code": "bank_loan_delayed_draw_funding",
      "mandate_request": {
        "scheme": "faster_payments",
        "constraints": {
          "start_date": "2024-11-01",
          "max_amount_per_payment": 50000,
          "periodic_limits": [{
            "period": "month",
            "max_total_amount": 100000,
            "alignment": "creation_date"
          }]
        }
      }
    }
  }'
<?php
require 'vendor/autoload.php';
 
$client = new \GoCardlessPro\Client([
    'access_token' => getenv('GC_ACCESS_TOKEN'),
    'environment' => \GoCardlessPro\Environment::SANDBOX
]);
 
$response = $client->billingRequests()->create([
    'params' => [
        'fallback_enabled'     => true,
        'purpose_code'         => 'loan',
        'payment_context_code' => 'billing_goods_and_services_in_arrears',
        'payment_purpose_code' => 'bank_loan_delayed_draw_funding',
        'mandate_request'      => [
            'scheme'      => 'faster_payments',
            'constraints' => [
                'start_date'             => '2024-11-01',
                'max_amount_per_payment' => 50000,
                'periodic_limits'        => [
                    [
                        'period'           => 'month',
                        'max_total_amount' => 100000,
                        'alignment'        => 'creation_date',
                    ],
                ],
            ],
        ],
    ],
]);
import os
import gocardless_pro
 
client = gocardless_pro.Client(
    access_token=os.environ['GC_ACCESS_TOKEN'],
    environment='sandbox'
)
 
response = client.billing_requests.create(
    params={
        'fallback_enabled':     True,
        'purpose_code':         'loan',
        'payment_context_code': 'billing_goods_and_services_in_arrears',
        'payment_purpose_code': 'bank_loan_delayed_draw_funding',
        'mandate_request': {
            'scheme': 'faster_payments',
            'constraints': {
                'start_date':             '2024-11-01',
                'max_amount_per_payment': 50000,
                'periodic_limits': [
                    {
                        'period':           'month',
                        'max_total_amount': 100000,
                        'alignment':        'creation_date',
                    },
                ],
            },
        },
    }
)
require 'gocardless_pro'
 
client = GoCardlessPro::Client.new(
    access_token: ENV['GC_ACCESS_TOKEN'],
    environment: :sandbox
)
 
response = client.billing_requests.create(
  params: {
    fallback_enabled:     true,
    purpose_code:         'loan',
    payment_context_code: 'billing_goods_and_services_in_arrears',
    payment_purpose_code: 'bank_loan_delayed_draw_funding',
    mandate_request: {
      scheme: 'faster_payments',
      constraints: {
        start_date:             '2024-11-01',
        max_amount_per_payment: 50000,
        periodic_limits: [
          {
            period:           'month',
            max_total_amount: 100000,
            alignment:        'creation_date'
          }
        ]
      }
    }
  }
)
import com.gocardless.GoCardlessClient;
 
GoCardlessClient client = GoCardlessClient
    .newBuilder(System.getenv("GC_ACCESS_TOKEN"))
    .withEnvironment(GoCardlessClient.Environment.SANDBOX)
    .build();
 
BillingRequestCreateResponse response = client.billingRequests()
    .create()
    .withFallbackEnabled(true)
    .withPurposeCode("loan")
    .withPaymentContextCode("billing_goods_and_services_in_arrears")
    .withPaymentPurposeCode("bank_loan_delayed_draw_funding")
    .withMandateRequestScheme("faster_payments")
    .withMandateRequestConstraintsStartDate("2024-11-01")
    .withMandateRequestConstraintsMaxAmountPerPayment(50000)
    .withMandateRequestConstraintsPeriodicLimitsBuilder()
        .withPeriod("month")
        .withMaxTotalAmount(100000)
        .withAlignment("creation_date")
    .done()
    .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({
  fallback_enabled: true,
  purpose_code: "loan",
  payment_context_code: "billing_goods_and_services_in_arrears",
  payment_purpose_code: "bank_loan_delayed_draw_funding",
  mandate_request: {
    scheme: "faster_payments",
    constraints: {
      start_date: "2024-11-01",
      max_amount_per_payment: 50000,
      periodic_limits: [
        {
          period: "month",
          max_total_amount: 100000,
          alignment: "creation_date",
        },
      ],
    },
  },
});
using GoCardless;
 
var client = GoCardlessClient.Create(
    Environment.GetEnvironmentVariable("GC_ACCESS_TOKEN"),
    GoCardlessClient.Environment.SANDBOX
);
 
var response = await client.BillingRequests.CreateAsync(
    new BillingRequestCreateRequest
    {
        FallbackEnabled    = true,
        PurposeCode        = "loan",
        PaymentContextCode = "billing_goods_and_services_in_arrears",
        PaymentPurposeCode = "bank_loan_delayed_draw_funding",
        MandateRequest = new BillingRequestCreateRequest.BillingRequestMandateRequest
        {
            Scheme = "faster_payments",
            Constraints = new BillingRequestCreateRequest.BillingRequestMandateRequestConstraints
            {
                StartDate           = "2024-11-01",
                MaxAmountPerPayment = 50000,
                PeriodicLimits = new List<BillingRequestCreateRequest.BillingRequestMandateRequestConstraintsPeriodicLimit>
                {
                    new BillingRequestCreateRequest.BillingRequestMandateRequestConstraintsPeriodicLimit
                    {
                        Period         = "month",
                        MaxTotalAmount = 100000,
                        Alignment      = "creation_date"
                    }
                }
            }
        }
    }
);
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)
    }
 
    fallbackEnabled := true
    params := gocardless.BillingRequestCreateParams{
        FallbackEnabled:    &fallbackEnabled,
        PurposeCode:        "loan",
        PaymentContextCode: "billing_goods_and_services_in_arrears",
        PaymentPurposeCode: "bank_loan_delayed_draw_funding",
        MandateRequest: &gocardless.BillingRequestCreateParamsMandateRequest{
            Scheme: "faster_payments",
            Constraints: &gocardless.BillingRequestCreateParamsMandateRequestConstraints{
                StartDate:          "2024-11-01",
                MaxAmountPerPayment: 50000,
                PeriodicLimits: []gocardless.BillingRequestCreateParamsMandateRequestConstraintsPeriodicLimit{
                    {
                        Period:         "month",
                        MaxTotalAmount: 100000,
                        Alignment:      "creation_date",
                    },
                },
            },
        },
    }
 
    result, err := client.BillingRequests.Create(context.TODO(), params)
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}

Response

{
  "billing_requests": {
    "id": "BRQ0000301",
    "created_at": "2024-11-01T00:00:00.000Z",
    "status": "pending",
    "mandate_request": {
      "scheme": "faster_payments",
      "currency": "GBP",
      "verify": "recommended",
      "consent_type": "recurring",
      "description": null,
      "sweeping": false,
      "payer_requested_dual_signature": false,
      "metadata": {},
      "links": {},
      "constraints": {
        "start_date": "2024-11-01",
        "end_date": null,
        "max_amount_per_payment": 50000,
        "periodic_limits": [
          {
            "period": "month",
            "max_total_amount": 100000,
            "alignment": "creation_date",
            "max_amount_per_period": null,
            "max_payments": null
          }
        ]
      }
    },
    "payment_request": null,
    "subscription_request": null,
    "instalment_schedule_request": null,
    "metadata": {},
    "fallback_enabled": true,
    "fallback_occurred": false,
    "purpose_code": "loan",
    "payment_purpose_code": "bank_loan_delayed_draw_funding",
    "sign_flow_url": null,
    "auto_fulfil": false,
    "actions": [
      {
        "type": "collect_customer_details",
        "required": true,
        "completes_actions": [],
        "requires_actions": [],
        "status": "pending",
        "collect_customer_details": {
          "default_country_code": "GB",
          "incomplete_fields": {
            "customer": ["given_name", "family_name", "email"],
            "customer_billing_detail": ["address_line1", "city", "postal_code", "country_code"]
          }
        }
      },
      {
        "type": "select_institution",
        "required": true,
        "completes_actions": [],
        "requires_actions": [],
        "status": "pending",
        "institution_guess_status": "pending"
      },
      {
        "type": "collect_bank_account",
        "required": true,
        "completes_actions": [],
        "requires_actions": [],
        "status": "pending",
        "available_country_codes": ["GB"]
      },
      {
        "type": "bank_authorisation",
        "required": true,
        "completes_actions": [],
        "requires_actions": ["select_institution", "collect_customer_details"],
        "status": "pending",
        "bank_authorisation": {
          "authorisation_type": "mandate"
        }
      },
      {
        "type": "confirm_payer_details",
        "required": true,
        "completes_actions": [],
        "requires_actions": ["collect_customer_details", "collect_bank_account"],
        "status": "pending"
      }
    ],
    "links": {
      "mandate_request": "MRQ0000301"
    }
  }
}

Confirm the mandate#

After creating the Billing Request, direct the customer to authorise the consent. 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#

Listen for webhooks to confirm the mandate status:

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

Collect a variable payment#

You can collect a payment with the customer not present (recurring billing etc.):

curl -X POST https://api.gocardless.com/payments \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: UNIQUE_KEY" \
  -d '{
    "payments": {
      "amount": 5000,
      "currency": "GBP",
      "psu_interaction_type": "off_session",
      "charge_date": "2024-11-01",
      "description": "November subscription",
      "links": {
        "mandate": "MD123"
      }
    }
  }'
$response = $client->payments()->create(
    [
        'params' => [
            'amount'               => 5000,
            'currency'             => 'GBP',
            'psu_interaction_type' => 'off_session',
            'charge_date'          => '2024-11-01',
            'description'          => 'November subscription',
            'links'                => [
                'mandate' => 'MD123',
            ],
        ],
    ],
    'UNIQUE_KEY'
);
response = client.payments.create(
    params={
        'amount':               5000,
        'currency':             'GBP',
        'psu_interaction_type': 'off_session',
        'charge_date':          '2024-11-01',
        'description':          'November subscription',
        'links': {
            'mandate': 'MD123',
        },
    },
    headers={'Idempotency-Key': 'UNIQUE_KEY'}
)
response = client.payments.create(
  params: {
    amount:               5000,
    currency:             'GBP',
    psu_interaction_type: 'off_session',
    charge_date:          '2024-11-01',
    description:          'November subscription',
    links: {
      mandate: 'MD123'
    }
  },
  headers: { 'Idempotency-Key' => 'UNIQUE_KEY' }
)
Payment payment = client.payments()
    .create()
    .withAmount(5000)
    .withCurrency("GBP")
    .withPsuInteractionType("off_session")
    .withChargeDate("2024-11-01")
    .withDescription("November subscription")
    .withLinksMandate("MD123")
    .withIdempotencyKey("UNIQUE_KEY")
    .execute();
 
const payment = await client.payments.create(
  {
    amount: 5000,
    currency: "GBP",
    psu_interaction_type: "off_session",
    charge_date: "2024-11-01",
    description: "November subscription",
    links: {
      mandate: "MD123",
    },
  },
  "UNIQUE_KEY",
);
var payment = await client.Payments.CreateAsync(
    new PaymentCreateRequest
    {
        Amount             = 5000,
        Currency           = "GBP",
        PsuInteractionType = "off_session",
        ChargeDate         = "2024-11-01",
        Description        = "November subscription",
        Links = new PaymentCreateRequest.PaymentLinks
        {
            Mandate = "MD123"
        }
    },
    new RequestSettings { IdempotencyKey = "UNIQUE_KEY" }
);
params := gocardless.PaymentCreateParams{
    Amount:             5000,
    Currency:           "GBP",
    PsuInteractionType: "off_session",
    ChargeDate:         "2024-11-01",
    Description:        "November subscription",
    Links: gocardless.PaymentCreateParamsLinks{
       Mandate: "MD123",
    },
}
 
payment, err := client.Payments.Create(context.TODO(), params, gocardless.WithIdempotencyKey("UNIQUE_KEY"))

Or when the customer is present for top-ups and one-off purchases:

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",
      "psu_interaction_type": "in_session",
      "description": "One-off top-up",
      "links": {
        "mandate": "MD123"
      }
    }
  }'
$response = $client->payments()->create([
    'params' => [
        'amount'               => 5000,
        'currency'             => 'GBP',
        'psu_interaction_type' => 'in_session',
        'description'          => 'One-off top-up',
        'links'                => [
            'mandate' => 'MD123',
        ],
    ],
]);
response = client.payments.create(
    params={
        'amount':               5000,
        'currency':             'GBP',
        'psu_interaction_type': 'in_session',
        'description':          'One-off top-up',
        'links': {
            'mandate': 'MD123',
        },
    }
)
response = client.payments.create(
  params: {
    amount:               5000,
    currency:             'GBP',
    psu_interaction_type: 'in_session',
    description:          'One-off top-up',
    links: {
      mandate: 'MD123'
    }
  }
)
Payment payment = client.payments()
    .create()
    .withAmount(5000)
    .withCurrency("GBP")
    .withPsuInteractionType("in_session")
    .withDescription("One-off top-up")
    .withLinksMandate("MD123")
    .execute();
const payment = await client.payments.create({
  amount: 5000,
  currency: "GBP",
  psu_interaction_type: "in_session",
  description: "One-off top-up",
  links: {
    mandate: "MD123",
  },
});
var payment = await client.Payments.CreateAsync(
    new PaymentCreateRequest
    {
        Amount             = 5000,
        Currency           = "GBP",
        PsuInteractionType = "in_session",
        Description        = "One-off top-up",
        Links = new PaymentCreateRequest.PaymentLinks
        {
            Mandate = "MD123"
        }
    }
);
params := gocardless.PaymentCreateParams{
    Amount:             5000,
    Currency:           "GBP",
    PsuInteractionType: "in_session",
    Description:        "One-off top-up",
    Links: gocardless.PaymentCreateParamsLinks{
        Mandate: "MD123",
    },
}
 
payment, err := client.Payments.Create(context.TODO(), params)

To cancel a future-dated payment (off-session only): POST /payments/{id}/actions/cancel. In-session payments cannot be cancelled once submitted.

Listen for webhooks to confirm the variable payment status:

EventMeaning
payments_confirmedPayment confirmed and settled
payments_failedPayment failed

Listen for webhooks to confirm the consent status:

EventMeaning
mandates_cancelledConsent cancelled (by you or the customer via their banking app)

sVRP: 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": "faster_payments",
        "sweeping": true,
        "constraints": {
          "start_date": "2024-11-01",
          "end_date": "2025-11-01",
          "max_amount_per_payment": 50000,
          "periodic_limits": [{
            "period": "month",
            "max_total_amount": 100000,
            "alignment": "creation_date"
          }]
        }
      }
    }
  }'
<?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'      => 'faster_payments',
            'sweeping'    => true,
            'constraints' => [
                'start_date'             => '2024-11-01',
                'end_date'               => '2025-11-01',
                'max_amount_per_payment' => 50000,
                'periodic_limits'        => [
                    [
                        'period'           => 'month',
                        'max_total_amount' => 100000,
                        'alignment'        => 'creation_date',
                    ],
                ],
            ],
        ],
    ],
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':   'faster_payments',
            'sweeping': True,
            'constraints': {
                'start_date':             '2024-11-01',
                'end_date':               '2025-11-01',
                'max_amount_per_payment': 50000,
                'periodic_limits': [
                    {
                        'period':           'month',
                        'max_total_amount': 100000,
                        'alignment':        'creation_date',
                    },
                ],
            },
        }
    }
)
require 'gocardless_pro'
 
client = GoCardlessPro::Client.new(
    access_token: ENV['GC_ACCESS_TOKEN'],
    environment: :sandbox
)
 
response = client.billing_requests.create(
  params: {
    mandate_request: {
      scheme:   'faster_payments',
      sweeping: true,
      constraints: {
        start_date:             '2024-11-01',
        end_date:               '2025-11-01',
        max_amount_per_payment: 50000,
        periodic_limits: [
          {
            period:           'month',
            max_total_amount: 100000,
            alignment:        'creation_date'
          }
        ]
      }
    }
  }
)
import com.gocardless.GoCardlessClient;
 
GoCardlessClient client = GoCardlessClient
    .newBuilder(System.getenv("GC_ACCESS_TOKEN"))
    .withEnvironment(GoCardlessClient.Environment.SANDBOX)
    .build();
 
BillingRequestCreateResponse response = client.billingRequests()
    .create()
    .withMandateRequestScheme("faster_payments")
    .withMandateRequestSweeping(true)
    .withMandateRequestConstraintsStartDate("2024-11-01")
    .withMandateRequestConstraintsEndDate("2025-11-01")
    .withMandateRequestConstraintsMaxAmountPerPayment(50000)
    .withMandateRequestConstraintsPeriodicLimitsBuilder()
        .withPeriod("month")
        .withMaxTotalAmount(100000)
        .withAlignment("creation_date")
    .done()
    .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: "faster_payments",
    sweeping: true,
    constraints: {
      start_date: "2024-11-01",
      end_date: "2025-11-01",
      max_amount_per_payment: 50000,
      periodic_limits: [
        {
          period: "month",
          max_total_amount: 100000,
          alignment: "creation_date",
        },
      ],
    },
  },
});
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    = "faster_payments",
            Sweeping  = true,
            Constraints = new BillingRequestCreateRequest.BillingRequestMandateRequestConstraints
            {
                StartDate           = "2024-11-01",
                EndDate             = "2025-11-01",
                MaxAmountPerPayment = 50000,
                PeriodicLimits = new List<BillingRequestCreateRequest.BillingRequestMandateRequestConstraintsPeriodicLimit>
                {
                    new BillingRequestCreateRequest.BillingRequestMandateRequestConstraintsPeriodicLimit
                    {
                        Period         = "month",
                        MaxTotalAmount = 100000,
                        Alignment      = "creation_date"
                    }
                }
            }
        }
    }
);
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)
    }
 
    sweeping := true
    params := gocardless.BillingRequestCreateParams{
        MandateRequest: &gocardless.BillingRequestCreateParamsMandateRequest{
            Scheme:    "faster_payments",
            Sweeping:  &sweeping,
            Constraints: &gocardless.BillingRequestCreateParamsMandateRequestConstraints{
                StartDate:          "2024-11-01",
                EndDate:            "2025-11-01",
                MaxAmountPerPayment: 50000,
                PeriodicLimits: []gocardless.BillingRequestCreateParamsMandateRequestConstraintsPeriodicLimit{
                    {
                        Period:         "month",
                        MaxTotalAmount: 100000,
                        Alignment:      "creation_date",
                    },
                },
            },
        },
    }
 
    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": "faster_payments"
    },
    "links": {
      "customer": "CU123",
      "customer_billing_detail": "CBD123",
      "mandate_request": "MRQ123"
    },
    "actions": [
      {
        "type": "collect_customer_details",
        "required": true,
        "status": "pending"
      },
      { "type": "collect_bank_account", "required": true, "status": "pending" },
      { "type": "bank_authorisation", "required": true, "status": "pending" }
    ]
  }
}

After creating the Billing Request, direct the customer to authorise the consent. 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#

Listen for webhooks to confirm the mandate status:

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

Collect a variable payment#

Retrieve the mandate ID from:

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

And collect a variable payment from the consent within the agreed constraints:

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

Response:

{
  "payments": {
    "id": "PM123",
    "status": "pending_submission",
    "amount": 10000,
    "currency": "GBP",
    "charge_date": "2024-11-01",
    "links": {
      "mandate": "MD123"
    }
  }
}

Listen for webhooks to confirm the consent status:

EventMeaning
mandates_cancelledConsent cancelled (by you or the customer via their banking app)

What's next?#