Skip to main content
More in Learn

All About Subscription Contracts

Subscription contracts tie subscription plans and customers together. Each contract represents an agreement with a customer regarding what product they’re buying and how they’re paying for it. The contract is what you use to keep track of usage and renewals.

A Note On Customers

Subscription contracts cannot exist on their own, they must belong to a customer. This is why the first step when creating a subscription contract is to ensure that we have a customer. You can do this programmatically or manually through the PIM UI, using the Customers interface. Customer records may also be created as the result of orders being processed.

You can manage customers any way you like; just make sure that you have some means of authenticating them. You can use your own authentication mechanism or integrate third-party authentication services into the Service API like Userfront or Magic.link.

If a customer is associated with one or more subscription plans, you’ll see that listed in the PIM UI, under the Subscriptions section of their customer record. However, you can’t tie customers and subscriptions together through the UI. It must be done programmatically.

Creating Subscription Contracts

Once you’ve got a valid customer, created a subscription plan, and added the subscription plan to a product variant as needed, you’re ready to create a subscription contract. You can design the flow that you want, but usually, it’d be very close to what you would do on paper. First, you create a contract with your customer (subscription contract) that sets up the rules (price, metered variables, etc.), including the payment information (payment field) and the different subscription periods (initial and recurring). After the contract is created comes the payment, prepaid or paid after. Finally, there will be an order in Crystallize with the subscription contract ID and a subscription OrderItem to describe what this charge is for.

When it comes time for renewal, Crystallize will trigger a webhook so you can repeat the process: create an order and charge the customer for the price of the contract plus usage.

Here’s the mutation for creating a contract:

mutation CREATE_SUBSCRIPTION_CONTRACT(
  $input: CreateSubscriptionContractInput!
) {
  subscriptionContracts {
    create(input: $input) {
      id
    }
  }
}

The variables you pass must follow the schema type CreateSubscriptionContractInput, which you can hunt for if you click the Docs button on the right side of the Subscription API GraphQL playground (https://api.crystallize.com/{your-tenant-name}/subscriptions).

The arguments marked with “!” are required, the rest are optional. Here’s a minimal example for a subscription contract for a non-metered subscription plan:

{
  "input": {
    "customerIdentifier": "customer.name@crystallize.com",
    "subscriptionPlan": {
      "identifier": "mobile-subscription",
      "periodId": "61e01cd0ec1318c567929dc2"
    },
    "item": {
      "name": "Mobile subscription",
      "sku": "mobile-subscription"
    },
    "recurring": {
      "price": 9,
      "currency": "eur"
    },
    "status": {
      "activeUntil": "2099-01-01T00:00:00.000Z",
      "renewAt": "2099-01-01T00:00:00.000Z",
      "price": 9,
      "currency": "eur"
    }
  }
}

Let’s look at the individual components of that payload. We start out by referring to the customer via their unique identifier:

"customerIdentifier": "customer.name@crystallize.com",

Next is to get a reference to the subscription plan and period the customer wants to subscribe to. You can find these values by querying for subscription information on products.

"subscriptionPlan": {
      "identifier": "mobile-subscription",
      "periodId": "61e01cd0ec1318c567929dc2"
},

Then it’s time to specify the product in question, referred to here as the item. The SKU needs to be a valid product variant that implements the subscription plan. The name is typically the name of the variant, but you’re free to input something different.

"item": {
       "name": "Mobile subscription",
       "sku": "mobile-subscription"
},

Now you define the pricing structure for the subscription by specifying the recurring and (optionally) initial period pricing:

"recurring": {
      "price": 9,
      "currency": "eur"
 }

Finally, you need to provide the current status of the subscription. This tells Crystallize if the subscription is currently active, and when to renew it. You must also provide the current pricing, which will be the same as the recurring pricing unless you’ve specified an initial period.

"status": {
      "activeUntil": "2099-01-01T00:00:00.000Z",
      "renewAt": "2099-01-01T00:00:00.000Z",
      "price": 9,
      "currency": "eur"
 }

A Note On Subscription Pricing

You may be wondering why it’s necessary to provide the subscription price. The pricing for the subscription is defined at the product level, after all. Well, it’s for the same reason that we require you to specify pricing within the Orders API, too: flexibility. You may want to set special prices for different customers for a multitude of reasons. You’re not required to make anything different, though. If you want to pass along the regular price, that’s fine. The pricing you pass in will not be validated. It will be stored in the mutation as the agreed subscription price.

Also, if the underlying price for a product’s subscription plan changes, the pricing for existing subscription contracts will not be updated. Since the customer agreed to purchase a specific product at a specific price, you need their consent before that agreement can be altered.

Renewing Subscription Contracts

Crystallize will automatically renew a subscription contract that has a renewAt value set. In practice, this means that when the renewAt date for a subscription contract is met, the contract’s activeUntil and renewAt attributes will change, extended by the period defined by recurring.period when the contract was created.


The renewal is a silent operation; Crystallize will not send any information to the customer. How you handle renewals is completely up to you. It’s recommended that you register a webhook that listens on Subscriptions.Renewed events for this purpose:

Initiating payments is a typical chore for such webhooks. You could also send an email to notify the customer of the renewal. If the payment fails, it’s recommended that you set up a fulfilment pipeline to manage the state of orders with failed payments. You can tailor this completely to your business using the pipelines and the Service API. You can keep offering the service for months or even years, even though payments aren’t registered, or you can pause the subscription contract immediately. It’s your choice and responsibility.

Canceling Subscription Contracts

Use the subscriptionContracts.cancel mutation to cancel a subscription contract. This will clear the renewAt field but leave activeUntil untouched, effectively making the subscription run out of time.

mutation CANCEL_SUBSCRIPTION_CONTRACT(
  $id: ID!
) {
  subscriptionContracts {
    cancel(id: $id) {
      status {
        activeUntil
        renewAt
      }
    }
  }
}

Updating Subscription Contracts

For any other updates you may need, use the subscriptionContracts.update mutation:

mutation UPDATE_SUBSCRIPTION_CONTRACT(
  $id: ID!
  $input: UpdateSubscriptionContractInput!
) {
  subscriptionContracts {
    update(id: $id, input: $input)
  }
}

Once subscription contracts have been created, you may need to track the usage of metered variables and report it to Crystallize.

People showing thumbs up

Need further assistance?

Ask the Crystallize team or other enthusiasts in our slack community.

Join our slack community