Skip to main content

Build a subscription contract

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 guide shows you how to create subscription contracts by illustrating two use cases: "Subscribe and save" subscriptions and "Prepaid" subscriptions.

Note

Shopify automatically creates subscription contracts when products with selling plans are purchased through checkout.


Note
  • Most subscriptions, pre-order and try before you buy apps need to request API access through the Partner Dashboard. We give API access to apps that are designed according to our [principles for subscriptions, pre-order and TBYB apps] (/docs/apps/selling-strategies/purchase-options#shopifys-principles).
  • Public apps that use subscriptions, pre-order or TBYB need to meet specific requirements to be published on the Shopify App Store.
  • Custom apps created in the Shopify admin can't use subscriptions, pre-order or TBYB because these apps can't use extensions or request access to protected scopes. If you're building a solution for a single store, then build your custom app in the Partner Dashboard.

Anchor to Step 1: Create a new subscription draftStep 1: Create a new subscription draft

Tip

This guide shows you how to build a subscription contract incrementally. However, you can also create a subscription contract in one call with the subscriptionContractAtomicCreate mutation.

A subscription draft captures the intent to create a subscription contract. Apps can incrementally build subscription drafts and then commit the draft to create or update a subscription contract.

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 and email subscribers, and build flows based on subscription changes.

Depending on your selling strategy, you might create a "Subscribe and save" or a "Prepaid" subscription.

Anchor to Subscribe and save subscriptionsSubscribe and save subscriptions

The following example shows the creation of a new subscription draft for a monthly "Subscribe and save" subscription, with a minimum commitment of three orders.

For error codes related to subscription contracts, refer to SubscriptionDraftErrorCode.

Note

Using the subscriptionContractCreate mutation doesn't affect existing fulfillments that have been paid for. To edit an existing order, apps should use the orderEditBegin mutation.

POST https://{shop}.myshopify.com/api/{api_version}/graphql.json

GraphQL query

mutation {
subscriptionContractCreate(
input: {
customerId: "gid://shopify/Customer/3963517010085"
nextBillingDate: "2022-10-15"
currencyCode: USD
contract: {
note: "Dear John, I hope you enjoy this gift."
status: ACTIVE
paymentMethodId: "gid://shopify/CustomerPaymentMethod/424359b3cecf1229e56697ab1e71b3d4"
billingPolicy: { interval: MONTH, intervalCount: 1, minCycles: 3 }
deliveryPolicy: { interval: MONTH, intervalCount: 1 }
deliveryPrice: 14.99
deliveryMethod: {
shipping: {
address: {
firstName: "John"
lastName: "McDonald"
address1: "33 New Montgomery St"
address2: "#750"
city: "San Francisco"
province: "California"
country: "USA"
zip: "94105"
}
}
}
}
}
) {
draft {
id
}
userErrors {
field
message
}
}
}

JSON response

{
"data": {
"subscriptionContractCreate": {
"draft": {
"id": "gid://shopify/SubscriptionDraft/22"
},
"userErrors": []
}
},
"extensions": {
"cost": {
"requestedQueryCost": 10,
"actualQueryCost": 10,
"throttleStatus": {
"maximumAvailable": 1000.0,
"currentlyAvailable": 990,
"restoreRate": 50.0
}
}
}
}
Note

The minCycle and maxCycle are not actively performing any functions; rather, they serve as informational elements for apps. The nextBillingDate field primarily serves as a reminder for apps. Any changes made to this field won't affect the fulfillment date. Apps are responsible of keeping the contract status up-to-date and scheduling the orders.

Anchor to Prepaid subscriptionsPrepaid subscriptions

Prepaid contracts are created by defining a delivery policy that is more frequent than the billing policy. In the following example, the policies combine to define a prepaid subscription with three monthly deliveries.

For error codes related to subscription contracts, refer to SubscriptionDraftErrorCode.

Note

Using the subscriptionContractCreate mutation does not affect existing fulfillments that have been paid for. To edit an existing order, apps should use the orderEditBegin mutation.

POST https://{shop}.myshopify.com/api/{api_version}/graphql.json

GraphQL query

mutation {
subscriptionContractCreate(
input: {
customerId: "gid://shopify/Customer/3963517010085"
nextBillingDate: "2022-10-15"
currencyCode: USD
contract: {
note: "Dear Sam, I hope you enjoy this gift."
status: ACTIVE
paymentMethodId: "gid://shopify/CustomerPaymentMethod/424359b3cecf1229e56697ab1e71b3d4"
billingPolicy: { interval: MONTH, intervalCount: 3 }
deliveryPolicy: { interval: MONTH, intervalCount: 1 }
deliveryPrice: 14.99
deliveryMethod: {
shipping: {
address: {
firstName: "John"
lastName: "McDonald"
address1: "33 New Montgomery St"
address2: "#750"
city: "San Francisco"
province: "California"
country: "USA"
zip: "94105"
}
}
}
}
}
) {
draft {
id
}
userErrors {
field
message
}
}
}

JSON response

{
"data": {
"subscriptionContractCreate": {
"draft": {
"id": "gid://shopify/SubscriptionDraft/22"
},
"userErrors": []
}
},
"extensions": {
"cost": {
"requestedQueryCost": 10,
"actualQueryCost": 10,
"throttleStatus": {
"maximumAvailable": 1000.0,
"currentlyAvailable": 990,
"restoreRate": 50.0
}
}
}
}

Anchor to Step 2: Add a line to the subscription draftStep 2: 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:

POST https://{shop}.myshopify.com/api/{api_version}/graphql.json

GraphQL query

mutation {
subscriptionDraftLineAdd(
draftId: "gid://shopify/SubscriptionDraft/22"
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/22"
},
"userErrors": []
}
},
"extensions": {
"cost": {
"requestedQueryCost": 11,
"actualQueryCost": 11,
"throttleStatus": {
"maximumAvailable": 1000.0,
"currentlyAvailable": 989,
"restoreRate": 50.0
}
}
}
}

