CAD checkout: why we use Checkout Sessions instead of Payment Links

Last updated 2026-10-15 · Usage Lab by Canada Site Ops

The problem: Payment Links can't stay in CAD

Usage Lab bills in Canadian dollars (Pro CA$12/month, Ultra CA$36/month). Stripe Payment Links always use Adaptive Pricing, so a buyer outside Canada sees a converted local price (for example US$8.80) and a currency toggle. You can't turn this off for a single link; the Dashboard setting only covers Checkout.

The fix: a server-created Checkout Session

When you click Continue to checkout after signing up, the page calls POST /api/v1/checkout. The API checks that your account exists and creates a Stripe Checkout Session with:

If the API can't create a session, the page falls back to the plan's Payment Link, so checkout still works.

curl -X POST https://usage-lab.pages.dev/api/v1/checkout \
  -H "content-type: application/json" \
  -d '{"plan":"pro","tenant_id":"ten_...","email":"you@example.com"}'
# {"url":"https://checkout.stripe.com/c/pay/cs_...","mode":"test"}

The endpoint is rate-limited per IP and only accepts plans pro and ultra for an existing ten_ tenant.

HST and sales tax

Prices are before tax. Canadian buyers in Ontario pay 13% HST (Pro CA$12 + CA$1.56). Stripe's Managed Payments mode handles tax but requires Adaptive Pricing, which we turn off to keep prices in CAD. So CAD sessions use Stripe Tax for tax calculation instead. Until that is configured, test-mode sessions are created without tax.

How the webhook checks an event is ours

Our Stripe account also serves other products, so the webhook receives every product's events. Before changing your plan it:

  1. Verifies the Stripe-Signature HMAC and ignores duplicate event ids.
  2. For checkout.session.completed: requires a ten_ client_reference_id that exists in our database, and either one of our Payment Links or (for sessions) metadata.product=usage-lab with one of our plans or prices. A different product's payment link is always ignored.
  3. For subscription updates and cancellations: acts only on a subscription already stored on one of our accounts, and only on one of our prices.
  4. Answers 200 {"ignored":true} to everything else, without changing any account.

Related: Usage-based billing with Stripe · Billing Meters vs a usage ledger · API docs