Referencia de APICheckout sessions

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"
}
CampoTipoDescripción
idstringIdentificador único de la sesión
statusstringopen · complete · expired
subscription_idstring | nullSuscripción creada, una vez completado el pago
urlstringURL hospedada a la que rediriges a tu cliente
expires_atdatetimeFecha 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" }
}
CampoTipoRequeridoDescripción
plan_idstringSíPlan que se le cobrará al cliente
cyclestringSímonthly o annual
customerobjectSí*Cliente nuevo (email, name)
customer_idstringSí*ID de un cliente existente, en lugar de customer
success_urlstringNoRedirección tras el pago; si se omite, el checkout muestra su pantalla de confirmación
cancel_urlstringNoRedirección si el cliente abandona o la sesión expira
metadataobjectNoPares 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

Statuserror.typeCausa
400invalid_requestplan_id inválido
404invalid_requestplan_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

Statuserror.typeCausa
404invalid_requestLa 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.

200 OK
{
  "id": "cs_01HZXK7QJ5M8Z9X2R3TVQ7A1B2",
  "status": "complete",
  "subscription_id": "sub_01HZXK7QJ5M8Z9X2R3TVQ7A1B2"
}
CampoTipoDescripción
idstringIdentificador de la sesión
statusstringopen · complete · expired
subscription_idstring | nullSuscripció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 429 con error.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

Statuserror.typeCausa
404invalid_requestLa sesión no existe
429rate_limitSe 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:

200 OK
{
  "status": "complete",
  "subscription_id": "sub_01HZXK7QJ5M8Z9X2R3TVQ7A1B2",
  "redirect_url": "https://tuapp.com/gracias?subscription_id=sub_01HZXK7QJ5M8Z9X2R3TVQ7A1B2&status=active&amount_in_cents=2500000&currency=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.