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.
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'export SELLAPP_VARIANT_ID='4321'2. Create Natya's invoice and checkout session
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.
{"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"}}{"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
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.