Create a Session

The request that creates a Hosted Checkout session, field by field, and what the response gives you.

Call this endpoint from your backend each time a customer is ready to pay. It creates one checkout session — for one payment or one card enrollment — and returns the url to send the customer to.

POST https://api-stage.tonder.io/checkout/v1/sessions   # Sandbox
POST https://api.tonder.io/checkout/v1/sessions          # Production

How it works

  1. Your server calls this endpoint with the customer, the amount and the items.
  2. Tonder creates the session and, for payment sessions, the payment. You receive the checkout url and the payment_id.
  3. You redirect the customer to the url, or embed it.
  4. The customer pays on Tonder's page. They can retry in the same session.
  5. The customer returns to your site, and you confirm the result with webhooks or by reading the session.

A session is single-use and valid for at most 24 hours. Its amount, currency and line items cannot be changed — create a new session instead.

Headers

HeaderValueRequiredDescription
AuthorizationToken <your_api_key>YesYour API key, from Developers in the Dashboard. Call the API only from your backend.
Content-Typeapplication/jsonYesThe body is JSON.
x-idempotency-keyAny unique stringNoProtects against creating the same session twice when you retry a request within 5 seconds. See Idempotency.

Body

FieldTypeRequiredValuesDescription
session_typestringNopayment, enrollmentDefaults to payment. enrollment saves a card without charging — see Save a card without charging.
customerobjectYesSee CustomerThe customer who pays. Saved cards belong to this customer.
amount_totalnumberPayment onlyGreater than 0Total to charge, in decimal format (150.00). Not checked against the sum of line_items.
currencystringPayment onlye.g. MXNYour business currency.
line_itemsarrayPayment onlySee Line itemsWhat the customer is paying for.
payment_method_typesarrayNoSee Payment methodsMethods to offer. Defaults to ["card"].
payment_method_configobjectNoSee Saved cardsOptions for saved cards and Mercado Pago.
checkout_ui_versionstringNov1, v2Checkout design. v2 is the current one. If omitted, your business default applies, then v2.
submit_typestringNopay, deposit, subscribeWording of the amount and the pay button. A custom button text in ui_config takes precedence.
localestringNoes, en, zhLanguage the checkout opens in. The customer can still change it on the page.
ui_configobjectNoSee Customize the checkoutAppearance for this session only.
ui_config_versionstringNoe.g. V1Which saved business appearance to use. Defaults to V1. Unrelated to checkout_ui_version.
success_url, pending_url, cancel_urlstringNoURLSee Redirect URLs.
return_urlstringYes, if no success_urlURLFallback redirect.
expires_atnumberNoUnix time in secondsBetween 30 minutes and 24 hours from now. Defaults to 24 hours.
external_idstringNo—Your order reference. Generated for you if you do not send one.
metadataobjectNo—Any data you want back in responses and webhooks. Echo your order reference here as metadata.external_id.
checkout_typestringNohosted, embeddedDefaults to hosted. Use embedded for an iframe.
post_message_enabledbooleanNotrue, falseSend events to your page. Requires checkout_type: "embedded".
redirect_on_completionstringNoalways, neverDefaults to always. never keeps the customer on the checkout so your page handles navigation.

Customer

FieldTypeRequiredDescription
first_name, last_name, emailstringYesCustomer identity.
phonestringNoCustomer phone, E.164. If Tonder configured your account to also require a phone for saved cards, send it on every session that includes saved_cards, always the same value. It doesn't replace email, which always identifies the customer. See Which customer a saved card belongs to.
addressobjectNocountry (required inside), state, city, streets, postal_code.
identificationobjectNotype and number (both required inside).

Line items

FieldTypeRequiredDescription
namestringYesItem name.
quantitynumberYesWhole number, 1 or more.
unit_pricenumberYesPrice per unit, greater than 0.
description, product_idstringNoItem description and your product reference.
discount, taxesnumberNoAmounts per line, shown in the order summary.
tax_ratenumberNoRate shown next to the taxes. Display only.

Line items are for display. The customer is always charged amount_total. The checkout shows the full breakdown only when the items add up to amount_total; otherwise it shows the total alone.

Example

curl -X POST 'https://api-stage.tonder.io/checkout/v1/sessions' \
  -H 'Authorization: Token YOUR_TEST_API_KEY' \
  -H 'Content-Type: application/json' \
  -H 'x-idempotency-key: ORD-001-attempt-1' \
  -d '{
    "customer": { "first_name": "Maria", "last_name": "Garcia", "email": "maria@example.com" },
    "amount_total": 150.00,
    "currency": "MXN",
    "external_id": "ORD-001",
    "line_items": [
      { "name": "Premium Plan", "quantity": 1, "unit_price": 150.00, "product_id": "plan-premium-01" }
    ],
    "payment_method_types": ["card", "saved_cards", "oxxopay"],
    "submit_type": "pay",
    "locale": "en",
    "return_url": "https://your-store.com/checkout/complete",
    "metadata": { "external_id": "ORD-001" }
  }'
