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

# Payment method sessions

> The short-lived context that lets a browser collect a card or a bank account without your secret key.

Card and bank account details never pass through your servers, or Payra's, in the clear: Payra Elements collects
them in secure fields, they are tokenized before they reach Payra, and Payra keeps only the tokens plus what you
may see (a card's brand, last four digits and expiration date; a bank account's last four digits, account type and
holder type; the holder's name and billing postal code), a card's first digits (BIN), which Payra uses internally,
and, for a bank account, the record of the holder's authorization. A
**payment method session** is what authorizes one such collection, of one instrument: `payment_method_types` is
`["card"]` (the default) or `["us_bank_account"]`. Your server creates it with a secret key; your page opens it
with your publishable key and the session's `client_secret`.

<Steps>
  <Step title="Create the session on your server">
    Optionally bind it to one of your customers, so the collected method belongs to them.

    ```bash theme={null}
    curl -X POST https://api-dashboard.payra.com/v1/payment-method-sessions \
      -H "Authorization: Bearer sk_test_..." \
      -H "Idempotency-Key: 9c1e2b6a-3b0d-4a8e-9a57-1f2b3c4d5e6f" \
      -H "Content-Type: application/json" \
      -d '{ "customer": "019e..." }'
    ```

    ```json Response theme={null}
    {
      "object": "payment_method_session",
      "id": "pms_test_7Hs2kQ9mL1pXv4cR8tWzAbCd",
      "client_secret": "pms_test_7Hs2kQ9mL1pXv4cR8tWzAbCd_secret_...",
      "status": "requires_payment_method",
      "customer": "019e...",
      "payment_method_types": ["card"],
      "amount": null,
      "currency": null,
      "livemode": false,
      "expires_at": "2026-09-17T18:30:00.000Z",
      "canceled_at": null,
      "succeeded_at": null,
      "card": null,
      "us_bank_account": null,
      "ach_authorization": null,
      "payment_method": null,
      "created_at": "2026-09-17T18:00:00.000Z"
    }
    ```

    `client_secret` is returned only by this call, and by a replay of it under the same `Idempotency-Key` for the
    24 hours a stored answer is kept.

    A **bank account session** names the debit the account holder will authorize. Without a customer it takes
    the `amount` (in cents, USD only) of the one payment the account may be debited for; with a customer it
    saves the account under a standing authorization (`ach_authorization.scope: "standing"`, a text without an
    amount) and takes no amount. `amount` is not used on a card session or on a bank session with a customer,
    but if you send it, it must still be a positive integer and `currency` must be `USD`.

    ```bash theme={null}
    curl -X POST https://api-dashboard.payra.com/v1/payment-method-sessions \
      -H "Authorization: Bearer sk_test_..." \
      -H "Idempotency-Key: 5e1d7c2a-9b4f-4a3e-8c6d-0f1a2b3c4d5e" \
      -H "Content-Type: application/json" \
      -d '{ "payment_method_types": ["us_bank_account"], "amount": 21375, "currency": "USD" }'
    ```

    The answer carries `ach_authorization`: the text Payra Elements shows the account holder, worded for your
    business name and that amount. The page cannot change it.

    ```json theme={null}
    "payment_method_types": ["us_bank_account"],
    "amount": 21375,
    "currency": "USD",
    "ach_authorization": {
      "scope": "single",
      "terms_version": "2026-08-21.v1",
      "short_text": "I authorize Northwind Traders to electronically debit the bank account provided for this one-time payment of $213.75, in accordance with the ACH Authorization.",
      "full_text": "Northwind Traders is authorized to initiate a single electronic debit (ACH) ...",
      "accepted_at": null
    }
    ```
  </Step>

  <Step title="Hand the client secret to your page">
    Send the page the `client_secret` and nothing more: the secret key stays on your server. The page passes it to
    Payra Elements together with your publishable key.

    Under the hood, Elements retrieves the session with the publishable key:

    ```bash theme={null}
    curl "https://api-dashboard.payra.com/v1/payment-method-sessions/pms_test_7Hs2kQ9mL1pXv4cR8tWzAbCd?client_secret=pms_test_..._secret_..." \
      -H "Authorization: Bearer pk_test_..."
    ```

    The answer is the same object, without `client_secret` and with a `collect` block telling Elements where to
    collect.
  </Step>

  <Step title="Collect">
    When the customer submits, [Elements](/elements) sends the secure fields through the secure route, which
    replaces the card number and security code, or the routing and account numbers, with tokens, and
    [confirms the session](/api-reference/payment-method-sessions/confirm-a-payment-method-session). The session
    becomes `succeeded` and carries a `card` block:

    ```json theme={null}
    "status": "succeeded",
    "succeeded_at": "2026-09-17T18:04:12.000Z",
    "card": { "brand": "visa", "last4": "4242", "exp_month": 12, "exp_year": 2030 }
    ```

    or a `us_bank_account` block, together with the moment the account holder accepted the authorization:

    ```json theme={null}
    "status": "succeeded",
    "succeeded_at": "2026-09-23T18:04:12.000Z",
    "us_bank_account": { "last4": "6789", "account_type": "checking", "account_holder_type": "individual" },
    "ach_authorization": { "scope": "single", "terms_version": "2026-08-21.v1", "short_text": "...", "full_text": "...", "accepted_at": "2026-09-23T18:04:12.000Z" }
    ```

    A bank account is confirmed only with that acceptance: Elements sends the version the holder saw, Payra
    stamps the moment, the address and the text itself, and keeps the record for any dispute. Elements refuses to
    send a confirm without it (`ach_authorization_required`, raised in the browser), and a confirm that reaches the
    API without it is `400 parameter_invalid` and stores nothing.

    Your server then turns the session into a [payment method](/payment-methods) it can charge, with the secret
    key. Once it did, the session carries the method's id as `payment_method`.
  </Step>
</Steps>

## Lifecycle

| `status`                  | Meaning                                                                                                                                                                                                                                                                                                                 |
| ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `requires_payment_method` | Collecting. A browser can open the session.                                                                                                                                                                                                                                                                             |
| `succeeded`               | A card or a bank account was collected; `card` or `us_bank_account` says which, as far as you may see it. Once. Your server [creates the payment method](/payment-methods) from it, within 30 minutes of `succeeded_at`. It never reads `expired`, but after those 30 minutes it can no longer become a payment method. |
| `canceled`                | Your server [canceled it](/api-reference/payment-method-sessions/cancel-a-payment-method-session).                                                                                                                                                                                                                      |
| `expired`                 | 30 minutes passed since creation. Never stored: it is read off `expires_at`.                                                                                                                                                                                                                                            |

A browser opening a session that is not `requires_payment_method` gets `400 payment_method_session_inactive`.
Create a new session; they are cheap.

## What a session cannot do

* **Charge or refund.** It authorizes collection only.
* **Carry a card number or a routing number in the clear.** The confirm refuses a card number or a routing
  number that did not come through the secure route (`400 card_not_tokenized`, `400 bank_account_not_tokenized`)
  and stores nothing.
* **Skip the authorization.** A bank account confirmed without the holder's acceptance is
  `400 parameter_invalid` (Elements never sends one: it raises
  `ach_authorization_required` in the browser instead); an acceptance of a text version this server no longer
  defines is `400 ach_terms_version_unsupported` (reload Elements, which fetches the current one).
* **Debit any other amount.** A one-time bank account session needs `amount` (`400 parameter_invalid` without it),
  and the later [charge](/payments) must match it.
* **Act as a key.** A `client_secret` sent as a bearer token answers `401 invalid_api_key`, and a publishable
  key sent without `client_secret` answers `401 client_secret_missing`.
* **Cross a workspace or an environment.** It belongs to the workspace and environment of the key that created it.
  A `client_secret` presented with another workspace's publishable key, with a key of the other environment, or
  under another session's id answers `401 invalid_client_secret`, without saying which.

## Idempotency

Send an [`Idempotency-Key`](/idempotency) when you create a session, so a retried request returns the same session
and the same `client_secret` instead of a second one.