Anchor to Step 3: Commit the subscription draftStep 3: Commit the subscription draft

When you're satisfied with the state of the subscription draft, you can commit it. When you commit a draft subscription, all of the changes are made active:

POST https://{shop}.myshopify.com/api/{api_version}/graphql.json

GraphQL query

mutation {
subscriptionDraftCommit(draftId: "gid://shopify/SubscriptionDraft/22") {
contract {
id
}
userErrors {
field
message
}
}
}

JSON response

{
"data": {
"subscriptionDraftCommit": {
"contract": {
"id": "gid://shopify/SubscriptionContract/33"
},
"userErrors": []
}
},
"extensions": {
"cost": {
"requestedQueryCost": 10,
"actualQueryCost": 10,
"throttleStatus": {
"maximumAvailable": 1000.0,
"currentlyAvailable": 990,
"restoreRate": 50.0
}
}
}
}

Anchor to Viewing subscription contract detailsViewing 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.

Anchor to Customers page in the Shopify adminCustomers page in the Shopify admin

Customers page screenshot

Anchor to Order page in the Shopify adminOrder page in the Shopify admin

Order page screenshot

Anchor to Redirecting to the subscription contract within the appRedirecting to the subscription contract within the app

To redirect merchants to the relevant subscription contract, the app needs to implement a specific endpoint. After it's implemented, the endpoint redirects to the subscription contract page within the app for the subscription defined by subscription_contract_id.

You can customize the View subscription link by managing the Subscription link app extension from your app through the Shopify CLI.

To learn how to create and manage a subscription link extension from the Shopify CLI, refer to Start building subscription link extensions.

If you don't customize the View subscription link, then the link is hardcoded. The hardcoded link has the following format:

{app_application_url}/subscriptions?customer_id={customer_id}&hmac={hmac}&id={subscription_contract_id}&shop={myshopify_domain}


Anchor to Step 4: Create a billing attemptStep 4: Create a billing attempt

To bill a subscription contract and create an order, apps need to 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 based on the billing cycle at the origin time if provided. Otherwise, the billing attempt is created for the current billing cycle by default. You can also create a billing attempt on a specific billing cycle.

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.

    If an action is pending on the part of the customer in regards to 3D Secure, then a 3D Secure challenge can occur before the billing attempt transitions to a terminal state.

Note

A billing attempt can fail if Shopify's fraud analysis service has flagged a subscription contract's origin order. It is strongly recommended to check the order risk level of a contract's origin order before executing a billing attempt.

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.
    • originTime: An optional field that changes the way fulfillment intervals are calculated. If nothing is provided, fulfillment is calculated using the date that the billing attempt was successful. Otherwise, fulfillment is calculated using the provided originTime value. The UTC offset of originTime should match the shop's timezoneOffset.

    Billing attempts are processed asynchronously, which means the resulting order won't 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).

Note

If you have adopted Subscriptions Billing Cycle APIs, you can create orders by charging a billing cycle directly. This approach enables more precise management of billing cycles by directly linking order creation to the specific cycle being billed.

POST https://{shop}.myshopify.com/api/{api_version}/graphql.json

GraphQL query

mutation {
subscriptionBillingAttemptCreate(
subscriptionContractId: "gid://shopify/SubscriptionContract/33"
subscriptionBillingAttemptInput: {
idempotencyKey: "abc123"
originTime: "2022-10-30T04:05:02+14:00"
}
)
{
subscriptionBillingAttempt {
id
originTime
errorMessage
nextActionUrl
order {
id
}
ready
}
}
}

JSON response

