# Integrate: Hosted Checkout

An interactive bench for hosted-checkout integrations. You create a payment with `confirm: false` and `payment_link: true`; Peach hosts the payment page. You redirect the shopper there, they pay, and they return to your site. There is no checkout for you to build and no payment method data in your request.

## What this page covers
- A generated `POST /payments` body with `confirm: false`, `payment_link: true` and `session_expiry`
- One checkout for every payment method, the same as the embedded checkout: there is no method rail here. By default the request sends no `allowed_payment_method_types`, so the page offers everything the account has enabled for the currency
- A checkbox picker for `allowed_payment_method_types` when you want to narrow that, with the currency and amount beside it
- A form over `payment_link_config` that writes straight into the request body
- The checkout URL (`payment_link.link`) with copy and open actions, the `return_url` it comes back to, and the redirect that sends the shopper there
- The shopper paying in a live preview of Peach's page, then landing on your `return_url`
- Final-state confirmation through the webhook feed and `GET /payments/{id}?force_sync=true`
- Refund steps, whose rules follow whichever method the shopper chose

## Customisation
`payment_link_config` controls the hosted page:

| Option | Effect |
|---|---|
| `theme` | Accent colour |
| `logo` | Logo shown on the page |
| `seller_name` | Merchant name displayed to the shopper |
| `branding_visibility` | Whether Peach branding shows |
| `sdk_layout` | `accordion`, `tabs` or `spaced_accordion` |
| `details_layout` | `layout1` or `layout2` |
| `display_sdk_only` | Hides the order summary, leaving just the payment form |
| `show_card_form_by_default` | Opens the card form on load |
| `enabled_saved_payment_method` | Offers the customer's saved cards |
| `hide_card_nickname_field` | Hides the nickname input on the card form |
| `payment_button_text` | Label on the pay button |
| `custom_message_for_card_terms` | Message under the card form |
| `transaction_details` | Key/value rows shown to the shopper |

## Recurring: storing a credential here, charging it later

The flows above are all **CIT** — customer-initiated, with the shopper present. Switch the panel to **Merchant (MIT)** and the sequence changes: the payment that stores the credential comes first, then an off-session charge with nobody there.

The setup payment is the same hosted checkout with `setup_future_usage: "off_session"`, a `customer_id` and `mandate_data` added. The shopper agrees to the mandate on Peach's page, so you never handle the credential or the acceptance yourself:

```json
"payment_link": true,
"customer_id": "cust_123",
"setup_future_usage": "off_session",
"authentication_type": "three_ds",
"mandate_data": {
  "customer_acceptance": {
    "acceptance_type": "online",
    "online": { "ip_address": "127.0.0.1", "user_agent": "Mozilla/5.0" }
  },
  "mandate_type": { "multi_use": { "amount": 100000, "currency": "ZAR" } }
}
```

The off-session charge is identical in every integration style — how the shopper paid the first time changes nothing about charging them later:

```json
"amount": 6500,
"currency": "ZAR",
"confirm": true,
"off_session": true,
"customer_id": "cust_123",
"recurring_details": { "type": "payment_method_id", "data": "pm_..." }
```

`payment_method_id` is **not** a field on `POST /payments` — the stored credential is referenced through `recurring_details`. Only card-backed credentials can be stored this way, so the toggle is offered for cards and network tokens and not for the alternative methods.

## Key concepts
- The response carries the checkout URL as `payment_link.link`.
- `session_expiry` is in seconds and expires the client secret, which is what makes the hosted page stop working.
- `payment_link_config` is read when the checkout is created and baked into the page. A live checkout cannot be restyled after the fact — editing the appearance while one is live prompts you to create it again, which is a new payment and a new URL.
- Peach's page collects the payment method, so your request sends no `payment_method_data`. Omit `allowed_payment_method_types` and the page offers every method the account has enabled for the currency; send it to restrict what the shopper can choose.
- After they pay, Peach sends the shopper back to your `return_url`.
- Amounts are in minor units. Alternative payment methods are automatic capture only.
- Peach rate-limits status queries to 2 per minute per transaction; automatic polling here is 30 seconds apart.

## Related
- [Integrate: Server-to-Server](/integrate/api-only.md): send the payment method data yourself
- [Integrate: Embedded Checkout](/integrate/sdk.md): one embedded checkout in your own app
- [Payment links](/flows/payment-links.md): the guided walkthrough and the full config reference
- [API reference](/playground.md): every endpoint, with the OpenAPI reference beside it

---

Interactive version: https://playground.sandbox-next.peachpayments.com/integrate/hosted-checkout
