Skip to content

Confirm the payment

The checkout.hosted.confirm operation: POST /api/v1/public/checkout_sessions/{sessionId}/confirm in the REST API and the checkout_hosted_confirm tool in the MCP server.

POST/api/v1/public/checkout_sessions/{sessionId}/confirm

The customer's confirmation: the brand's terms accepted and, for EU and UK consumers buying digital services delivered at once, the consent to immediate performance and the loss of the withdrawal right (stored with the time, the text version and the IP country). The total the customer saw must still be the quote's (checkout_total_changed otherwise) and the quote must be current (checkout_quote_expired).

The brand's risk rules run a last time (spec §20): a refused payment is risk_payment_declined; when they ask for 3-D Secure, the payment intent requests it. The payment intent is brought up to date, then:

  • fake processor: the payment is confirmed here with the chosen test card: succeeded, processing, requires_action (a fake 3-D Secure challenge: confirm again to pass it) or a decline;
  • Stripe: the answer is client_confirm, and the browser confirms in the Payment Element (3-D Secure included);
  • Mollie (redirect): the answer is redirect with redirectUrl, Mollie's payment page, where the buyer chooses a method the brand's Mollie profile offers (cards, iDEAL, Bancontact, …) and pays; Mollie sends them back to the checkout's success page. The session waits for the webhook (processing); a payment that fails, is cancelled or expires on Mollie's page brings it back to open, and confirming again starts a new payment.

The sale is recorded only when the processor's webhook confirms the payment; poll checkout.hosted.get until the session is complete. Public: the hosted page calls it with the session key.

At a glance

  • REST: POST /api/v1/public/checkout_sessions/{sessionId}/confirm
  • MCP tool: checkout_hosted_confirm
  • Operation ID: checkout.hosted.confirm
  • Scope: Brand
  • Permission: checkout:write
  • Risk tier: financial
  • Dry run: Not supported
  • Idempotency: Idempotency-Key required
  • Modes: Test and live
  • Authentication: None (a public endpoint, rate-limited per IP address)
  • Since: M4

Request

Path parameters

FieldTypeDescription
sessionIdcs_… IDThe checkout session's ID (cs_…).

Body

A JSON object (Content-Type: application/json):

FieldTypeRequiredDescription
keystringNoThe session key from the hosted URL (?key=…). Required without an API key; signed-in callers with checkout:write don't need it.
paymentMethodstringNoThe fake processor's payment method to charge (the Stripe Payment Element confirms in the browser).
expectedTotalobjectYesThe total the customer saw and agreed to pay.
expectedTotal.amountstringYesInteger micros as a string.
expectedTotal.currencystringYes
acceptTermsbooleanYesThe customer accepted the brand's terms.
immediatePerformancebooleanNoEU and UK consumers buying digital services: consent to immediate performance and the loss of the withdrawal right.
localeone of en, de, fr, hr, it, esNoThe language the consents were shown in; they are recorded with it (text version 2026-10-20/de).

Response

200 OK with a JSON object:

