> ## Documentation Index
> Fetch the complete documentation index at: https://docs.stablemint.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Payment sessions

> Create a hosted checkout, redirect your customer, and act on the webhook.

A **payment session** is how you collect money from one of your customers. Every incoming payment
belongs to exactly one session: you create it with `POST /v1/checkout/hosted`, redirect your
customer to the `url` it returns, and StableMint tells you by webhook what happened.

<Warning>
  **Accepting payments is enabled per partner, and is off by default.** It is not provisioned
  automatically with your account, so this flow does not work out of the box — until it is switched
  on for you, `POST /v1/checkout/hosted` answers `403` with `code: feature_not_enabled`, however
  correct your credentials are. Request it from your StableMint contact before building against
  this page.
</Warning>

<Note>
  Every example on this page is a signed request. `$SIGNATURE` is computed per request over a
  canonical string — see [Authentication](/guides/authentication). Bearer tokens are not accepted.
</Note>

## How a session runs

<Steps>
  <Step title="You create the session">
    Send the amount, the currency, who the customer is, and the four URLs you want them returned to.
    You get back a `url`, a `reference` and an `expiresAt`.
  </Step>

  <Step title="Your customer pays">
    Redirect them to `url`. The checkout is hosted by StableMint — it handles the payment method,
    the bank redirect and the result screens, so none of that lives in your application.
  </Step>

  <Step title="They come back to you">
    The customer lands on whichever of your URLs matches the outcome: success, exit, or error.
  </Step>

  <Step title="You act on the webhook">
    **The redirect is not the source of truth.** Credit the customer when the deposit event arrives —
    see [Event types](/guides/event-types).
  </Step>
</Steps>

<Warning>
  **Never treat a redirect to your `successUrl` as proof of payment.** A browser redirect is under
  the customer's control and says only that they reached the end of the flow. The money is confirmed
  by `partner.customer.deposit.completed`, and that is the event to act on.
</Warning>

## Create a session

```bash theme={null}
curl -X POST https://api.stablemint.net/v1/checkout/hosted \
  -H "ApiKey: $STABLEMINT_API_KEY" \
  -H "Timestamp: $TIMESTAMP" \
  -H "Nonce: $NONCE" \
  -H "Signature: $SIGNATURE" \
  -H "Content-Type: application/json" \
  -d '{
    "userId": "your-customer-8412",
    "userEmail": "customer@example.com",
    "userCountryCode": "FI",
    "currency": "EUR",
    "amount": 100.00,
    "successUrl": "https://yourapp.example/pay/success",
    "exitUrl": "https://yourapp.example/pay/cancelled",
    "errorUrl": "https://yourapp.example/pay/error",
    "supportUrl": "https://yourapp.example/support",
    "idempotencyKey": "order-8412-attempt-1",
    "websiteReference": "order-8412"
  }'
```

### Required fields

<ParamField body="userId" type="string" required>
  Your own identifier for the customer paying. Reuse it across sessions to keep one identity — see
  [You identify the customer](#you-identify-the-customer).
</ParamField>

<ParamField body="userEmail" type="string" required>
  The customer's email address.
</ParamField>

<ParamField body="userCountryCode" type="string" required>
  ISO 3166-1 alpha-2, e.g. `FI`.
</ParamField>

<ParamField body="currency" type="string" required>
  The currency to collect, e.g. `EUR`.
</ParamField>

<ParamField body="amount" type="number" required>
  The amount to collect.
</ParamField>

<ParamField body="successUrl" type="string" required>
  Where the customer lands after paying. Proof of payment is the webhook, not this redirect.
</ParamField>

<ParamField body="exitUrl" type="string" required>
  Where the customer lands if they abandon the flow.
</ParamField>

<ParamField body="errorUrl" type="string" required>
  Where the customer lands if the payment fails.
</ParamField>

<ParamField body="supportUrl" type="string" required>
  Your support page, linked from the checkout.
</ParamField>

### Optional fields

<ParamField body="idempotencyKey" type="string">
  Retrying with the same key returns the original session instead of creating a second one — see
  [Idempotency](#idempotency).
</ParamField>

<ParamField body="websiteReference" type="string">
  Your own reference. Echoed back on the webhook payloads for this payment.
</ParamField>

<ParamField body="customFields" type="object">
  Arbitrary JSON carried through to the webhook payloads untouched.
</ParamField>

<ParamField body="widgetConfigurationSlug" type="string">
  Selects which saved checkout configuration to apply — theming, labels and behaviour. Set these up
  in the dashboard. Omit it to use your default.
</ParamField>

<ParamField body="userKyc" type="object">
  Optional KYC details: `fullName`, `citizenshipCountryCode`, `residenceAddress`, `dateOfBirth`
  (`YYYY-MM-DD`), `placeOfBirth`, `gender`, `mobilePhone`, `personalIdentificationNumber`. Every
  subfield is optional.
</ParamField>

## Response

```json theme={null}
{
  "url": "https://checkout.stablemint.io/s/550e8400-e29b-41d4-a716-446655440000",
  "expiresAt": 1790123456,
  "reference": "ref_session_xyz789"
}
```

| Field       | Meaning                                                                        |
| ----------- | ------------------------------------------------------------------------------ |
| `url`       | The hosted checkout. Redirect your customer here.                              |
| `expiresAt` | Unix timestamp in seconds. After this the URL no longer starts a payment.      |
| `reference` | The session's reference. Store it — it ties the webhooks back to this payment. |

<Tip>
  Store `reference` against your order before you redirect. It is how you match the deposit events
  to the thing the customer was buying.
</Tip>

## What happens next

The customer pays, returns to one of your URLs, and the deposit events arrive:
`partner.customer.deposit.initiated`, then `accepted`, then `completed` — or `expired` if the
session is never funded. Credit your customer on `completed`.

See [Event types](/guides/event-types) for the payloads and
[Receiving webhooks](/guides/webhooks) for registering an endpoint and verifying signatures.

## You identify the customer

You own the customer relationship; StableMint never contacts them. That means every session carries
your own identification of who is paying — `userId`, `userEmail` and `userCountryCode` — and the
payment is attributed to that customer.

Pass the same `userId` each time and the customer's payments accumulate under one identity, which is
what makes their transaction history and balance meaningful. See [Customers](/guides/customers).

You may optionally send KYC details with the session. Whether you need to depends on your
configuration — if you are unsure, you do not need to send them.

## Sessions expire

`expiresAt` is a Unix timestamp in seconds. After it passes, the `url` no longer starts a payment
and the customer must be sent through a fresh session.

A session that is never funded ends as `partner.customer.deposit.expired`. That event is terminal —
it is your signal to release anything you were holding against the pending payment.

## Idempotency

Send an `idempotencyKey` and a retried create returns the original session instead of opening a
second one. Generate it from something stable in your own system — an order id — rather than a
random value that changes on retry.
