{"templateId":"markdown","sharedDataIds":{"sidebar":"sidebar-rest-api/sidebars.yaml"},"props":{"metadata":{"markdoc":{"tagList":["admonition"]},"type":"markdown"},"seo":{"title":"Headless Stripe Payments","description":"Developer documentation for the WHMCS API — the","llmstxt":{"hide":false,"sections":[{"title":"Table of contents","includeFiles":["**/*"],"excludeFiles":[]}],"excludeFiles":[]}},"dynamicMarkdocComponents":[],"compilationErrors":[],"ast":{"$$mdtype":"Tag","name":"article","attributes":{},"children":[{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"headless-stripe-payments","__idx":0},"children":["Headless Stripe Payments"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["In a headless payment integration, WHMCS provides the payment data but does not provide the interface that displays it. Your application renders the payment form itself and keeps the client in your application while they complete checkout, instead of returning them to a WHMCS page for payment."]},{"$$mdtype":"Tag","name":"Admonition","attributes":{"type":"info","name":"Info"},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The workflow is the same for any payment gateway module that supports headless payment sessions (",{"$$mdtype":"Tag","name":"a","attributes":{"href":"https://docs.whmcs.com/payments/payment-gateway-modules/stripe-dynamic-payments","target":"_blank"},"children":["Stripe Dynamic Payments"]},", ",{"$$mdtype":"Tag","name":"a","attributes":{"href":"https://docs.whmcs.com/payments/payment-gateway-modules/paypal-payments","target":"_blank"},"children":["PayPal Payments"]},", or ",{"$$mdtype":"Tag","name":"a","attributes":{"href":"https://docs.whmcs.com/payments/payment-gateway-modules/paypal-card-payments","target":"_blank"},"children":["PayPal Card Payments"]},"). The examples below use the Stripe payment gateway and WHMCS API version 2.1.0."]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"payment-sessions","__idx":1},"children":["Payment Sessions"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["WHMCS returns the same four values in every payment session:"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":["The ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["provider"]}," value is the gateway that issued the session."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["The ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["params"]}," value is the set of keys that the gateway's SDK accepts."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["The ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["settlement"]}," value is the method that records the payment."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["The ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["sdk"]}," value is the method that loads the client-side library."]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Only the contents of the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["params"]}," value differ between gateways. For Stripe, the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["params"]}," value contains a client secret for Stripe.js. For PayPal, it contains an order ID for the PayPal SDK. Use the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["provider"]}," value to determine which gateway SDK to pass the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["params"]}," value to."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The gateway serves the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["iframe"]}," element that collects card details. The ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["iframe"]}," element submits those details directly to the gateway. The details do not pass through WHMCS or your server. This allows the merchant to use the shortest PCI DSS self-assessment. The scope of any given assessment depends on the merchant's wider environment."]},{"$$mdtype":"Tag","name":"Admonition","attributes":{"type":"info","name":"Info"},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["For more information about how Stripe describes this boundary, see ",{"$$mdtype":"Tag","name":"a","attributes":{"href":"https://docs.stripe.com/security","target":"_blank"},"children":["Stripe security"]},"."]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"the-payment-flow","__idx":2},"children":["The Payment Flow"]},{"$$mdtype":"Tag","name":"Admonition","attributes":{"type":"info","name":"Info"},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The payment flow below assumes that a cart is ready for checkout or an unpaid invoice is ready for payment. For more information, see ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/rest-api/custom-buy-flow"},"children":["Custom Buy-Flow"]},"."]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Headless payments use the following payment flow:"]},{"$$mdtype":"Tag","name":"img","attributes":{"src":"/assets/headless-stripe-flow.55465862df4e3dc6023ae9cf7f7ac6b3b588337c06a9189bef98a8461a798fde.9c1bb791.svg","alt":"The seven steps of a headless payment. WHMCS handles authentication, gateway discovery, checkout, and recording the payment. The payment gateway handles loading the SDK, mounting the Payment Element, and confirming, all in the browser.","style":{"width":"600px"}},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"1-authenticate-and-store-the-state-token","__idx":3},"children":["1. Authenticate and store the state token."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Sign the client in, and then store the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["X-Api-State"]}," token that WHMCS returns with the response."]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Every ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["/user/*"]}," endpoint and checkout body that authenticates as an existing client requires the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["X-Api-State"]}," header. This header contains the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["/user/session"]}," state token, which includes the client and user IDs if they exist."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["A guest who registers during checkout does not need a prior ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["/user/session"]}," call. Instead, the checkout response returns an ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["X-Api-State"]}," value for the new client."]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Store the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["/user/session"]}," token using a method that retains it through full-page redirects, to ensure that it is available after the client returns from a redirect payment method."]},{"$$mdtype":"Tag","name":"Admonition","attributes":{"type":"info","name":"Info"},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["For more information about the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["X-Api-State"]}," header and how to sign a client in, see ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/rest-api/authentication"},"children":["Authentication"]},"."]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The wrapper below uses the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["sessionStorage"]}," object to store the most recent value and send it with the next request:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"js","header":{"controls":{"copy":{}}},"source":"const api = (() => {\n  const KEY = 'whmcs-api-state';\n  let state = sessionStorage.getItem(KEY);\n  return async function api(path, init = {}) {\n    const res = await fetch(`/api/v2${path}`, {\n      ...init,\n      headers: {\n        'Content-Type': 'application/json',\n        ...(state ? { 'X-Api-State': state } : {}),\n        ...(init.headers || {}),\n      },\n    });\n    const refreshed = res.headers.get('X-Api-State');\n    if (refreshed) {\n      state = refreshed;\n      sessionStorage.setItem(KEY, refreshed);\n    }\n    const body = await res.json().catch(() => ({}));\n    if (!res.ok) throw Object.assign(new Error(`${path} → ${res.status}`), { status: res.status, body });\n    return body;\n  };\n})();\n","lang":"js"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"2-find-headless-capable-gateways","__idx":4},"children":["2. Find headless-capable gateways."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Request the list of payment gateways from the store and filter it to find the available payment gateways with a ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["supports_headless_payment_session"]}," value of ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["true"]},":"]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":5,"id":"using-javascript","__idx":5},"children":["Using JavaScript"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"js","header":{"controls":{"copy":{}}},"source":"const { data: gateways } = await api('/store/billing/gateways');\nconst headlessCapable = gateways.filter(g => g.supports_headless_payment_session);\n","lang":"js"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":5,"id":"using-curl","__idx":6},"children":["Using cURL"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"header":{"controls":{"copy":{}}},"source":"curl -X GET --location \"https://example.com/api/v2/store/billing/gateways\"\n  -H \"X-Api-State: <your latest state token>\"\n"},"children":[]},{"$$mdtype":"Tag","name":"Admonition","attributes":{"type":"success","name":"Note"},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["A gateway with a ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["supports_headless_payment_session"]}," value of ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["false"]}," returns a ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["422"]}," response for a ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["headless"]}," checkout."]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"3-check-out-in-headless-mode","__idx":7},"children":["3. Check out in headless mode."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Check the cart out through the checkout endpoint. Set the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["payment.mode"]}," value to ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["headless"]}," to ensure that WHMCS returns a payment session instead of a redirect:"]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":5,"id":"using-javascript-1","__idx":8},"children":["Using JavaScript"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"js","header":{"controls":{"copy":{}}},"source":"const { data: checkout } = await api(`/cart/${cartId}/checkout`, {\n  method: 'POST',\n  body: JSON.stringify({\n    paymentmethod: 'stripe_dynamic',\n    payment: {\n      mode: 'headless',\n    },\n  }),\n});\n\nconst { invoice_id, payment } = checkout;\n","lang":"js"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":5,"id":"using-curl-1","__idx":9},"children":["Using cURL"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"header":{"controls":{"copy":{}}},"source":"curl -X POST --location \"https://example.com/api/v2/cart/TLlowSWlQjGkGrC1/checkout\"\n  -H \"Content-Type: application/json\"\n  -H \"X-Api-State: <your latest state token>\"\n  -d \"{\\\"paymentmethod\\\":\\\"stripe_dynamic\\\",\\\"payment\\\":{\\\"mode\\\":\\\"headless\\\"}}\"\n"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Use the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["payment.status"]}," and ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["payment.next_action.type"]}," values from the checkout response to determine your application's next action."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["For example:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"js","header":{"controls":{"copy":{}}},"source":"const action = payment.next_action;\nif (payment.status === 'not_required')    return done(invoice_id);   // No invoice. invoice_id is 0.\nif (payment.status === 'paid')            return done(invoice_id);   // Already paid.\nif (action?.type === 'redirect')          return followRedirect(action.url);\nif (action?.type !== 'payment_session')   return handlePendingOrFailed(payment);\n\nconst session = action.session;   // { provider, params, settlement, sdk }\nif (session.provider !== 'stripe') return renderOtherProvider(session);\n","lang":"js"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["payment.status"]}," value reports the outcome:"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["paid"]}," — The payment gateway has already accepted payment."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["pending"]}," — A payment is in progress."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["action_required"]}," — The payment needs a payment session."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["not_required"]}," — The order did not generate an invoice. The ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["invoice_id"]}," value is ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["0"]}," and the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["next_action"]}," value is ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["null"]},"."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["failed"]}," — The attempt did not complete."]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["payment.next_action.type"]}," value returns ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["payment_session"]}," for headless payments. Use the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["session"]}," value to load the payment session in the browser (step 4)."]},{"$$mdtype":"Tag","name":"Admonition","attributes":{"type":"info","name":"Info"},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["WHMCS does not generate an invoice for a free billing cycle. It does generate one if a promotion discounts the order to a zero balance, and the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["payment.status"]}," value is then ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["paid"]},"."]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"declined-stored-payment-methods","__idx":10},"children":["Declined Stored Payment Methods"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["A checkout request can name a stored payment method through the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["payment.paymethod_id"]}," value. If the gateway declines it, the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["payment.status"]}," value is ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["failed"]}," and the API does not return an error response. A ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["headless"]}," checkout also returns a new ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["payment_session"]}," value in the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["next_action"]}," value."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Use that value to collect a different payment method:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"js","header":{"controls":{"copy":{}}},"source":"if (payment.status === 'failed' && action?.type === 'payment_session') {\n  return renderPaymentElement(action.session);\n}\n","lang":"js"},"children":[]},{"$$mdtype":"Tag","name":"Admonition","attributes":{"type":"info","name":"Info"},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["For more information, see ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/rest-api/stored-payment-methods"},"children":["Stored Payment Methods"]},"."]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["A ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["422"]}," response indicates that the gateway does not support headless payment sessions, cannot open a session, or is misconfigured."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"order-before-payment","__idx":11},"children":["Order Before Payment"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Checkout places the order and generates its invoice first. It then screens for fraud and resolves a payment strategy. The payment strategy creates the session against the existing invoice."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["A checkout can reach a session and then fail, returning a ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["failed"]}," status. Retrying the same cart returns the same order and ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["invoice_id"]}," value with one of the following statuses:"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["paid"]}," — The payment cleared."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["pending"]}," — A payment is in progress."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["action_required"]}," — WHMCS created a new session."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["A ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["422"]}," response — WHMCS could not create a new session."]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"4-load-stripejs","__idx":12},"children":["4. Load Stripe.js."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Load the script that the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["session.sdk.scriptUrl"]}," value specifies into your page. WHMCS cannot create a session without a ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["scriptUrl"]}," value."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Then, construct a Stripe instance from the session's publishable key and API version:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"js","header":{"controls":{"copy":{}}},"source":"async function loadSdk(scriptUrl) {\n  if (window.Stripe) return;\n  await new Promise((resolve, reject) => {\n    const el = document.createElement('script');\n    el.src = scriptUrl;\n    el.onload = resolve;\n    el.onerror = () => reject(new Error(`Could not load ${scriptUrl}`));\n    document.head.appendChild(el);\n  });\n}\n\nawait loadSdk(session.sdk.scriptUrl);\nconst stripe = Stripe(session.params.publishableKey, {\n  apiVersion: session.sdk.apiVersion,\n});\n","lang":"js"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["sdk.apiVersion"]}," value is the gateway API version that WHMCS pinned. Use this version to ensure that the browser transacts on the correct version for the session."]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Do not pin your own version. The payment gateway module supplies this value, and it can differ between installations and module versions."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Stripe also ships dated Stripe.js builds such as ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["acacia"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["basil"]},", and ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["clover"]},". Each dated build pins its own Stripe API version and does not accept an override."]}]},{"$$mdtype":"Tag","name":"Admonition","attributes":{"type":"info","name":"Info"},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["For more information, see ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/rest-api/stored-payment-methods"},"children":["Stored Payment Methods"]}," and ",{"$$mdtype":"Tag","name":"a","attributes":{"href":"https://docs.stripe.com/sdks/stripejs-versioning","target":"_blank"},"children":["Stripe.js versioning"]},"."]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"5-mount-the-payment-element","__idx":13},"children":["5. Mount the Payment Element."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Pass the session's ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["clientSecret"]}," value to the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["elements"]}," object. Then, mount the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["paymentElement"]}," value into a container in your page:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"js","header":{"controls":{"copy":{}}},"source":"const elements = stripe.elements({ clientSecret: session.params.clientSecret });\nconst paymentElement = elements.create('payment');\npaymentElement.mount('#payment-element');\n","lang":"js"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["A ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["session.params.mode"]}," value of ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["payment"]}," indicates that the session has a ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["PaymentIntent"]}," object."]},{"$$mdtype":"Tag","name":"Admonition","attributes":{"type":"warning","name":"Warning"},"children":[{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Do not pass the value to the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["stripe.elements()"]}," method. It applies to Stripe's deferred-intent pattern. The headless payment flow does not use that pattern because WHMCS creates the intent."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["If the client abandons the payment after WHMCS creates the intent, the invoice remains in the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Unpaid"]}," status."]}]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"6-confirm-the-payment-with-the-payment-gateway","__idx":14},"children":["6. Confirm the payment with the payment gateway."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Call the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["stripe.confirmPayment()"]}," method with the elements group and a ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["return_url"]}," value, so that Stripe can collect the payment and run any authentication challenges:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"js","header":{"controls":{"copy":{}}},"source":"const { error, paymentIntent } = await stripe.confirmPayment({\n  elements,\n  redirect: 'if_required',\n  confirmParams: {\n    return_url: `${window.location.origin}/checkout/return?invoice=${invoice_id}`,\n  },\n});\n","lang":"js"},"children":[]},{"$$mdtype":"Tag","name":"Admonition","attributes":{"type":"warning","name":"Warning"},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Do not pass the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["clientSecret"]}," value to the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["elements"]}," object again in this step. Passing it again switches Stripe to its deferred-intent flow."]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"redirect-behavior","__idx":15},"children":["Redirect Behavior"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["A 3D Secure (3DS) or Strong Customer Authentication (SCA) challenge on a card opens as a modal inside Stripe's ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["iframe"]}," element, and the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["stripe.confirmPayment()"]}," method resolves in the same page load. The ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["redirect: 'if_required'"]}," value prevents a redirect."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The session source determines the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["allow_redirects"]}," value, which in turn determines which methods the element offers:"]},{"$$mdtype":"Tag","name":"div","attributes":{"className":"md-table-wrapper"},"children":[{"$$mdtype":"Tag","name":"table","attributes":{"className":"md"},"children":[{"$$mdtype":"Tag","name":"thead","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Session Source"},"children":["Session Source"]},{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Allows Redirects"},"children":["Allows Redirects"]},{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Methods Resolved on a Test Account"},"children":["Methods Resolved on a Test Account"]}]}]},{"$$mdtype":"Tag","name":"tbody","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/rest-api/reference/whmcs-rest-api/paths/~1cart~1%7Bcart_id%7D~1checkout/post"},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["POST /cart/{cart_id}/checkout"]}]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["never"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Card, Link, US bank account"]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/rest-api/reference/whmcs-rest-api/paths/~1user~1invoices~1%7Binvoice_id%7D~1payment-session/post"},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["POST /user/invoices/{invoice_id}/payment-session"]}]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["always"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Card, Link, US bank account, Klarna, Cash App Pay, Amazon® Pay"]}]}]}]}]},{"$$mdtype":"Tag","name":"Admonition","attributes":{"type":"success","name":"Note"},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The methods an account offers depend on what the merchant has enabled and on the invoice currency."]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Pass a ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["return_url"]}," value for both session sources. If the client selects a redirect method, they leave for the provider's page and return to that URL. Stripe appends the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["payment_intent"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["payment_intent_client_secret"]},", and ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["redirect_status"]}," values to the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["return_url"]}," value."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"finishing-after-a-redirect","__idx":16},"children":["Finishing After a Redirect"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["A redirect returns as a new page load, and the session object is no longer available. The return URL contains the invoice ID that you set and the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["PaymentIntent"]}," ID that Stripe appended."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Read both values from the query string, then confirm and poll:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"js","header":{"controls":{"copy":{}}},"source":"// On the return page.\nconst q = new URLSearchParams(window.location.search);\nconst invoiceId = q.get('invoice');\nconst paymentIntentId = q.get('payment_intent');\n\nawait confirmPaymentSession(invoiceId, paymentIntentId);\nawait pollUntilPaid(invoiceId);\n","lang":"js"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["A successful return indicates that the client completed the payment in the browser. The invoice remains in the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Unpaid"]}," status until your confirmation call or the webhook records it."]},{"$$mdtype":"Tag","name":"Admonition","attributes":{"type":"warning","name":"Warning"},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Stripe does not sign the redirect, so the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["payment_intent"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["payment_intent_client_secret"]},", and ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["redirect_status"]}," values are query parameters that anyone can set."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The confirmation endpoint takes a ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["provider_reference"]}," value. WHMCS then reads the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["PaymentIntent"]}," object from Stripe with the merchant's secret key. WHMCS rejects a ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["PaymentIntent"]}," object whose metadata does not name this invoice and this client. WHMCS records the status that Stripe reports, so a forged return cannot change the recorded outcome."]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"outcome-handling","__idx":17},"children":["Outcome Handling"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Handle the payment outcome using the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["error"]}," value that the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["stripe.confirmPayment()"]}," method returns:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"js","header":{"controls":{"copy":{}}},"source":"if (error) {\n  if (error.type === 'card_error' || error.type === 'validation_error') {\n    return showRetryable(error.message);\n  }\n  return showFatal(error.message);\n}\n","lang":"js"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["After a declined payment in the browser, the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["PaymentIntent"]}," object returns to a ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["requires_payment_method"]}," status. The same ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["clientSecret"]}," value stays valid and the element can attempt another confirmation. Do not create a new session to retry payment: a new session for the same invoice will result in duplicate payment intents."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["If a failure occurs after the client approves payment, the approval remains valid."]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":["For Stripe, the webhook records the payment if Stripe can reach the installation."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["For PayPal, nothing captures the payment."]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"7-record-the-payment-in-whmcs","__idx":18},"children":["7. Record the payment in WHMCS."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Use the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["session.settlement"]}," value to decide whether to call the confirmation endpoint. This value describes how a gateway integrates."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Then, poll the invoice until it reaches the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Paid"]}," status:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"js","header":{"controls":{"copy":{}}},"source":"if (session.settlement === 'async_webhook') {\n  await pollUntilPaid(invoice_id);\n} else {\n  await confirmPaymentSession(invoice_id, session.params.paymentIntentId);\n  await pollUntilPaid(invoice_id);\n}\n","lang":"js"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["All payment gateways that support headless payments return a ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["sync_confirm"]}," value. When a payment gateway returns this value, WHMCS only records the payment after you confirm it."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Without that confirmation:"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Stripe"]}," — The invoice remains ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Unpaid"]}," until the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["charge.succeeded"]}," webhook arrives, and that webhook requires Stripe to reach the merchant's installation."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["PayPal"]}," — The approval expires uncaptured, and no webhook recovers it."]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Call the confirmation endpoint with the gateway's reference for the payment."]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":["For Stripe, use the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["params.paymentIntentId"]}," value."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["For PayPal, use the order ID from the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["onApprove"]}," callback."]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["For example:"]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":5,"id":"using-javascript-2","__idx":19},"children":["Using JavaScript"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"js","header":{"controls":{"copy":{}}},"source":"async function confirmPaymentSession(invoiceId, providerReference) {\n  const { data } = await api(`/user/invoices/${invoiceId}/payment-session/confirm`, {\n    method: 'POST',\n    body: JSON.stringify({ provider_reference: providerReference }),\n  });\n  return data.payment;    // { status: 'paid' | 'pending' | 'declined' | 'failed', message? }\n}\n","lang":"js"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":5,"id":"using-curl-2","__idx":20},"children":["Using cURL"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"header":{"controls":{"copy":{}}},"source":"curl -X POST --location \"https://example.com/api/v2/user/invoices/1234/payment-session/confirm\"\n  -H \"Content-Type: application/json\"\n  -H \"X-Api-State: <your latest state token>\"\n  -d \"{\\\"provider_reference\\\":\\\"pi_3MtwBwLkdIwHu7ix28a3tqPa\\\"}\"\n"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["WHMCS returns a ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["200"]}," response whenever the gateway responds."]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":["If you receive a ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["paid"]}," status, the payment succeeded."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["If you receive a ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["pending"]}," status, the payment is in progress. Keep polling the invoice."]}]},{"$$mdtype":"Tag","name":"Admonition","attributes":{"type":"info","name":"Info"},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["For more information about this endpoint's ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["status"]}," values and error responses, see the ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/rest-api/reference/whmcs-rest-api"},"children":["API Reference"]}," and ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/rest-api/http-status-codes"},"children":["HTTP Status Codes"]},"."]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"invoice-polling","__idx":21},"children":["Invoice Polling"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Poll the invoice until it reaches the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Paid"]}," status or the timeout expires:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"js","header":{"controls":{"copy":{}}},"source":"async function pollUntilPaid(invoiceId, { timeoutMs = 120000 } = {}) {\n  const deadline = Date.now() + timeoutMs;\n  let delay = 1000;\n  while (Date.now() < deadline) {\n    const { data: invoice } = await api(`/user/invoices/${invoiceId}`);\n    if (invoice.status === 'Paid') return invoice;\n    if (['Cancelled', 'Refunded', 'Draft'].includes(invoice.status)) {\n      throw new Error(`Invoice ${invoiceId} is ${invoice.status}`);\n    }\n    await new Promise(r => setTimeout(r, delay));\n    delay = Math.min(delay * 1.5, 8000);\n  }\n  throw new Error(`Timed out waiting for invoice ${invoiceId}`);\n}\n","lang":"js"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["WHMCS does not rate limit ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/rest-api/reference/whmcs-rest-api/paths/~1user~1invoices~1%7Binvoice_id%7D/get"},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["GET /user/invoices/{invoice_id}"]}]},", so you can poll as often as you need."]},{"$$mdtype":"Tag","name":"Admonition","attributes":{"type":"info","name":"Info"},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["For more information, see ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/rest-api/rate-limits"},"children":["Rate Limits"]},"."]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"paying-an-existing-invoice","__idx":22},"children":["Paying an Existing Invoice"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["If the customer is paying an existing invoice (usually, a renewal), you do not need a shopping cart. Instead, you can create a session against the invoice directly."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["For example:"]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":5,"id":"using-javascript-3","__idx":23},"children":["Using JavaScript"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"js","header":{"controls":{"copy":{}}},"source":"// No body: pay with the gateway already on the invoice.\nconst { data } = await api(`/user/invoices/${invoiceId}/payment-session`, { method: 'POST' });\n\n// Or name a gateway to pay with instead. WHMCS records it on the invoice.\nconst { data } = await api(`/user/invoices/${invoiceId}/payment-session`, {\n  method: 'POST',\n  body: JSON.stringify({ gateway: 'stripe_dynamic' }),\n});\n\nconst session = data.payment_session;   // Same structure as next_action.session above.\n","lang":"js"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":5,"id":"using-curl-3","__idx":24},"children":["Using cURL"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"header":{"controls":{"copy":{}}},"source":"curl -X POST --location \"https://example.com/api/v2/user/invoices/1234/payment-session\"\n  -H \"Content-Type: application/json\"\n  -H \"X-Api-State: <your latest state token>\"\n  -d \"{\\\"gateway\\\":\\\"stripe_dynamic\\\"}\"\n"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Creating an invoice session does not place an order, change an invoice status, or record a transaction."]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":["You can abandon an invoice session and create a new session for the same invoice with no adverse effects."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["The invoice must be in the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Unpaid"]}," or ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Collections"]}," status, or the API returns a ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["422"]}," response."]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"hand-off-links","__idx":25},"children":["Hand-Off Links"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["payment_session"]}," responses do not contain a ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["url"]}," value. A hand-off link is a separate single-use credential that signs its bearer into the Client Area. Every call issues a new link."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["For example:"]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":5,"id":"using-javascript-4","__idx":26},"children":["Using JavaScript"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"js","header":{"controls":{"copy":{}}},"source":"const { data } = await api(`/user/invoices/${invoiceId}/payment-link`, { method: 'POST' });\nconst url = data.next_action.url;   // Single-use and short-lived.\n","lang":"js"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":5,"id":"using-curl-4","__idx":27},"children":["Using cURL"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"header":{"controls":{"copy":{}}},"source":"curl -X POST --location \"https://example.com/api/v2/user/invoices/1234/payment-link\"\n  -H \"X-Api-State: <your latest state token>\"\n"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The invoice must be in the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Unpaid"]}," or ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Collections"]}," status, or the API returns a ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["422"]}," response."]},{"$$mdtype":"Tag","name":"Admonition","attributes":{"type":"info","name":"Info"},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["WHMCS rate limits this endpoint. For more information, see ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/rest-api/rate-limits"},"children":["Rate Limits"]},"."]}]}]},"headings":[{"value":"Headless Stripe Payments","id":"headless-stripe-payments","depth":2},{"value":"Payment Sessions","id":"payment-sessions","depth":2},{"value":"The Payment Flow","id":"the-payment-flow","depth":2},{"value":"1. Authenticate and store the state token.","id":"1-authenticate-and-store-the-state-token","depth":2},{"value":"2. Find headless-capable gateways.","id":"2-find-headless-capable-gateways","depth":2},{"value":"Using JavaScript","id":"using-javascript","depth":5},{"value":"Using cURL","id":"using-curl","depth":5},{"value":"3. Check out in headless mode.","id":"3-check-out-in-headless-mode","depth":2},{"value":"Using JavaScript","id":"using-javascript-1","depth":5},{"value":"Using cURL","id":"using-curl-1","depth":5},{"value":"Declined Stored Payment Methods","id":"declined-stored-payment-methods","depth":3},{"value":"Order Before Payment","id":"order-before-payment","depth":3},{"value":"4. Load Stripe.js.","id":"4-load-stripejs","depth":2},{"value":"5. Mount the Payment Element.","id":"5-mount-the-payment-element","depth":2},{"value":"6. Confirm the payment with the payment gateway.","id":"6-confirm-the-payment-with-the-payment-gateway","depth":2},{"value":"Redirect Behavior","id":"redirect-behavior","depth":3},{"value":"Finishing After a Redirect","id":"finishing-after-a-redirect","depth":3},{"value":"Outcome Handling","id":"outcome-handling","depth":3},{"value":"7. Record the payment in WHMCS.","id":"7-record-the-payment-in-whmcs","depth":2},{"value":"Using JavaScript","id":"using-javascript-2","depth":5},{"value":"Using cURL","id":"using-curl-2","depth":5},{"value":"Invoice Polling","id":"invoice-polling","depth":3},{"value":"Paying an Existing Invoice","id":"paying-an-existing-invoice","depth":2},{"value":"Using JavaScript","id":"using-javascript-3","depth":5},{"value":"Using cURL","id":"using-curl-3","depth":5},{"value":"Hand-Off Links","id":"hand-off-links","depth":2},{"value":"Using JavaScript","id":"using-javascript-4","depth":5},{"value":"Using cURL","id":"using-curl-4","depth":5}],"frontmatter":{"title":"Headless Stripe Payments","seo":{"title":"Headless Stripe Payments"}},"lastModified":"2026-10-19T18:21:39.000Z","pagePropGetterError":{"message":"","name":""}},"slug":"/rest-api/headless-stripe-payments","userData":{"isAuthenticated":false,"teams":["anonymous"]},"isPublic":true}