Skip to content

Add the payment method

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

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

Records the payment method the customer entered (the Stripe Payment Element's pm_…, or a fake test card) and reads its card's country from the processor. The card country joins the location evidence in a new evidence set and the sale is quoted again (spec §13.3). If the total changes, the page shows the new total, the customer confirms it, and the payment intent is updated before the charge (checkout.hosted.confirm refuses an outdated total with checkout_total_changed).

The brand's risk rules run again with the card's fingerprint and country (spec §20): a refused card is risk_payment_declined, and the customer can try another.

Public: the hosted page calls it with the session key.

At a glance

  • REST: POST /api/v1/public/checkout_sessions/{sessionId}/payment_method
  • MCP tool: checkout_hosted_attach_payment_method
  • Operation ID: checkout.hosted.attach_payment_method
  • Scope: Brand
  • Permission: checkout:write
  • Risk tier: write
  • 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.
paymentMethodstringYesThe payment method the customer entered (Stripe pm_…; the fake processor's pm_fake_…).

Response

200 OK with a JSON object:

FieldTypeDescription
idcs_… ID
object"checkout.hosted_session"
livemodeboolean
statusone of open, processing, complete, expired, blocked
modeone of payment, subscription, topup, setup
currencystring
brandobject
brand.slugstring
brand.namestring
brand.primaryColorstring or null
brand.termsUrlstring or null
brand.privacyUrlstring or null
brand.supportEmailstring or null
sellerobjectThe merchant disclosure: “Sold by {legal name}, {address}, {VAT ID}” (spec §29.1).
seller.legalNamestring
seller.addressstring
seller.taxIdstring
lineItemsarray of object
lineItems[].idstring
lineItems[].descriptionstring
lineItems[].quantityinteger
lineItems[].amountmoney
lineItems[].recurringobject or null
lineItems[].adjustableQuantityobject or null
lineItems[].discountmoney or null
subscriptionobject or nullSubscription sessions: the trial, what is due today and the promotion code.
topupobject or nullTop-up sessions (spec §14.3): what the customer's wallet receives.
setupobject or nullSetup sessions: whether saving the card also pays the subscription's first invoice.
collectBusinessDetailsone of auto, required, never
prefillobject
prefill.emailstring or null
prefill.namestring or null
prefill.countrystring or null
prefill.subdivisionstring or null
prefill.businessobject or null
quoteobject or null
blockedobject or null
waitlistobject or null
paymentobject
payment.startedboolean
payment.intentone of payment, setup or nullpayment: a charge now; setup: the card is saved without a charge.
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).
payment.publishableKeystring or null
payment.paymentMethodobject or null
payment.upToDatebooleanThe prepared payment is for the current quote's total.
legalobject
legal.termsAcceptedboolean
legal.immediatePerformanceRequiredboolean
legal.immediatePerformanceTextstring
successUrlstring or nullSet once the session is complete: successUrl?cs_id=….
cancelUrlstring or null
expiresAtdate-time
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.
locale.preferredarray of stringThe session's language, then its customer's, where set (BCP 47): the page's language before the visitor's.
locale.brandarray of stringThe brand's checkout languages; the first is its default.

Examples

A card from Germany

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

{
  "key": "Zk3tq8Vb2xQ9mN4rP7sL1yH6cJ0wE5aD-uT_fG8hK2o",
  "paymentMethod": "pm_fake_card_de"
}

Response (200):

JSON
{
  "id": "cs_01m4tx5nj8emwbyf7y9vcf48aa",
  "object": "checkout.hosted_session",
  "livemode": false,
  "status": "open",
  "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"]
  }
}

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.

It can also return these common errors: validation_failed, 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.