Crear una sesión
La petición que crea una sesión de Hosted Checkout, campo por campo, y lo que te da la respuesta.
Llama a este endpoint desde tu backend cada vez que un cliente esté listo para pagar. Crea una
sesión de checkout —para un pago o para un registro de tarjeta— y devuelve la url a la que
envías al cliente.
POST https://api-stage.tonder.io/checkout/v1/sessions # Sandbox
POST https://api.tonder.io/checkout/v1/sessions # ProducciónCómo funciona
- Tu servidor llama a este endpoint con el cliente, el monto y los artículos.
- Tonder crea la sesión y, en las sesiones de pago, el pago. Recibes la
urldel checkout y elpayment_id. - Rediriges al cliente a la
url, o la embebes. - El cliente paga en la página de Tonder. Puede reintentar en la misma sesión.
- El cliente regresa a tu sitio y confirmas el resultado con webhooks o consultando la sesión.
Una sesión es de un solo uso y es válida por un máximo de 24 horas. Su monto, moneda y artículos no se pueden cambiar; en su lugar, crea una sesión nueva.
Headers
| Header | Valor | Requerido | Descripción |
|---|---|---|---|
Authorization | Token <your_api_key> | Sí | Tu llave API, desde Developers en el Dashboard. Llama a la API solo desde tu backend. |
Content-Type | application/json | Sí | El cuerpo es JSON. |
x-idempotency-key | Cualquier cadena única | No | Evita crear la misma sesión dos veces cuando reintentas una petición dentro de 5 segundos. Consulta Idempotencia. |
Cuerpo
| Campo | Tipo | Requerido | Valores | Descripción |
|---|---|---|---|---|
session_type | string | No | payment, enrollment | Por defecto payment. enrollment guarda una tarjeta sin cobrar; consulta Guardar una tarjeta sin cobrar. |
customer | object | Sí | Ver Cliente | El cliente que paga. Las tarjetas guardadas pertenecen a este cliente. |
amount_total | number | Solo en pagos | Mayor que 0 | Total a cobrar, en formato decimal (150.00). No se valida contra la suma de line_items. |
currency | string | Solo en pagos | p. ej. MXN | La moneda de tu negocio. |
line_items | array | Solo en pagos | Ver Artículos | Lo que el cliente está pagando. |
payment_method_types | array | No | Ver Métodos de pago | Métodos a ofrecer. Por defecto ["card"]. |
payment_method_config | object | No | Ver Tarjetas guardadas | Opciones para tarjetas guardadas y Mercado Pago. |
checkout_ui_version | string | No | v1, v2 | Diseño del checkout. v2 es el actual. Si se omite, aplica el valor por defecto de tu negocio y después v2. |
submit_type | string | No | pay, deposit, subscribe | Texto del monto y del botón de pago. Un texto de botón personalizado en ui_config tiene precedencia. |
locale | string | No | es, en, zh | Idioma en el que abre el checkout. El cliente puede cambiarlo en la página. |
ui_config | object | No | Ver Personalizar el checkout | Apariencia solo para esta sesión. |
ui_config_version | string | No | p. ej. V1 | Cuál de las apariencias guardadas de tu negocio usar. Por defecto V1. No tiene relación con checkout_ui_version. |
success_url, pending_url, cancel_url | string | No | URL | Ver URLs de redirección. |
return_url | string | Sí, si no hay success_url | URL | Redirección de respaldo. |
expires_at | number | No | Tiempo Unix en segundos | Entre 30 minutos y 24 horas a partir de ahora. Por defecto 24 horas. |
external_id | string | No | — | Tu referencia del pedido. Se genera por ti si no envías una. |
metadata | object | No | — | Cualquier dato que quieras recibir de vuelta en las respuestas y los webhooks. Repite aquí tu referencia del pedido como metadata.external_id. |
checkout_type | string | No | hosted, embedded | Por defecto hosted. Usa embedded para un iframe. |
post_message_enabled | boolean | No | true, false | Envía eventos a tu página. Requiere checkout_type: "embedded". |
redirect_on_completion | string | No | always, never | Por defecto always. never mantiene al cliente en el checkout para que tu página maneje la navegación. |
Cliente
| Campo | Tipo | Requerido | Descripción |
|---|---|---|---|
first_name, last_name, email | string | Sí | Identidad del cliente. |
phone | string | No | Teléfono del cliente, en E.164. Si Tonder configuró tu cuenta para requerir además el teléfono para las tarjetas guardadas, envíalo en cada sesión que incluya saved_cards, siempre el mismo valor. No reemplaza al email, que siempre identifica al cliente. Consulta A qué cliente pertenece una tarjeta guardada. |
address | object | No | country (requerido dentro del objeto), state, city, streets, postal_code. |
identification | object | No | type y number (ambos requeridos dentro del objeto). |
Artículos
| Campo | Tipo | Requerido | Descripción |
|---|---|---|---|
name | string | Sí | Nombre del artículo. |
quantity | number | Sí | Número entero, 1 o más. |
unit_price | number | Sí | Precio por unidad, mayor que 0. |
description, product_id | string | No | Descripción del artículo y tu referencia del producto. |
discount, taxes | number | No | Montos por línea, mostrados en el resumen del pedido. |
tax_rate | number | No | Tasa que se muestra junto a los impuestos. Solo informativa. |
Los artículos son solo para mostrarse. Al cliente siempre se le cobra amount_total. El checkout
muestra el desglose completo solo cuando los artículos suman amount_total; de lo contrario,
muestra únicamente el total.
Ejemplo
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
}Qué te da la respuesta
| Campo | Descripción |
|---|---|
id | El id de la sesión. Úsalo con Consultar una sesión. |
url | La página de checkout a la que rediriges o que embebes. Nunca cambia, aunque después cambies tus valores por defecto. |
status | pending, completed o expired. |
payment_id | Un número que identifica el pago. Existe desde la creación, nunca cambia y se mantiene igual cuando el cliente reintenta con otra tarjeta. 0 en las sesiones de registro de tarjeta. |
checkout_ui_version, submit_type, locale | Los valores aplicados a esta sesión, fijos desde la creación. |
payment_flow | direct cuando tu cuenta se procesa con la API Direct; en otro caso no está presente. |
direct_transaction_id | Solo en cuentas de API Direct, una vez que el cliente ha intentado pagar: el id del intento más reciente. |
La lista completa de identificadores y estados está en la Referencia.
Intentos de pago
Un cliente puede intentar pagar más de una vez en la misma sesión: una tarjeta es rechazada y
reintenta con otra. La sesión conserva solo el intento más reciente: transaction_status (y
direct_transaction_id en cuentas de API Direct) se reemplaza con cada intento nuevo, así que un
primer Declined puede convertirse en Success. El payment_id se mantiene igual durante toda la
sesión, así que todos los intentos pertenecen al mismo pago.
Errores
| Estado HTTP | Código | Significado | Qué hacer |
|---|---|---|---|
| 400 | E004 | La petición es inválida: falta un campo requerido, un valor no está permitido, o se envió un campo de pago en una sesión de registro de tarjeta. | Lee metadata.errors (una entrada por problema), corrige la petición y envíala de nuevo. |
| 401 / 403 | — | Llave API faltante o inválida. | Revisa el header Authorization y que la llave pertenezca a este entorno. |
| 500 | E001 | Error inesperado. | Reintenta con la misma x-idempotency-key; contacta a soporte si persiste. |
{
"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
}Tarjetas guardadas
Incluye saved_cards en payment_method_types y el checkout muestra las tarjetas que el cliente
ya guardó, junto al formulario de tarjeta nueva. Listarlas, seleccionarlas y eliminarlas se maneja
por ti: no hay un endpoint extra que llamar.
Esta es la vía de Hosted Checkout. Si renderizas tu propio formulario con el SDK Web, las tarjetas
guardadas funcionan distinto; consulta
Tarjetas guardadas (secure_token).
A qué cliente pertenece una tarjeta guardada
Las tarjetas guardadas siempre pertenecen al cliente identificado por el customer.email de la
sesión. Tonder puede configurar tu cuenta para requerir además customer.phone para las
tarjetas guardadas: el cliente se sigue identificando por su email, y el teléfono es lo que habilita
sus tarjetas guardadas. Pregunta a soporte de Tonder si tu cuenta lo
requiere — no se puede consultar por API.
Cuando tu cuenta requiere teléfono:
- Envía
customer.emailycustomer.phoneen cada sesión que incluyasaved_cards, incluida aquella en la que la tarjeta se guarda por primera vez (y en cada sesión de registro de tarjeta). Sincustomer.phoneno hay tarjetas guardadas: el cliente no las ve. - El teléfono no reemplaza al email. El mismo teléfono con otro email es otro cliente y no ve esas tarjetas.
- Tonder compara el teléfono exactamente como lo envías. No se normaliza:
+525512345678y5512345678no son el mismo valor. Normaliza el número en tu propia aplicación antes de crear la sesión y envía siempre el mismo valor para el mismo cliente, en E.164 —+, código de país y número, por ejemplo+525512345678.
"customer": {
"first_name": "Maria",
"last_name": "Garcia",
"email": "maria@example.com",
"phone": "+525512345678"
}La casilla de guardar tarjeta
Pon payment_method_config.saved_cards.show_save_card_checkbox en true para mostrar una casilla
"guardar esta tarjeta para futuros pagos" en el formulario de tarjeta nueva, para que el cliente
pueda elegir guardar su tarjeta.
| Campo | Tipo | Descripción |
|---|---|---|
show_save_card_checkbox | boolean | Muestra una casilla "guardar esta tarjeta para futuros pagos" en el formulario de tarjeta nueva. No se muestra cuando Card-on-File está habilitado, porque entonces las tarjetas se guardan automáticamente. |
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) guarda las tarjetas automáticamente y permite que el cliente vuelva a pagar sin volver a capturar su CVV. Funciona sobre la infraestructura de suscripciones y tokenización de Tonder.
Cuando Card-on-File está habilitado para tu cuenta, el checkout:
- Ejecuta una verificación 3D Secure al guardar la tarjeta.
- Crea una suscripción para esa tarjeta.
- Acepta pagos con ella en visitas posteriores sin pedir el CVV.
Si Card-on-File está habilitado para tu cuenta, el checkout se encarga de todo esto por sí solo. Lo
único que tu integración puede necesitar enviar es customer.phone, cuando tu cuenta requiere
además el teléfono para las tarjetas guardadas — consulta
A qué cliente pertenece una tarjeta guardada. Es
la misma suscripción que el SDK expone como subscription_id; consulta la
Referencia del SDK Web.
Enviar show_save_card_checkbox: true con Card-on-File activo no mostrará la casilla. Es el
comportamiento esperado: Card-on-File tiene precedencia y las tarjetas se guardan automáticamente
de cualquier forma.
Referencia de la API
Autorización
Authorization 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
Cuerpo de la petición
application/json
TypeScript Definitions
Use the request body type in TypeScript.
Cuerpo de la respuesta
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
}