Create and manage subscription contracts
A subscription contract is the agreement between a customer and a merchant over a specific term for recurring purchases over a set or undefined period of time.
This tutorial shows you how to create and manage subscription contracts by illustrating two use cases: "Subscribe and save" subscriptions and "Prepaid" subscriptions.
Requirements
- You've completed our Getting started with the GraphQL Admin and REST Admin APIs guide and you're authenticated with the API.
- You've created products and product variants in your development store.
- You've completed our Create and manage selling plans tutorial.
Scope requirements
To use the GraphQL mutations, your app needs to request the following access scopes for a Shopify store:
read_own_subscription_contracts
: Allows an app to read subscription contract mutations for contracts they own.write_own_subscription_contracts
: Allows an app to write subscription contract mutations for contracts they own.
Subscription contracts
A subcription contract is the result of purchasing a selling plan that has recurring policies. After a customer purchases a subscription product or variant at checkout, Shopify generates a subscription contract and shares the contract with your app using a webhook.
Subscription contracts generate new orders for merchants to fulfill at subscription renewal time. They're also the mechanism by which apps will bill customers for their subscriptions.
View subscription contracts object descriptions
API object | Description |
---|---|
Subscription contract | Represents the agreement for a set of items delivered to a customer, on a specific billing and delivery schedule and at a specific price.
A subscription contract is different from an order. An order is a customer's completed request to purchase one or more products from a shop. An order is created when a customer completes the checkout process, during which time they provide an email address or phone number, billing address, and payment information. Subscription contracts might not be immediately available when an order is created. It's best to rely on subscription contract webhooks to be notified when contracts are created. |
Contract line | Represents the price and quantity of items in a subscription contract. |
Pricing policy | Represents the future intent to change the price after a given number of billing cycles. When the customer completes a checkout, Shopify creates a contract, and the app developer is notified of which policies the customer chose. |
Subscription order line | Links a subscription contract to its related order line items, and optionally, to the billing attempt that created this order line. A subscription order line is created when one of the following events occurs:
|
Subscription draft | Represents the intent to change a subscription. If a customer wants to change their subscription, then the app needs to create a subscription draft and commit the change to make the changes active. |
Draft change | Represents a change made to a subscription draft, such as changing a contract line. |
Billing attempt | Represents an attempt at executing a billing cycle and charging the customer payment method for a subscription contract. |
Query the available contracts on a store
To view the available contracts on a store, you can query SubscriptionContracts
. In the following example, the response body returns all of the available contracts, including the status of the contract and information about the customer and billing policy.
A subscription contract can have one of the following statuses:
ACTIVE
: The contract is active and continuing as per its policies.PAUSED
: The contract is temporarily paused and is expected to resume in the future.EXPIRED
: The contract has ended per the expected circumstances. All billing and delivery cycles of the subscriptions were executed.CANCELED
: The contract ended by an unplanned customer action.FAILED
: The contract ended because billing failed and no further billing attempts are expected.
Example request:
POST /admin/api/2021-01/graphql.json
query {
subscriptionContracts(first: 10) {
edges {
node {
id
createdAt
status
nextBillingDate
customer {
firstName
lastName
}
billingPolicy {
interval
intervalCount
}
}
}
}
}
JSON response:
{
"data": {
"subscriptionContracts": {
"edges": [
{
"node": {
"id": "gid://shopify/SubscriptionContract/1",
"createdAt": "2020-06-16T19:01:46Z",
"status": "ACTIVE",
"nextBillingDate": "2020-06-01T00:00:00Z",
"customer": {
"firstName": "John",
"lastName": "Smith"
},
"billingPolicy": {
"interval": "MONTH",
"intervalCount": 3
}
}
},
{
"node": {
"id": "gid://shopify/SubscriptionContract/2",
"createdAt": "2020-06-16T19:04:15Z",
"status": "ACTIVE",
"nextBillingDate": "2020-06-01T00:00:00Z",
"customer": {
"firstName": "Jane",
"lastName": "Smith"
},
"billingPolicy": {
"interval": "MONTH",
"intervalCount": 3
}
}
}
]
}
},
"extensions": {
"cost": {
"requestedQueryCost": 32,
"actualQueryCost": 8
}
}
}
Create a new subscription draft
A subscription draft captures the intent to change a subscription contract. Apps can incrementally build subscription contracts.
A subscription draft provides a way to get the projected state of the contract with all of the updates applied. Subscription contracts should always be up-to-date and accurate so that you can report on subscriptions, email subscribers, and build flows based on subscription changes.
Update a subscription draft
You can call the subscriptionDraftUpdate
mutation to make changes to a subscription draft. In the following example, the delivery policy is changed to every two months.
Example request
POST /admin/api/2021-01/graphql.json
mutation {
subscriptionDraftUpdate(
draftId: "gid://shopify/SubscriptionDraft/22"
input: { deliveryPolicy: { interval: MONTH, intervalCount: 2 } }
) {
draft {
id
}
userErrors {
field
message
}
}
}
JSON response:
{
"data": {
"subscriptionDraftUpdate": {
"draft": {
"id": "gid://shopify/SubscriptionDraft/22"
},
"userErrors": []
}
},
"extensions": {
"cost": {
"requestedQueryCost": 10,
"actualQueryCost": 10,
"throttleStatus": {
"maximumAvailable": 1000.0,
"currentlyAvailable": 990,
"restoreRate": 50.0
}
}
}
}
Add a line to the subscription draft
You can call the subscriptionDraftLineAdd
mutation to add a subscription line to the subscription draft. In the following example, a subscription line is added to specify a product variant with its quantity and price.
Example request:
POST /admin/api/2021-01/graphql.json
mutation {
subscriptionDraftLineAdd(
draftId: "gid://shopify/SubscriptionDraft/7"
input: {
productVariantId: "gid://shopify/ProductVariant/2"
quantity: 20
currentPrice: 25.00
}
) {
lineAdded {
id
quantity
productId
variantId
variantImage {
id
}
title
variantTitle
currentPrice {
amount
currencyCode
}
requiresShipping
sku
taxable
}
draft {
id
}
userErrors {
field
message
code
}
}
}
JSON response:
{
"data": {
"subscriptionDraftLineAdd": {
"lineAdded": {
"id": "gid://shopify/SubscriptionLine/818b344f-1e7f-4b0e-9fc2-2b749d4b5494",
"quantity": 20,
"productId": "gid://shopify/Product/1",
"variantId": "gid://shopify/ProductVariant/2",
"variantImage": {
"id": "gid://shopify/ImageSource/1474738389014"
},
"title": "Aerodynamic Wool Coat",
"variantTitle": "Rustic Plastic Computer",
"currentPrice": {
"amount": "25.0",
"currencyCode": "USD"
},
"requiresShipping": true,
"sku": "",
"taxable": true
},
"draft": {
"id": "gid://shopify/SubscriptionDraft/7"
},
"userErrors": []
}
},
"extensions": {
"cost": {
"requestedQueryCost": 11,
"actualQueryCost": 11,
"throttleStatus": {
"maximumAvailable": 1000.0,
"currentlyAvailable": 989,
"restoreRate": 50.0
}
}
}
}
Update a line on a subscription contract
You can call the subscriptionDraftLineUpdate
mutation to update a line on the subscription draft. In this example, quantity
is updated to 10
.
Example request:
POST /admin/api/2021-01/graphql.json
mutation {
subscriptionDraftLineUpdate(
draftId: "gid://shopify/SubscriptionDraft/229541"
lineId: "gid://shopify/SubscriptionLine/9c952e0f-43df-4881-84a1-f720003c43a3"
input: { quantity: 10 }
) {
lineUpdated {
id
quantity
productId
variantId
variantImage {
id
}
title
variantTitle
requiresShipping
sku
taxable
}
draft {
id
}
userErrors {
field
message
code
}
}
}
JSON response:
{
"data": {
"subscriptionDraftLineUpdate": {
"lineUpdated": {
"id": "gid://shopify/SubscriptionLine/9c952e0f-43df-4881-84a1-f720003c43a3",
"quantity": 10,
"productId": "gid://shopify/Product/5515510218917",
"variantId": "gid://shopify/ProductVariant/35599671197861",
"variantImage": {
"id": "gid://shopify/ProductImage/18372397138085"
},
"title": "Shoes",
"variantTitle": null,
"requiresShipping": true,
"sku": null,
"taxable": true
},
"draft": {
"id": "gid://shopify/SubscriptionDraft/229541"
},
"userErrors": []
}
},
"extensions": {
"cost": {
"requestedQueryCost": 11,
"actualQueryCost": 11,
"throttleStatus": {
"maximumAvailable": 5000.0,
"currentlyAvailable": 4989,
"restoreRate": 250.0
}
}
}
}
Remove a line on a subscription draft
You can call the subscriptionDraftLineRemove
mutation and supply the lineId
to remove a subscription line on the subscription draft.
Example request:
POST /admin/api/2021-01/graphql.json
mutation {
subscriptionDraftLineRemove(
draftId: "gid://shopify/SubscriptionDraft/7"
lineId: "gid://shopify/SubscriptionLine/cb0141b4-93fc-4dde-b6dc-53933b596b10"
) {
lineRemoved {
id
}
draft {
id
}
userErrors {
field
message
code
}
}
}
JSON response:
{
"data": {
"subscriptionDraftLineRemove": {
"lineRemoved": {
"id": "gid://shopify/SubscriptionLine/cb0141b4-93fc-4dde-b6dc-53933b596b10"
},
"draft": {
"id": "gid://shopify/SubscriptionDraft/7"
},
"userErrors": []
}
},
"extensions": {
"cost": {
"requestedQueryCost": 10,
"actualQueryCost": 10
}
}
}
Commit a subscription draft
When you are satisfied with the state of the draft subscription, you can commit it. When you commit a draft subscription, all of the changes are made active.
Example request
POST /admin/api/2021-01/graphql.json
mutation {
subscriptionDraftCommit(draftId: "gid://shopify/SubscriptionDraft/22") {
contract {
id
}
userErrors {
field
message
}
}
}
JSON response:
{
"data": {
"subscriptionDraftCommit": {
"contract": {
"id": "gid://shopify/SubscriptionContract/22"
},
"userErrors": []
}
},
"extensions": {
"cost": {
"requestedQueryCost": 10,
"actualQueryCost": 10,
"throttleStatus": {
"maximumAvailable": 1000.0,
"currentlyAvailable": 990,
"restoreRate": 50.0
}
}
}
}
Viewing subscription contract details
The View subscription button on the customer subscriptions card and the order subscriptions card allows merchants to navigate to the app and view the subscription contract details.
Customers page in the Shopify admin
Order page in the Shopify admin
Redirecting to the subscription contract within the app
The View subscription button is hardcoded and isn't supported by app extensions. In order for the merchant to be directed to the relevant subscription contract, the app must implement a specific endpoint.
The hardcoded link has the following format:
{app_application_url}/subscriptions?customer_id={customer_id}&hmac={hmac}&id={subscription_contract_id}&shop={myshopify_domain}
Once implemented, the endpoint redirects to the subscription contract page within the app for the subscription defined by subscription_contract_id
.
Update a subscription contract
When a customer updates their payment method or makes a change to their subscription, you need to update the appropriate subscription contract.
To update a subscription contract, use the subscriptionContractUpdate
mutation and supply the subscription contract ID. The call returns a draft with the existing state of the contract.
Example request:
POST /admin/api/2021-01/graphql.json
mutation {
subscriptionContractUpdate(
contractId: "gid://shopify/SubscriptionContract/22"
) {
draft {
id
}
userErrors {
field
message
}
}
}
JSON response:
{
"data": {
"subscriptionContractUpdate": {
"draft": {
"id": "gid://shopify/SubscriptionContract/22"
},
"userErrors": []
}
},
"extensions": {
"cost": {
"requestedQueryCost": 10,
"actualQueryCost": 10,
"throttleStatus": {
"maximumAvailable": 1000.0,
"currentlyAvailable": 990,
"restoreRate": 50.0
}
}
}
}
Create a billing attempt
To schedule and automate the billing of subscriptions, apps must create a billing attempt. A subscription is renewed when an app makes a billing attempt.
A billing attempt represents an attempt at executing a billing cycle and charging the customer payment method for a subscription contract. A billing attempt executes a contract in its current state and ensures that the app’s contract data is synced with Shopify before billing.
Statuses
A billing attempt starts in a pending status. After it has been processed, it either transitions to successful or failed, both of which are terminal states:
- If the billing attempt is successful, then an order is created.
- If the billing attempt fails, then it means that the transaction has failed.
Example call
To create a billing attempt, specify the following inputs in the subscriptionBillingAttemptCreate
mutation:
subscriptionContractId
: The ID of the subscription contract.subscriptionBillingAttemptInput
idempotencyKey
: A unique key generated by the client to avoid duplicate payments.test
: Whether to execute the payment in test mode, if possible. The default isfalse
. When set totrue
, it will ignore the payment gateway’s mode. When set tofalse
, it will use the payment gateway’s mode.
Billing attempts are processed asynchronously, which means the resulting order will not be available right away. You can fetch the billing attempt and inspect the ready
field to find out whether the order has been created (true
) or not (false
).
Example request:
POST /admin/api/2021-01/graphql.json
mutation {
subscriptionBillingAttemptCreate(
subscriptionContractId: "gid://shopify/SubscriptionContract/1"
subscriptionBillingAttemptInput: { idempotencyKey: "abc123" }
) {
subscriptionBillingAttempt {
id
errorMessage
nextActionUrl
order {
id
}
ready
}
}
}
JSON response:
{
"data": {
"subscriptionBillingAttemptCreate": {
"subscriptionBillingAttempt": {
"id": "gid://shopify/SubscriptionBillingAttempt/32933",
"errorMessage": null,
"nextActionUrl": null,
"order": null,
"ready": false
}
}
},
"extensions": {
"cost": {
"requestedQueryCost": 11,
"actualQueryCost": 11,
"throttleStatus": {
"maximumAvailable": 5000.0,
"currentlyAvailable": 4989,
"restoreRate": 250.0
}
}
}
}
Since the order isn't ready right away, you can query the SubScriptionBillingAttempt to get the resulting order information.
Example request:
POST /admin/api/2021-01/graphql.json
query {
subscriptionBillingAttempt(
id: "gid://shopify/SubscriptionBillingAttempt/524310"
) {
id
errorMessage
nextActionUrl
order {
id
}
ready
}
}
JSON response:
{
"data": {
"subscriptionBillingAttempt": {
"id": "gid://shopify/SubscriptionBillingAttempt/32933",
"errorMessage": null,
"nextActionUrl": null,
"order": {
"id": "gid://shopify/Order/2014567596054"
},
"ready": true
}
},
"extensions": {
"cost": {
"requestedQueryCost": 2,
"actualQueryCost": 2,
"throttleStatus": {
"maximumAvailable": 1000.0,
"currentlyAvailable": 998,
"restoreRate": 50.0
}
}
}
}
Subscription-related webhooks
Your app can subscribe to the following subscription-related webhooks as of the 2021-01 API version. To learn how to set up and consume webhooks, refer to our webhooks tutorial.
customer_payment_methods/create
Description | Payload | Required scope |
---|---|---|
Emitted when a payment method is created. | { customer_id, token, instrument_type, payment_instrument, } | read_customer_payment_methods |
customer_payment_methods/update
Description | Payload | Required scope |
---|---|---|
Emitted when a payment method is updated. | { customer_id, token, instrument_type, payment_instrument, } | read_customer_payment_methods |
customer_payment_methods/revoke
Description | Payload | Required scope |
---|---|---|
Emitted when the payment method is revoked. | Not applicable | read_customer_payment_methods |
subscription_contracts/create
Description | Payload | Required scope |
---|---|---|
Emitted when a contract is first created. This is deferred until after the order is created. | { admin_graphql_api_id, id, billing_policy, delivery_policy, admin_graphql_api_customer_id, customer_id, currency_code, status, admin_graphql_api_origin_order_id, origin_order_id } | read_own_subscription_contracts |
subscription_contracts/update
Description | Payload | Required scope |
---|---|---|
Emitted when a contract is updated. | { admin_graphql_api_id, id, billing_policy, delivery_policy, admin_graphql_api_customer_id, customer_id, currency_code, status, admin_graphql_api_origin_order_id, origin_order_id } | read_own_subscription_contracts |
subscription_billing_attempts/success
Description | Payload | Required scope |
---|---|---|
Emitted when billing succeeds. | { id, admin_graphql_api_id, idempotency_key, order_id, admin_graphql_api_order_id, subscription_contract_id, admin_graphql_api_subscription_contract_id, ready, error_message, } | read_own_subscription_contracts |
subscription_billing_attempts/failure
Description | Payload | Required scope |
---|---|---|
Emitted when billing fails. |
{
id,
admin_graphql_api_id,
idempotency_key,
order_id,
admin_graphql_api_order_id,
subscription_contract_id,
admin_graphql_api_subscription_contract_id,
ready,
error_message,
}
In this case, order_id might be null . The error_message will be populated. |
read_own_subscription_contracts |
Next steps
Create and manage discounts on subscriptions: Learn how to apply different types of discounts on subscriptions, including percentage, fixed amount, and free shipping discounts.
Create and manage subscription shipping rates and delivery methods: Learn how to update delivery methods on subscriptions and understand the shipping rates customers see during checkout.