Checkout (Beta)
23 min
beta voltage checkout is currently in beta availability, supported payment methods, configuration, and interfaces may change confirm that the current beta fits your implementation before launching it in production voltage checkout gives merchants a hosted lightning payment page without requiring them to build and operate the payment interface themselves see the demo if you want to see a demo of what to expect from the checkout, you can view a live example here https //voltage raincheck netlify app/ raincheck demo https //voltage raincheck netlify app/ how checkout works your server creates a checkout session for an order voltage returns a hosted checkout url, checkout session id, and payment id your browser opens the returned url, preferably with the voltage checkout javascript sdk the customer pays the lightning invoice the hosted page reports status to the browser your server verifies the payment before fulfilling the order the browser experience helps the customer complete payment, but it is not the authoritative payment record a customer can close a tab, lose network access, or modify browser code, so always verify payment from your server before you integrate make sure you have a voltage wallet that you can use (mutinynet for testing, or mainnet for real money) you can learn more about those topics in the wallet setup guide docid\ alpqa ayg179ccwgf2ija or wallet management docid\ lbzudo0gzuwudhwrfcd6i article configure browser origins first, decide which browser origins you want to allow when using the checkout in an iframe or sdk overlay for example, you could declare that you only want the checkout to be shown on shop example com, as well as staging shop example com for testing and localhost 5173 for local development you can also allow your checkout to appear on any domain name , though this is not recommended if you choose "deny", then you are effectively disabling checkout for a given environment, to configure browser origins sign in to voltage open the organization and environment select manage environment find checkout select an origin policy if you use an allowlist, enter one exact origin per line save the origins formatting the origin an origin contains a scheme, hostname, and optional port it does not contain a path, query, user information, or fragment valid origins https //shop example com https //admin example com 8443 http //localhost 5173 http //127 0 0 1 5173 invalid origins shop example com https //shop example com/checkout https //shop example com?store=1 https // example com policy behavior recommended use deny all prevents browser embedding disable browser checkout access allowlist permits only exact entries in allowed origins live use and normal testing allow all permits any browser origin short lived troubleshooting only localhost and 127 0 0 1 are different origins netlify, vercel, and similar deploy previews also have origins that differ from the primary site add each origin intentionally, and review the list before launch create a checkout session a checkout session should be created from your server never call the voltage session endpoint directly from browser code, as this could expose your api key post {api base}/organizations/{organization id}/environments/{environment id}/checkout/sessions send the environment api key in the x api key header example the following node js example creates a usd 25 00 bolt11 checkout with a 15 minute lifetime const api base = 'https //voltageapi com/v1'; export async function createcheckout({ orderid, amountincents }) { const sessionid = crypto randomuuid(); const expiresat = new date(date now() + 15 60 1000) toisostring(); const response = await fetch( `${api base}/organizations/${process env voltage organization id}` + `/environments/${process env voltage environment id}/checkout/sessions`, { method 'post', headers { accept 'application/json', 'content type' 'application/json', 'x api key' process env voltage api key }, body json stringify({ id sessionid, wallet id process env voltage wallet id, payment kind 'bolt11', amount { currency 'usd', amount amountincents }, expires at expiresat, description `order ${orderid}`, metadata { order id orderid } }) } ); const checkout = await response json(); if (!response ok) { throw new error(checkout error? detail || 'unable to create checkout session '); } // store both ids with the merchant order await savecheckoutreferences(orderid, { checkoutsessionid checkout id, paymentid checkout payment id }); // return the complete url, but do not return the api key or a separate token return { checkout url checkout checkout url, checkout session id checkout id, payment id checkout payment id, expires at checkout expires at }; } use the api base, organization, environment, wallet, and api key values for the environment you intend to charge during development, those values should point to an environment containing a mutinynet wallet switch them to your live environment and mainnet wallet when you are ready to accept live payments amount encoding send integer amounts in the unit expected for the currency customer amount request value usd 1 00 { "currency" "usd", "amount" 100 } usd 25 00 { "currency" "usd", "amount" 2500 } 1 satoshi { "currency" "btc", "amount" 1000 } usd uses cents btc uses millisatoshis do not send decimal major unit values in amount amount core request fields field requirement description id required merchant generated uuid and checkout session idempotency key wallet id required wallet that receives the payment payment kind required use bolt11 for the verified beta flow amount required for fixed checkout currency and integer amount expires at required future iso 8601 timestamp fifteen minutes is a practical default payment id optional merchant generated payment uuid voltage generates one when omitted description optional payment description associated with the session metadata optional merchant references such as an order id the session id is the idempotency key reusing it with the same immutable inputs replays the request safely reusing it with different immutable inputs returns 409 conflict successful response a successful request returns 201 created { "id" "11111111 1111 4111 8111 111111111111", "payment id" "22222222 2222 4222 8222 222222222222", "checkout token" "vlt co redacted", "checkout url" "https //app voltage cloud/cs/11111111 1111 4111 8111 111111111111#vlt co redacted", "expires at" "2026 08 25t19 00 00z", "origin policy" "allowlist", "allowed origins" \["https //shop example com"] } use checkout url exactly as returned the url fragment contains a bearer credential do not log it, send it to analytics or error tracking, include it in screenshots, or move it into a query parameter open checkout with the javascript sdk load the checkout sdk \<script src="https //app voltage cloud/checkout/v1/checkout js">\</script> your browser should call your own server endpoint, receive the complete checkout url, and pass it to the sdk async function startcheckout(productid) { const response = await fetch('/api/checkout', { method 'post', headers { 'content type' 'application/json' }, body json stringify({ product id productid }) }); const checkout = await response json(); if (!response ok) { throw new error(checkout error || 'unable to create checkout session '); } let instance; instance = window\ voltagecheckout open({ checkouturl checkout checkout url, theme 'dark', dismissible true, title 'pay example store', amountdisplay { primary 'requested amount', secondary 'auto', bitcoinunit 'sats', locale 'en us', currencydisplay 'code' }, oncomplete(payload) { // this is a ui signal verification still happens on the server instance close(); showorderpendingverification(payload session id); }, onexpired() { showcheckoutexpired(); }, onfailed() { showcheckoutfailed(); }, oncancel() { showcheckoutcancelled(); }, onclose() { restoremerchantpage(); } }); } the sdk creates and sizes the iframe, validates message origins, locks page scrolling, restores focus, and removes the overlay when closed do not build a direct iframe integration unless the sdk cannot meet a verified requirement and the integration receives a separate security review sdk options option description checkouturl complete checkout url returned by voltage required theme light or dark dismissible when false, hides customer dismissal controls merchant code can still call close() title accessible iframe title describe the payment task amountdisplay primary amount, requested amount, or custom display text amountdisplay secondary auto, hidden, or custom display text amountdisplay bitcoinunit auto, btc, or sats amountdisplay locale bcp 47 locale such as en us amountdisplay currencydisplay symbol or code oncomplete checkout reported a completed payment verify it on your server onexpired session expired before completion onfailed checkout reported a failed payment oncancel checkout reported an explicit cancellation event onmessage receives every accepted voltage checkout message onclose runs after the sdk removes the overlay open() returns an object with close() — removes the overlay programmatically iframe — the iframe element created by the sdk terminal callbacks do not automatically remove the overlay this lets the hosted page display its completed, failed, or expired state call close() when your merchant experience should continue elsewhere an ordinary customer dismissal runs onclose; it does not imply payment failure or cancellation before creating another payment, check the original order on your server example completion payload terminal callback payloads can include the session and amount details { "status" "completed", "session id" "11111111 1111 4111 8111 111111111111", "amount" { "currency" "usd", "amount" 2500, "unit" "cents" }, "btc amount" { "currency" "btc", "amount" 153790000, "unit" "msats" } } use session id to update the customer interface, then verify the corresponding payment from trusted server code hosted redirect use a hosted redirect only when the sdk overlay cannot meet your requirements window\ location assign(checkout checkout url); validate navigation, return, order status, and recovery behavior before launch a redirect or browser callback must not be your only confirmation that an order was paid verify payment on your server store the checkout session id and payment id with your merchant order before fulfillment, either process a trusted voltage webhook or retrieve the payment from the payments api get {api base}/organizations/{organization id}/environments/{environment id}/payments/{payment id} authenticate from your server fulfill only after the authoritative payment state is complete, and make fulfillment idempotent so retries or duplicate webhook delivery cannot produce a second order see webhooks and the voltage payments api for the complete verification contract payment lifecycle status meaning generating voltage is creating the payment request receiving the request is ready and voltage is waiting for payment completed voltage completed the receive payment failed voltage could not complete the payment expired the session reached expires at before completion browser dismissal is not a payment status a customer may close or refresh the page while payment is still in flight give customers a merchant controlled order status page if a session is interrupted, verify the existing payment before creating another checkout if you retry session creation after an uncertain network response, reuse the original session id and immutable inputs first security checklist keep the environment api key on your server never use a vite , next public , or similar public prefix for the api key create checkout sessions through a merchant controlled server endpoint use the complete returned checkout url without reconstructing it treat the checkout token and checkout url as bearer credentials redact tokens and urls from logs, analytics, screenshots, and support tickets use an exact origin allowlist for every live browser origin treat browser callbacks as ui signals only verify payment on the server before fulfillment make order fulfillment idempotent troubleshooting symptom likely cause what to check 401 unauthorized creating a session missing or invalid api key x api key, api environment, and secret loading 403 forbidden creating a session api key lacks access organization, environment, and key permissions 409 conflict creating a session session id reused with different inputs replay the original request or generate a new uuid browser says the checkout refused to connect origin blocked by checkout framing policy exact scheme, hostname, port, and environment settings checkout says its token is missing url fragment was removed pass the complete returned checkout url unchanged sdk does not load wrong script url, csp, network, or script failure sdk url and browser console checkout remains in generating payment creation or status updates are delayed payment and session records in voltage checkout completes but merchant ui does not update callback or origin message problem parent origin, sdk version, callback handlers, and console checkout returns a plain text 5xx hosted checkout or upstream service is unavailable record the time and ids, avoid logging the token, and contact support related guides wallet setup guide docid\ alpqa ayg179ccwgf2ija staging environment docid\ ukhb7 3ektubbv hblw1t payments access docid\ txpojpm5pxe1ksxjbt2an receiving docid uq9knquoc9sgq0kuogmy webhooks docid\ pdeh 4et9aqawca5q91na voltage payments api docid\ ee9anjjlombmff1vx4ujw