Payment Sessions vs Payment Links: Which to Use When
Solpaygate gives you two ways to hand a customer a checkout URL: short-lived payment sessions created from your backend, or persistent payment links created in the dashboard. They look similar from the customer's side — both open the hosted /pay page — but they behave differently for reconciliation, security, and integration effort. Here's how to choose.
Payment sessions in one line
A payment session is a short-lived, backend-created checkout token: your server calls POST /api/company/{companyId}/payment-session and gets back a 30-minute URL you hand to one specific customer for one specific order.
Payment links in one line
A payment link is a long-lived, shareable URL created in the dashboard for a fixed product or amount: anyone with the link can pay it, and each payment is tracked separately in your dashboard.
The difference matters. Sessions are ephemeral and personalized; links are durable and public.
When sessions win
Sessions fit anywhere your backend already knows who is checking out:
- A cart on your web app — the customer clicks Pay, your backend creates a session with their user ID and cart ID as
orderId, and redirects. - A SaaS renewal — your cron job creates a session, emails it with a 30-minute window, and marks the invoice "sent".
- An in-person POS or kiosk — your terminal creates a session per transaction and displays the QR code.
- A mobile app flow — you create the session server-side and hand the URL to a WebView.
Because each session is one-shot and scoped to an orderId, reconciliation is trivial: every webhook you receive maps to exactly one order in your database. Sessions also carry an optional returnUrl, so after payment the customer's browser is redirected back to your success page automatically.
Security-wise, a session URL is worthless once the customer has paid or the 30 minutes are up. If it leaks, the damage window is small.
When links win
Links fit anywhere the payer isn't known ahead of time or the same URL is reused many times:
- A "Buy me a coffee" tip button on a personal site.
- A single-price course, ebook, or digital download sold to whoever shows up.
- A donation page for a nonprofit.
- A QR code printed on a poster, menu, or invoice PDF.
Because a link is public, you can't rely on the payer having a known user record. You get the payment, the amount, the currency, and whatever metadata the customer entered (email, note) at checkout. Reconciliation is by paymentId rather than orderId — every payment is a new row, and you match it to a customer by whatever contact detail they gave you at the /pay page.
A hybrid pattern
Some flows want both. A common pattern for a mixed audience of logged-in and anonymous buyers:
- Publish a payment link for a fixed-price product on your marketing pages.
- For logged-in customers, generate a session on the fly instead — so the payment ties to their user record automatically.
- For anonymous visitors, fall back to the link and rely on the /pay page to capture their email.
The backend logic:
function checkoutUrl(user, product) {
if (user) {
// Session — tied to the user, expires in 30 min
return createSession({
userId: user.id,
orderId: `${user.id}:${product.id}:${Date.now()}`,
description: product.name,
returnUrl: `${SITE}/thanks`,
});
}
// Public link — anyone can pay
return product.paymentLinkUrl;
}
The customer sees one button either way; your reconciliation script handles both cases and normalizes them into your own payments table.
If your flow is closer to an e-commerce cart, sessions almost always win — the wiring is walked through step-by-step in our developer guide to the payment API. If instead you're building a top-up flow where users add credit to an account balance, the pattern is a slight variation again; see building a crypto wallet top-up flow for the full write-up.
Ready to accept crypto payments?
Solpaygate lets your business accept SOL, USDT, and USDC on Solana with a single API call. Non-custodial, no smart contract to deploy.
Start for free