{
"data": {
"subscriptionBillingAttemptCreate": {
"subscriptionBillingAttempt": {
"id": "gid://shopify/SubscriptionBillingAttempt/82",
"originTime": "2022-10-30T04:05:02+14:00",
"errorMessage": null,
"nextActionUrl": null,
"order": null,
"ready": false
}
}
},
"extensions": {
"cost": {
"requestedQueryCost": 11,
"actualQueryCost": 11
}
}
}

Because the order isn't ready immediately, you can query the subscriptionBillingAttempt to get the resulting order information.

POST https://{shop}.myshopify.com/api/{api_version}/graphql.json

GraphQL query

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

Shopify handles 3D Secure authentication by emailing the customer when the financial institution requires a challenge. This flow is demonstrated in the diagram below:

Subscription contracts objects diagram

You can poll the subscriptionBillingAttempt object until the nextActionUrl field is available to see the URL.

Note

The subscription_billing_attempts/success and subscription_billing_attempts/failure webhooks aren't triggered until the challenge is completed. If the customer doesn't complete the challenge, then your app won't be notified.

POST https://{shop}.myshopify.com/api/{api_version}/graphql.json

GraphQL query

query {
subscriptionBillingAttempt(
id: "gid://shopify/SubscriptionBillingAttempt/123"
) {
id
errorMessage
errorCode
nextActionUrl
order {
id
}
ready
}
}

JSON response

{
"data": {
"subscriptionBillingAttempt": {
"id": "gid:\/\/shopify\/SubscriptionBillingAttempt\/123",
"errorMessage": null,
"errorCode": null,
"nextActionUrl": "https:\/\/example.com\/subscriptions\/billing\/959c1a7cec286e06bfe7f16ff351c101\/challenge",
"order": null,
"ready": false
}
},
"extensions": {
"cost": {
"requestedQueryCost": 2,
"actualQueryCost": 2
}
}
}

Anchor to About re-billing failed payment attemptsAbout re-billing failed payment attempts

It's up to apps to attempt re-billing for failed payment attempts. We expose many signals to help you make the right decision about when to re-bill failed payment attempts and how often.

  • Only rebill payment attempts that failed with error codes that make sense to retry, such as insufficient_funds.
  • Avoid re-billing failed payments with the same customer payment method more than 30 times in 35 days. These requests will be failed and the payment method will be revoked.

You can keep track of how Shopify correlates failed payments by leveraging the payment_session_id and payment_group_id fields. Retrying billing for the same contract identity will result in billing attempts with the same payment_group_id. You can use this to track all failed, or the final successful, billing attempt linked to a final order. All billing attempts that kept their payment details identical will share the same payment_session_id. When surfacing merchants' payment success metrics, ensure that only the last billing attempt in a group that shares the same payment_session_id and payment_group_id is counted, as all the billing attempts in that group were retries of one another.

Similar to creating a new order through checkout, the availability of inventory is checked during the billing attempt process. Merchants can adjust inventory tracking so that they can continue to sell product variants when out of stock. They can also adjust inventory tracking to prevent selling product variants when out of stock.

If one or more of a subscription's product variants are out of stock (and aren't configured to continue selling), then the billing attempt moves to a failed state with either an insufficient inventory or a inventory location error.



Anchor to Shopify CLI commandsShopify CLI commands

The Shopify CLI includes commands that help you build apps and themes for Shopify. You can use these commands to perform tasks like creating new apps and themes, starting development servers, and deploying your code.

Note

The Shopify CLI is available as an npm package and as a standalone binary.

The following commands help you build Shopify apps:

CommandDescription
shopify app buildBuild the app
shopify app config linkLink an app to a configuration file
shopify app config pushPush your app configuration to the Partner Dashboard
shopify app deployDeploy the app
shopify app devStart a development server
shopify app env pullPull app and extensions environment variables
shopify app function buildBuild a function
shopify app function runRun a function
shopify app function schemaPrint the schema for a function
shopify app generate extensionGenerate a new app extension
shopify app generate schemaGenerate GraphQL types for the Admin API
shopify app infoPrint basic information about your app and extensions
shopify app versions listList deployed versions

The following commands help you authenticate with Shopify:

CommandDescription
shopify auth logoutLog out of Shopify

The following commands help you build Shopify themes:

CommandDescription
shopify theme checkRun theme checks
shopify theme deleteDelete remote themes
shopify theme devStart a development server
shopify theme initCreate a new theme
shopify theme language-serverStart a Language Server Protocol server
shopify theme listList remote themes
shopify theme openOpen your theme in the admin or onine store
shopify theme packagePackage your theme
shopify theme publishSet a remote theme as the live theme
shopify theme pullDownload your remote theme files locally
shopify theme pushUpload your local theme files to Shopify
shopify theme shareCreate a shareable, unpublished theme preview

The following commands help you work with the Shopify CLI:

CommandDescription
shopify searchSearch for CLI commands
shopify upgradeUpgrade the Shopify CLI
shopify versionPrint the version number


Was this page helpful?