StrimzStrimz
Hosted checkout

Success and cancel URLs

Where hosted checkout sends the payer after they pay, subscribe or cancel, the placeholders you can use, and what the URLs must look like.

When a payer completes a payment on the full-page hosted checkout, or hits its Cancel button, we send them back to you. You decide where by setting successUrl and cancelUrl on paymentSessions.create.

const session = await strimz.paymentSessions.create({
  amount: '10000000',
  currency: 'USDC',
  successUrl: 'https://your-site.com/thanks?session={CHECKOUT_SESSION_ID}',
  cancelUrl: 'https://your-site.com/cancelled',
})

Both are optional. If you don't set them, the payer stays on the hosted page: it shows "Payment complete" with the transaction link, or "Payment cancelled. Nothing was charged." Most merchants set them.

These URLs apply to the full-page checkout only. A checkout opened in a popup or embedded with @strimz/sdk-react never navigates. It reports the result to your page instead, through onSuccess, onCancel and onError.

Placeholders

You can put two placeholders in the URL and the checkout fills them in when it sends the payer back:

{CHECKOUT_SESSION_ID} is the PaymentSession id. You'll want this so you can look the session up server-side and confirm payment.

{ON_CHAIN_TX_HASH} is the transaction hash. Only available on successUrl after the session confirms. If you put it on cancelUrl, it'll be blank. Useful for showing the customer a link to Arcscan ("View your transaction on chain").

The substitution is a find-and-replace in the URL string. Values are URL-encoded so they survive your query-string parser:

successUrl: 'https://your-site.com/thanks?session={CHECKOUT_SESSION_ID}&tx={ON_CHAIN_TX_HASH}'
// → https://your-site.com/thanks?session=cmq4wck6a0002je6lfmb5bgjp&tx=0x4d32...

Subscription plans

Plans take the same two URLs, on subscriptionPlans.create or later with subscriptionPlans.update. Their placeholders are {SUBSCRIPTION_ID} and {ON_CHAIN_TX_HASH}. See Plans: return URLs.

Validation

We're strict about the URLs you can pass.

The URL has to be https:// or http://. Anything else (javascript:, data:, file:) fails validation at API call time with 400 invalid_request, and param names the field. The session is never created with a bad value; you get the error before anything else happens.

We allow http:// because some merchants need it for local dev (http://localhost:3000/thanks). Production should always be https://.

We don't allow relative paths. The URL has to be absolute, including the protocol. If you pass /thanks, validation rejects it.

That's the whole validation. We don't try to verify the domain belongs to your merchant or that the URL responds to a request. There's no useful guarantee we could give there. The validation exists to keep dangerous protocols out of the redirect path; everything else is your responsibility.

Don't rely on callbacks for security

This is the part to internalize before going to production. The callback URL is public. Anyone can navigate to your success page without ever paying. Don't grant the entitlement at the success page; grant it on the webhook.

The pattern that works:

  1. Customer pays on hosted checkout.
  2. The checkout sends them to your successUrl?session=cs_….
  3. Your success page shows a "thanks, your payment is confirming" UI. Optimistic.
  4. Meanwhile, your payment.completed webhook handler runs on your server and grants the entitlement (creates the order, sends the receipt, unlocks the feature).
  5. If your success page is server-rendered and the webhook ran before the page render, the page shows "confirmed" immediately. If not, the page polls the session via your own API and updates when it sees the webhook-projected state.

The thing you don't want to do is "if ?session=cs_… is in the URL, grant the entitlement." A bored attacker who reads your URL structure can forge that query string. The webhook is signed; query strings aren't.

Multi-environment URLs

Standard advice. Pass the env-appropriate base URL:

const session = await strimz.paymentSessions.create({
  amount: '10000000',
  currency: 'USDC',
  successUrl: `${process.env.NEXT_PUBLIC_APP_URL}/thanks?session={CHECKOUT_SESSION_ID}`,
  cancelUrl: `${process.env.NEXT_PUBLIC_APP_URL}/cancelled`,
})

Strimz doesn't allowlist URLs on the merchant; we accept any URL that passes validation. If you ever see sessions created with return URLs you don't recognise, rotate your API keys.

The cancel path

If the payer hits Cancel on the full-page hosted checkout, the checkout sends them to cancelUrl with {CHECKOUT_SESSION_ID} filled in. Nothing else is added to the URL. If you didn't set a cancelUrl, the page shows "Payment cancelled. Nothing was charged."

Cancel is a navigation, not a state change. The session stays payable until it expires, so a payer who comes back to the same link can still pay. To void a link, cancel the session from your server with strimz.paymentSessions.cancel(sessionId). A session that expires without a payment does not redirect anyone; it just moves to expired.

Cancel is hidden once the payer has signed, because the payment may already be on its way. Your handler probably just needs to log the abandonment and clear any in-progress cart state.

On this page