GoCardlessDeveloper Docs
Create a sandbox account

Subscriptions#

View as Markdown

What are Subscriptions?#

Automatically collect fixed amounts at regular intervals. Set up a subscription once, and GoCardless handles collection on schedule.

When to use Subscriptions

  • SaaS subscriptions
  • Membership fees
  • Content subscriptions
  • Gym memberships, insurance premiums, and ongoing services

Comparison between other recurring payment options#

Key difference from instalments: Instalment schedules have a defined end. Subscriptions continue indefinitely until cancelled.

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

Need to change the amount? If the amount varies between cycles, use one-off payments instead and control each charge individually. Alternatively, you can cancel and recreate a subscription with a new amount.

**Only taking payments for a defined period? **If the amount doesn't vary and you wish to only take a certain number of payments, use instalments 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

Frequency options: Weekly, monthly, yearly, or custom intervals.

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 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');
 
$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"
      }
    }
  }
}

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

Handle the subscription#

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

Manage the subscription#

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_finishedAll payments collected (if count was set)
subscriptions_cancelledSubscription cancelled

You can also cancel the subscription:

curl -X POST https://api.gocardless.com/subscriptions/SB123/actions/cancel \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
$response = $client->subscriptions()->cancel('SB123');
response = client.subscriptions.cancel('SB123')
response = client.subscriptions.cancel('SB123')
Subscription subscription = client.subscriptions()
    .cancel("SB123")
    .execute();
const client = gocardless(process.env.GC_ACCESS_TOKEN, constants.Environments.Sandbox);
var subscription = await client.Subscriptions.CancelAsync("SB123");
subscription, err := client.Subscriptions.Cancel(context.TODO(), "SB123", gocardless.SubscriptionCancelParams{})

Or pause the subscription:

curl -X POST https://api.gocardless.com/subscriptions/SB123/actions/pause \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "data": { "pause_cycles": 3 } }'
$response = $client->subscriptions()->pause('SB123', [
    'params' => [
        'pause_cycles' => 3,
    ],
]);
response = client.subscriptions.pause(
    'SB123',
    params={
        'pause_cycles': 3,
    }
)
response = client.subscriptions.pause(
  'SB123',
  params: {
    pause_cycles: 3
  }
)
Subscription subscription = client.subscriptions()
    .pause("SB123")
    .withPauseCycles(3)
    .execute();
const subscription = await client.subscriptions.pause("SB123", {
  pause_cycles: 3,
});
var subscription = await client.Subscriptions.PauseAsync(
    "SB123",
    new SubscriptionPauseRequest
    {
        PauseCycles = 3
    }
);
params := gocardless.SubscriptionPauseParams{
   PauseCycles: 3,
}
 
subscription, err := client.Subscriptions.Pause(context.TODO(), "SB123", params)

And resume the subscription:

curl -X POST https://api.gocardless.com/subscriptions/SB123/actions/resume \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
$response = $client->subscriptions()->resume('SB123');
response = client.subscriptions.resume('SB123')
response = client.subscriptions.resume('SB123')
Subscription subscription = client.subscriptions()
    .resume("SB123")
    .execute();
const subscription = await client.subscriptions.resume("SB123");
var subscription = await client.Subscriptions.ResumeAsync("SB123");
subscription, err := client.Subscriptions.Resume(context.TODO(), "SB123", gocardless.SubscriptionResumeParams{})

What's next?#