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

Variable Recurring Payments feature is only available to approved integrators with the VRP Sweeping upgrade or VRP Commercial upgrade to use either mechanism respectively. If you're interested in trying it out - please contact support.

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?#