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:
mode=subscriptionand the CAD monthly price for your planadaptive_pricing[enabled]=falseandcurrency=cad, so every buyer pays in CADclient_reference_idset to your tenant id, plusmetadata.product=usage-laband the plan on both the session and the subscription- No promotion codes. After payment you land on /welcome/
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:
- Verifies the
Stripe-SignatureHMAC and ignores duplicate event ids. - For
checkout.session.completed: requires aten_client_reference_idthat exists in our database, and either one of our Payment Links or (for sessions)metadata.product=usage-labwith one of our plans or prices. A different product's payment link is always ignored. - For subscription updates and cancellations: acts only on a subscription already stored on one of our accounts, and only on one of our prices.
- 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
- Playground: validate a usage event in your browser
- Usage event validator: check event JSON and idempotency keys
- Drop-in LLM metering helper + margin calculator
- Usage-based pricing calculator (LLM tokens or events)
- Live end-to-end demo
- API docs
- Quickstart: meter an AI app
- Code examples
- Metering usage for Stripe usage-based billing
- Deduping usage events with idempotency keys
- Backfilling late usage events: replays, late windows and adjustments
- Usage Lab vs UsageBox: an honest comparison
- Metronome and Orb alternatives for small SaaS teams
- How to meter LLM token usage per customer for billing
- Stripe Billing Meters vs a usage ledger: when you need both