Checkout sessions
/v1/checkout-sessions — checkout hospedado para suscripciones recurrentes.
Ver también el concepto Checkout y la guía
Crear una suscripción.
El objeto Checkout Session
{
"id": "cs_01HZXK7QJ5M8Z9X2R3TVQ7A1B2",
"status": "open",
"subscription_id": null,
"url": "https://checkout.rekurra.dev/cs_01HZXK7QJ5M8Z9X2R3TVQ7A1B2",
"expires_at": "2026-08-25T15:00:00Z"
}| Campo | Tipo | Descripción |
|---|---|---|
id | string | Identificador único de la sesión |
status | string | open · complete · expired |
subscription_id | string | null | Suscripción creada, una vez completado el pago |
url | string | URL hospedada a la que rediriges a tu cliente |
expires_at | datetime | Fecha de expiración de la sesión |
Crear una sesión de checkout
POST /v1/checkout-sessions
Request body
{
"plan_id": "plan_01HZXK7QJ5M8Z9X2R3TVQ7A1B2",
"cycle": "monthly",
"customer": { "email": "cliente@ejemplo.com", "name": "María Pérez" },
"success_url": "https://tuapp.com/gracias",
"cancel_url": "https://tuapp.com/checkout",
"metadata": { "origen": "landing" }
}| Campo | Tipo | Requerido | Descripción |
|---|---|---|---|
plan_id | string | Sí | Plan que se le cobrará al cliente |
cycle | string | Sí | monthly o annual |
customer | object | Sí* | Cliente nuevo (email, name) |
customer_id | string | Sí* | ID de un cliente existente, en lugar de customer |
success_url | string | No | Redirección tras el pago; si se omite, el checkout muestra su pantalla de confirmación |
cancel_url | string | No | Redirección si el cliente abandona o la sesión expira |
metadata | object | No | Pares clave-valor propios |
* Envía customer o customer_id, no ambos.
success_url y cancel_url son opcionales. Si omites success_url, al
completar el pago el checkout hospedado muestra su propia pantalla de
confirmación con el resumen de la transacción en vez de redirigir. Si lo
envías, Rekurra redirige ahí añadiendo subscription_id, status,
amount_in_cents y currency como query params — sin datos personales del
cliente (correo, tarjeta), que se consultan por subscription_id.
metadata (máx. 60 KB serializado) queda guardado en la Subscription que
resulta de completar el checkout, y se repite en data.metadata de cada
webhook que dispares para esa suscripción a partir de ahí (creación,
cancelación, pausa, cambio de plan, cada cobro, etc.) — ver
Webhooks → metadata en cada evento.
curl -X POST https://api.rekurra.dev/v1/checkout-sessions \
-H "Authorization: Bearer rk_live_XXXXXXXXXXXXXXXXXXXXXXXX" \
-H "Content-Type: application/json" \
-d '{
"plan_id": "plan_01HZXK7QJ5M8Z9X2R3TVQ7A1B2",
"cycle": "monthly",
"customer": { "email": "cliente@ejemplo.com", "name": "María Pérez" },
"success_url": "https://tuapp.com/gracias",
"cancel_url": "https://tuapp.com/checkout"
}'Response — 201 Created
{
"id": "cs_01HZXK7QJ5M8Z9X2R3TVQ7A1B2",
"url": "https://checkout.rekurra.dev/cs_01HZXK7QJ5M8Z9X2R3TVQ7A1B2"
}Errores posibles
| Status | error.type | Causa |
|---|---|---|
400 | invalid_request | plan_id inválido |
404 | invalid_request | plan_id no existe |
Obtener una sesión de checkout
GET /v1/checkout-sessions/{id}
curl https://api.rekurra.dev/v1/checkout-sessions/cs_01HZXK7QJ5M8Z9X2R3TVQ7A1B2 \
-H "Authorization: Bearer rk_live_XXXXXXXXXXXXXXXXXXXXXXXX"Response — 200 OK
{
"id": "cs_01HZXK7QJ5M8Z9X2R3TVQ7A1B2",
"status": "complete",
"subscription_id": "sub_01HZXK7QJ5M8Z9X2R3TVQ7A1B2",
"url": "https://checkout.rekurra.dev/cs_01HZXK7QJ5M8Z9X2R3TVQ7A1B2",
"expires_at": "2026-08-25T15:00:00Z"
}Errores posibles
| Status | error.type | Causa |
|---|---|---|
404 | invalid_request | La sesión no existe |
Consultar el estado (polling)
GET /v1/checkout-sessions/{id}/status
Superficie ligera para verificar el resultado del checkout si no te llegó el
webhook subscription.created. Devuelve solo el estado y el
subscription_id (una vez completado). Rekurra ya tiene el estado actualizado
por el webhook entrante de Wompi, así que esta consulta no hace una llamada
extra a Wompi.
{
"id": "cs_01HZXK7QJ5M8Z9X2R3TVQ7A1B2",
"status": "complete",
"subscription_id": "sub_01HZXK7QJ5M8Z9X2R3TVQ7A1B2"
}| Campo | Tipo | Descripción |
|---|---|---|
id | string | Identificador de la sesión |
status | string | open · complete · expired |
subscription_id | string | null | Suscripción creada, una vez completado el pago |
Rate limit. Este endpoint tiene un límite dedicado de 20 solicitudes por minuto por API key (además del límite global de 100/min), pensado para hacer polling cada pocos segundos durante un par de minutos tras el checkout. Al superarlo responde
429conerror.type: rate_limit(ver Errores). Usa los webhooks como fuente de verdad y el polling solo como respaldo.
curl https://api.rekurra.dev/v1/checkout-sessions/cs_01HZXK7QJ5M8Z9X2R3TVQ7A1B2/status \
-H "Authorization: Bearer rk_live_XXXXXXXXXXXXXXXXXXXXXXXX"Errores posibles
| Status | error.type | Causa |
|---|---|---|
404 | invalid_request | La sesión no existe |
429 | rate_limit | Se superó el límite de 20 solicitudes por minuto |
Cómo el checkout hospedado completa el pago (interno)
Este flujo es interno (no lo implementas tú si solo creas la sesión), pero entenderlo ayuda a saber qué datos hay disponibles después del pago:
POST /checkout/{id}/complete — invocado por la página hospedada
(checkout.rekurra.dev) cuando el cliente confirma el pago, con el
card_token y el acceptance_token de Wompi. La respuesta incluye la
subscription_id, la redirect_url (calculada a partir de success_url si
lo enviaste al crear la sesión, o null si no) y un objeto summary con el
resumen de la transacción:
{
"status": "complete",
"subscription_id": "sub_01HZXK7QJ5M8Z9X2R3TVQ7A1B2",
"redirect_url": "https://tuapp.com/gracias?subscription_id=sub_01HZXK7QJ5M8Z9X2R3TVQ7A1B2&status=active&amount_in_cents=2500000¤cy=COP",
"summary": {
"status": "active",
"customer_email": "cliente@ejemplo.com",
"customer_name": "María Pérez",
"plan_name": "Plan Pro",
"amount_in_cents": 2500000,
"currency": "COP",
"card_brand": "VISA",
"card_last4": "4242"
}
}Si no enviaste success_url al crear la sesión, redirect_url es null y
el checkout hospedado usa summary para renderizar su propia pantalla de
confirmación. Nota que, a diferencia de redirect_url, summary sí incluye
datos personales (customer_email, customer_name) — esta respuesta la
consume la página hospedada directamente, nunca se expone en la URL de
redirección.