curl -X POST 'https://api.tonder.io/checkout/v1/sessions' \
  -H 'Authorization: Token YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  -H 'x-idempotency-key: ORD-001-attempt-1' \
  -d '{
    "customer": { "first_name": "Maria", "last_name": "Garcia", "email": "maria@example.com" },
    "amount_total": 150.00,
    "currency": "MXN",
    "external_id": "ORD-001",
    "line_items": [
      { "name": "Premium Plan", "quantity": 1, "unit_price": 150.00, "product_id": "plan-premium-01" }
    ],
    "payment_method_types": ["card", "saved_cards", "oxxopay"],
    "submit_type": "pay",
    "locale": "en",
    "return_url": "https://your-store.com/checkout/complete",
    "metadata": { "external_id": "ORD-001" }
  }'
{
  "id": "cs_97_41528_42434df6c3644b9610df7b64a1fa9925e3b9562b05972097",
  "url": "https://stage-payflow.tonder.io/checkout/cs_97_41528_42434df6c3644b9610df7b64a1fa9925e3b9562b05972097",
  "status": "pending",
  "session_type": "payment",
  "checkout_type": "hosted",
  "payment_id": 41528,
  "checkout_ui_version": "v2",
  "submit_type": "pay",
  "locale": "en",
  "amount_total": 150.00,
  "currency": "MXN",
  "external_id": "ORD-001",
  "return_url": "https://your-store.com/checkout/complete",
  "payment_method_types": ["card", "saved_cards", "oxxopay"],
  "ui_config": {},
  "ui_config_version": "V1",
  "created_at": 1751483563558,
  "modified_at": 1751483563558,
  "expires_at": 1751569963
}

What the response gives you

FieldDescription
idThe session id. Use it with Get a session.
urlThe checkout page to redirect to or embed. It never changes, even if you change your defaults later.
statuspending, completed or expired.
payment_idA number that identifies the payment. It exists from creation, never changes, and stays the same when the customer retries with another card. 0 on enrollment sessions.
checkout_ui_version, submit_type, localeThe values applied to this session, fixed at creation.
payment_flowdirect when your account is processed through the Direct API; not present otherwise.
direct_transaction_idDirect API accounts only, once the customer has tried to pay: the id of the latest attempt.

The full list of identifiers and statuses is in the Reference.

Payment attempts

A customer can try to pay more than once in the same session — a card is declined and they retry with another one. The session keeps only the latest attempt: transaction_status (and direct_transaction_id on Direct API accounts) is replaced on every new attempt, so a first Declined can become Success. payment_id stays the same for the whole session, so every attempt belongs to the same payment.

Errors

HTTP statusCodeMeaningWhat to do
400E004The request is invalid: a required field is missing, a value is not allowed, or a payment field was sent on an enrollment session.Read metadata.errors (one entry per problem), fix the request and send it again.
401 / 403—Missing or invalid API key.Check the Authorization header and that the key belongs to this environment.
500E001Unexpected error.Retry with the same x-idempotency-key; contact support if it persists.
{
  "code": "E004",
  "message": "The request was invalid. Please check your input and try again.",
  "metadata": {
    "errors": [
      {
        "keyword": "required",
        "params": { "missingProperty": "currency" },
        "message": "must have required property 'currency'",
        "field": "currency"
      }
    ]
  },
  "name": "TNDR004",
  "statusCode": 400
}

Saved cards

Include saved_cards in payment_method_types and the checkout shows the customer's previously saved cards alongside the new card form. Listing, selecting and deleting them is handled for you — there is no extra endpoint to call.

This is the Hosted Checkout path. If you render your own form with the Web SDK, saved cards work differently — see Saved cards (secure_token).

Which customer a saved card belongs to

Saved cards always belong to the customer identified by the session's customer.email. Tonder can configure your account to also require customer.phone for saved cards: the customer is still identified by their email, and the phone is what makes their saved cards available. Ask Tonder support whether your account requires it — it cannot be read from the API.

When your account requires a phone:

  • Send both customer.email and customer.phone on every session that includes saved_cards, including the one where the card is first saved (and every card-enrollment session). Without customer.phone there are no saved cards: the customer does not see them.
  • The phone does not replace the email. The same phone with a different email is a different customer and does not see those cards.
  • Tonder compares the phone exactly as you send it. It is not normalized: +525512345678 and 5512345678 are not the same value. Normalize the number in your own application before you create the session, and always send the same value for the same customer, in E.164 — +, country code and number, for example +525512345678.
"customer": {
  "first_name": "Maria",
  "last_name": "Garcia",
  "email": "maria@example.com",
  "phone": "+525512345678"
}

The save-card checkbox

Set payment_method_config.saved_cards.show_save_card_checkbox to true to put a "save this card for future payments" checkbox on the new card form, so the customer can opt in to saving their card.