FieldTypeDescription
sessionobject
session.idcs_… ID
session.object"checkout.hosted_session"
session.livemodeboolean
session.statusone of open, processing, complete, expired, blocked
session.modeone of payment, subscription, topup, setup
session.currencystring
session.brandobject
session.brand.slugstring
session.brand.namestring
session.brand.primaryColorstring or null
session.brand.termsUrlstring or null
session.brand.privacyUrlstring or null
session.brand.supportEmailstring or null
session.sellerobjectThe merchant disclosure: “Sold by {legal name}, {address}, {VAT ID}” (spec §29.1).
session.seller.legalNamestring
session.seller.addressstring
session.seller.taxIdstring
session.lineItemsarray of object
session.lineItems[].idstring
session.lineItems[].descriptionstring
session.lineItems[].quantityinteger
session.lineItems[].amountmoney
session.lineItems[].recurringobject or null
session.lineItems[].adjustableQuantityobject or null
session.lineItems[].discountmoney or null
session.subscriptionobject or nullSubscription sessions: the trial, what is due today and the promotion code.
session.topupobject or nullTop-up sessions (spec §14.3): what the customer's wallet receives.
session.setupobject or nullSetup sessions: whether saving the card also pays the subscription's first invoice.
session.collectBusinessDetailsone of auto, required, never
session.prefillobject
session.prefill.emailstring or null
session.prefill.namestring or null
session.prefill.countrystring or null
session.prefill.subdivisionstring or null
session.prefill.businessobject or null
session.quoteobject or null
session.blockedobject or null
session.waitlistobject or null
session.paymentobject
session.payment.startedboolean
session.payment.intentone of payment, setup or nullpayment: a charge now; setup: the card is saved without a charge.
session.payment.componentone of fake, stripe_payment_element, redirect or nullHow the payment details are collected: the fake processor's test cards, the Stripe Payment Element, or redirect (the buyer pays on the processor's page, Mollie; confirm answers its address).
session.payment.publishableKeystring or null
session.payment.paymentMethodobject or null
session.payment.upToDatebooleanThe prepared payment is for the current quote's total.
session.legalobject
session.legal.termsAcceptedboolean
session.legal.immediatePerformanceRequiredboolean
session.legal.immediatePerformanceTextstring
session.successUrlstring or nullSet once the session is complete: successUrl?cs_id=….
session.cancelUrlstring or null
session.expiresAtdate-time
session.localeobjectWhat decides the page's language (spec §14.2): an explicit choice on the page, then preferred, then the browser's languages, then brand, then English.
session.locale.preferredarray of stringThe session's language, then its customer's, where set (BCP 47): the page's language before the visitor's.
session.locale.brandarray of stringThe brand's checkout languages; the first is its default.
confirmationone of succeeded, processing, requires_action, client_confirm, redirectsucceeded or processing: wait for the session to complete; requires_action: the bank wants the customer to verify (3-D Secure); client_confirm: confirm in the browser with the Stripe Payment Element; redirect: send the buyer to redirectUrl (the processor's payment page, Mollie), from where they return to the success page.
redirectUrlstring or null

Examples

Pay with a fake test card

HTTP
POST /api/v1/public/checkout_sessions/cs_01m4tx5nj8emwbyf7y9vcf48aa/confirm
Idempotency-Key: 3f1c9a52-7d0e-4b8f-9a61-0c2d5e8b7f14
Content-Type: application/json

{
  "key": "Zk3tq8Vb2xQ9mN4rP7sL1yH6cJ0wE5aD-uT_fG8hK2o",
  "paymentMethod": "pm_fake_card_de",
  "expectedTotal": {
    "amount": "100000000",
    "currency": "EUR"
  },
  "acceptTerms": true,
  "immediatePerformance": true
}

Response (200):

JSON
{
  "session": {
    "id": "cs_01m4tx5nj8emwbyf7y9vcf48aa",
    "object": "checkout.hosted_session",
    "livemode": false,
    "status": "processing",
    "mode": "payment",
    "currency": "EUR",
    "brand": {
      "slug": "genno",
      "name": "Genno",
      "primaryColor": null,
      "termsUrl": "https://genno.example/terms",
      "privacyUrl": "https://genno.example/privacy",
      "supportEmail": null
    },
    "seller": {
      "legalName": "Etna Labs AG",
      "address": "Dammstrasse 16, 6300 Zug, Switzerland",
      "taxId": "CHE-181.927.786 MWST"
    },
    "lineItems": [
      {
        "id": "1",
        "description": "Genno credits",
        "quantity": 1,
        "amount": {
          "amount": "100000000",
          "currency": "EUR"
        },
        "recurring": null,
        "adjustableQuantity": null,
        "discount": null
      }
    ],
    "subscription": null,
    "topup": null,
    "setup": null,
    "collectBusinessDetails": "auto",
    "prefill": {
      "email": "anna@example.com",
      "name": null,
      "country": "DE",
      "subdivision": null,
      "business": null
    },
    "quote": {
      "id": "tq_01m4tx5nj8emwbyf7y9vcf48ab",
      "decision": "charge",
      "customerType": "b2c",
      "country": "DE",
      "lines": [
        {
          "id": "1",
          "description": "Genno credits",
          "quantity": 1,
          "treatment": "charge",
          "net": {
            "amount": "84033613",
            "currency": "EUR"
          },
          "tax": {
            "amount": "15966387",
            "currency": "EUR"
          },
          "gross": {
            "amount": "100000000",
            "currency": "EUR"
          },
          "ratePpm": "190000",
          "jurisdictionId": "DE",
          "jurisdictionName": "Germany",
          "taxName": "VAT",
          "regimeId": "EU_OSS_NON_UNION",
          "pendingRegistration": false,
          "taxBehavior": "inclusive"
        }
      ],
      "totals": {
        "net": {
          "amount": "84033613",
          "currency": "EUR"
        },
        "tax": {
          "amount": "15966387",
          "currency": "EUR"
        },
        "gross": {
          "amount": "100000000",
          "currency": "EUR"
        }
      },
      "inclusive": true,
      "reverseCharge": false,
      "reverseChargeWording": null,
      "validUntil": "2026-10-14T10:30:00.000Z",
      "unverifiedRules": false
    },
    "blocked": null,
    "waitlist": null,
    "payment": {
      "started": true,
      "intent": "payment",
      "component": "fake",
      "publishableKey": null,
      "paymentMethod": {
        "ref": "pm_fake_card_de",
        "type": "card",
        "cardCountry": "DE",
        "brand": "visa",
        "last4": "4242"
      },
      "upToDate": true
    },
    "legal": {
      "termsAccepted": true,
      "immediatePerformanceRequired": true,
      "immediatePerformanceText": "I want the service to start now, and I understand that I lose my right to withdraw once it has started."
    },
    "successUrl": null,
    "cancelUrl": "https://shop.example/cart",
    "expiresAt": "2026-10-15T10:00:00.000Z",
    "locale": {
      "preferred": [],
      "brand": ["en"]
    }
  },
  "confirmation": "succeeded",
  "redirectUrl": null
}

Errors

CodeHTTP statusWhenWhat to do
resource_not_found404No such checkout session for this brand and mode, or (on the hosted page) the session key is missing or wrong.Check the ID and that your credentials are scoped to the right organization, entity, brand and mode.
checkout_session_not_open409The session is complete, expired, or a payment for it is being processed (details.status).The session is complete, expired or waiting for a payment's outcome (details.status). Create a new session for a new purchase.
checkout_details_required409The customer's details aren't in yet.Submit the customer's email and country first, so the tax can be quoted.
checkout_session_blocked409The sale is paused for the customer's location.Offer the waitlist, or let a business customer enter a valid tax ID (reverse charge). details.blocked_reason says why the sale is paused.
checkout_quote_expired409The quote is older than 30 minutes or from another day: submit the details again.Submit the customer's details again to get a fresh quote (quotes are valid for 30 minutes).
processor_request_failed502The processor refused or failed the request (retryable says whether to retry).If retryable is true, retry with the same Idempotency-Key after a short wait; otherwise check details.code.
checkout_session_changed409The session changed while the request ran; send it again.Send the request again.
risk_payment_declined400The brand's risk rules refused the payment (§20; WP-M9-risk). The answer doesn't say which rule.Use another payment method, or contact the seller. The seller's team sees why in the console (Brand → Risk); the answer never says.
checkout_payment_not_started409The payment step hasn't started (no payment intent yet).Start the payment step first, so the payment can be prepared for the quoted total.
checkout_terms_required400The terms, or the immediate-performance consent, weren't accepted (details.missing).Ask the customer to accept the brand's terms and, for digital services delivered at once to EU or UK consumers, to agree to immediate performance and the loss of the withdrawal right (details.missing).
checkout_total_changed409The total changed since the customer saw it (details.total): show it and confirm again.Show the customer the new total (details.total) and ask them to confirm it; then confirm again with that total.
processor_payment_declined502The card was declined (details.decline_code); the customer can try another method.Ask the customer for another payment method; details.decline_code says why the card was declined.
validation_failed400The fake processor's payment method wasn't chosen first.Fix the fields listed in details.issues and send the request again.

It can also return these common errors: api_version_unsupported, invalid_json, unsupported_media_type, scope_required, idempotency_key_required, idempotency_key_reused, idempotency_in_progress, dry_run_not_supported, rate_limited, internal_error.