Guides

Create a checkout

Create Natya's invoice and obtain a customer checkout URL.

This continues the Founder memo circle example: Natya buys the configured STRIPE subscription variant for $19.99 monthly. First create the purchase through the invoice API, then get the checkout URL to send to Natya.

1. Prepare the variant and receiver

You need Bash, cURL, jq, an invoice-enabled API token with store permission, and a purchasable variant from the same store. Replace the key, slug, and variant ID with your own values.

Before creating the invoice, configure and test a signed webhook receiver for order.created, order.paid, order.completed, and subscription.created. The single-page quickstart includes the registration and test requests. The receiver must save the full payload and recoverable pending work before returning a success response.

These are real store records. Opening and completing checkout can charge the customer and start recurring billing.

Run once in the same Bash session
set -euo pipefail
export SELLAPP_API_BASE_URL='https://sell.app/api'
export SELLAPP_API_KEY='replace-me'
export SELLAPP_STORE_SLUG='launch-lab'

api() {
  local method="$1" path="$2"
  shift 2
  curl --silent --show-error --fail-with-body \
    --request "$method" --url "${SELLAPP_API_BASE_URL}${path}" \
    --header "Authorization: Bearer ${SELLAPP_API_KEY}" \
    --header "X-STORE: ${SELLAPP_STORE_SLUG}" \
    --header 'Accept: application/json' \
    --dump-header /dev/stderr "$@"
}

api GET '/v2/products?limit=1' | jq '.data'
Use the variant returned by product setup
export SELLAPP_VARIANT_ID='4321'

2. Create Natya's invoice and checkout session

Create the purchase and obtain its actual checkout URL
invoice_json=$(api POST /v2/invoices \
  --header 'Content-Type: application/json' \
  --data "$(jq -nc --arg variant "$SELLAPP_VARIANT_ID" '{
    customer_email:"natya.sadella@example.com",
    payment_method:"STRIPE",
    product_variants:{($variant):{quantity:1}}
  }')")
SELLAPP_INVOICE_ID=$(jq -er '.data.id' <<< "$invoice_json")

checkout_json=$(api POST "/v2/invoices/${SELLAPP_INVOICE_ID}/checkout")
SELLAPP_CHECKOUT_URL=$(jq -er '.payment_url' <<< "$checkout_json")
printf '%s\n' "$SELLAPP_CHECKOUT_URL"

Invoice creation returns 201. Its URL, when supplied, is data.checkout. The checkout operation returns 201 for a new payment session or 200 when reusing an existing session. Its URL is the top-level payment_url.

Invoice response — abbreviated
{"data":{"id":9001,"subscription_id":null,"customer_information":{"email":"natya.sadella@example.com"},"status":{"status":{"status":"PENDING"}},"checkout":"https://checkout.stripe.com/c/pay/cs_example"}}
Checkout response — abbreviated, illustrative URL
{"message":"Existing payment session has been found.","payment_url":"https://checkout.stripe.com/c/pay/cs_example","invoice":{"id":9001,"status":{"status":{"status":"PENDING"}}}}

Open the URL printed by your request, not the illustrative URL above. Completing checkout can charge the configured payment method and start recurring billing. Never automatically rerun this tutorial after a timeout: catalog and invoice creation do not provide a general idempotency key. Use the saved IDs to check what was created, and keep the request IDs for debugging before trying again.

3. Check what happened after checkout

Retrieve after the customer completes checkout
api GET "/v2/invoices/${SELLAPP_INVOICE_ID}" \
  | jq '.data | {id, status, subscription_id, product_variants}'

For this paid example, expect payment_url. A zero-priced checkout can instead return 200 with a paid invoice and no URL; fulfillment may still be queued. Do not redirect to an absent URL or mark the purchase complete based on a browser return alone.

Use signed webhooks and retrieved state. order.paid records entry into the paid state; order.completed records the completed order state, not completion of every external task, such as granting access in your app. See Fulfil an order for processing that can recover after a crash.

On this page