FieldTypeDescription
show_save_card_checkboxbooleanShows a "save this card for future payments" checkbox on the new card form. Not shown when Card-on-File is enabled, because cards are then saved automatically.
curl -X POST 'https://api-stage.tonder.io/checkout/v1/sessions' \
  -H 'Authorization: Token YOUR_TEST_API_KEY' \
  -H 'Content-Type: application/json' \
  -d '{
    "customer": { "first_name": "Maria", "last_name": "Garcia", "email": "maria@example.com" },
    "amount_total": 150.00,
    "currency": "MXN",
    "line_items": [
      { "name": "Premium Plan", "quantity": 1, "unit_price": 150.00 }
    ],
    "payment_method_types": ["card", "saved_cards"],
    "payment_method_config": {
      "saved_cards": { "show_save_card_checkbox": true }
    },
    "external_id": "ORD-001",
    "return_url": "https://your-store.com/checkout/complete"
  }'
curl -X POST 'https://api.tonder.io/checkout/v1/sessions' \
  -H 'Authorization: Token YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  -d '{
    "customer": { "first_name": "Maria", "last_name": "Garcia", "email": "maria@example.com" },
    "amount_total": 150.00,
    "currency": "MXN",
    "line_items": [
      { "name": "Premium Plan", "quantity": 1, "unit_price": 150.00 }
    ],
    "payment_method_types": ["card", "saved_cards"],
    "payment_method_config": {
      "saved_cards": { "show_save_card_checkbox": true }
    },
    "external_id": "ORD-001",
    "return_url": "https://your-store.com/checkout/complete"
  }'

Card-on-File

Card-on-File (COF) saves cards automatically and lets the customer pay again without re-entering their CVV. It runs on Tonder's subscription and tokenization infrastructure.

When Card-on-File is enabled for your account, the checkout:

  1. Runs a 3D Secure check as the card is saved.
  2. Creates a subscription for that card.
  3. Accepts payments with it on return visits without asking for the CVV.

If Card-on-File is enabled for your account, the checkout does all of this on its own. The one thing your integration may need to send is customer.phone, when your account also requires a phone for saved cards — see Which customer a saved card belongs to. It is the same subscription the SDK surfaces as subscription_id — see the Web SDK reference.

Sending show_save_card_checkbox: true while Card-on-File is active will not show the checkbox. That is expected: Card-on-File takes precedence and cards are saved automatically either way.

API reference

POST
/checkout/v1/sessions

Authorization

Authorization
Authorization<token>

Your public API key, with the word Token and a space in front of it. Example: Token 6534bc0a7e1f4d2b9c8e3a5f7b1d2c4e6f8a9b0c — that whole value is the header. Sending the key alone returns 401 'Authentication credentials were not provided'. In this playground you may paste just the key; the prefix is added for you.

In: header

Request Body

application/json

TypeScript Definitions

Use the request body type in TypeScript.

Response Body

application/json

application/json

application/json

curl -X POST "https://example.com/checkout/v1/sessions" \  -H "Content-Type: application/json" \  -d '{    "customer": {      "first_name": "Maria",      "last_name": "Garcia",      "email": "maria@example.com"    },    "amount_total": 150,    "currency": "MXN",    "external_id": "ORD-001",    "line_items": [      {        "name": "Premium Plan",        "quantity": 1,        "unit_price": 150,        "product_id": "plan-premium-01"      }    ],    "payment_method_types": [      "card",      "saved_cards",      "oxxopay"    ],    "submit_type": "pay",    "locale": "en",    "return_url": "https://your-store.com/checkout/complete",    "metadata": {      "external_id": "ORD-001"    }  }'
{
  "id": "cs_97_41528_42434df6c3644b9610df7b64a1fa9925e3b9562b05972097",
  "url": "https://stage-payflow.tonder.io/checkout/cs_97_41528_42434df6c3644b9610df7b64a1fa9925e3b9562b05972097",
  "status": "pending",
  "session_type": "payment",
  "checkout_type": "hosted",
  "payment_id": 41528,
  "checkout_ui_version": "v2",
  "submit_type": "pay",
  "locale": "en",
  "amount_total": 150,
  "currency": "MXN",
  "external_id": "ORD-001",
  "return_url": "https://your-store.com/checkout/complete",
  "payment_method_types": [
    "card",
    "saved_cards",
    "oxxopay"
  ],
  "ui_config": {},
  "ui_config_version": "V1",
  "created_at": 1751483563558,
  "modified_at": 1751483563558,
  "expires_at": 1751569963
}
{
  "code": "E004",
  "message": "The request was invalid. Please check your input and try again.",
  "metadata": {
    "errors": [
      {
        "keyword": "required",
        "params": {
          "missingProperty": "currency"
        },
        "message": "must have required property 'currency'",
        "field": "currency"
      }
    ]
  },
  "name": "TNDR004",
  "statusCode": 400
}
{
  "code": "E004",
  "message": "The request was invalid. Please check your input and try again.",
  "metadata": {},
  "name": "TNDR004",
  "statusCode": 400
}

Next steps

Was this page helpful?

On this page