Skip to Content
Documentación de integración del API de Seif. ¿Dudas? soporte@pagosripei.com
EndpointsSesiones de pago

Sesiones de pago

Una sesión de pago representa una intención de cobro. La creas en tu servidor y obtienes un checkoutUrl para cobrar al comprador. Referencia pública: cs_…. Es de corta duración: 5 minutos por defecto, o lo que indiques en expiresInMinutes (ver Duración de la sesión). Créala justo antes de cobrar y usa expiresAt para el momento exacto de expiración.


Crear una sesión

POST /v1/sessions

Autenticación: clave secreta (Authorization: Bearer sk_…).

Cuerpo

CampoTipoRequeridoDescripción
amountMinorenteroSíMonto en unidades menores (céntimos). Mínimo 1.
currencystringSíVES o USD. Hoy el cobro con tarjeta se liquida en VES.
customerRefstringNoTu id de cliente. La tarjeta guardada quedará asociada a él. Máx. 120.
cardTokenstringNoToken de una tarjeta ya guardada (tok_seif_…). El checkout pedirá solo el CVV. Ver Cobrar una tarjeta guardada.
returnUrlstring (URL)NoA dónde redirigir al comprador tras un cobro aprobado.
descriptionstringNoTexto corto mostrado en el checkout. Máx. 140.
maxFailuresenteroNoCuántos intentos fallidos tolera la sesión antes de cerrarse sola. Omítelo para el comportamiento por defecto. Entre 0 y 10. Ver Limitar los intentos.
expiresInMinutesenteroNoCuántos minutos dura la sesión. Por defecto 5. Entre 2 y 60. Ver Duración de la sesión.
prefillobjetoNoDatos que ya conoces del comprador para rellenar el formulario del checkout. Ver Rellenar el formulario.

Ejemplo

curl -X POST https://api.seif.pagosripei.com/v1/sessions \ -H "Authorization: Bearer sk_test_tu_clave" \ -H "Content-Type: application/json" \ -d '{ "amountMinor": 105000, "currency": "VES", "customerRef": "usuario_123", "description": "Pedido #1042", "returnUrl": "https://mitienda.com/gracias" }'

Respuesta 201

{ "id": "9f0c1f2a-...-e3", "reference": "cs_8sKd2pQ1Aa", "status": "created", "amountMinor": 105000, "currency": "VES", "expiresAt": "2026-06-09T18:30:00.000Z", "checkoutUrl": "https://checkout.seif.pagosripei.com/checkout?session=cs_8sKd2pQ1Aa", "description": "Pedido #1042" }
CampoDescripción
referenceReferencia pública de la sesión (cs_…).
statusEstado de la sesión (ver Estados de una sesión).
closedReasonPor qué se cerró: expired (por tiempo), max_failures_reached (agotó sus intentos) o merchant_request (la expiraste tú). null mientras no esté cerrada. Son los mismos valores que reason en el webhook session.expired.
checkoutUrlURL para abrir el checkout incrustado.
expiresAtCuándo expira (ISO-8601).
attemptsRemainingIntentos que le quedan antes de cerrarse. null si no enviaste maxFailures (sin límite).
prefillLos datos de prellenado tal como quedaron guardados, o null. Es lo que enviaste ya normalizado, no lo que el checkout terminará usando — ver Lo ya guardado tiene prioridad.

Duración de la sesión

Una sesión dura 5 minutos por defecto. Si tu flujo necesita otra cosa, envía expiresInMinutes al crearla: entre 2 y 60 minutos, en minutos enteros.

{ "amountMinor": 105000, "currency": "VES", "expiresInMinutes": 15 }
  • Más tiempo sirve si tus compradores tardan en tener la tarjeta a mano.
  • Menos tiempo sirve si necesitas liberar pronto lo que la sesión reserva (un cupo, una entrada).

La respuesta trae el expiresAt resultante, y todo lo demás sigue igual: la sesión se cierra sola a esa hora y se emite session.expired. Un valor fuera de rango se rechaza con 400; no se recorta.

Si tu pasarela le pide un código (OTP) al comprador, el código tiene que confirmarse antes de expiresAt. Con una sesión muy corta, al comprador puede no alcanzarle el tiempo para recibirlo y escribirlo.

Si lo que quieres es cerrar una sesión ya, antes de que se le acabe el tiempo, no hace falta acortarla: usa Expirar una sesión.


Rellenar el formulario (prefill)

Si ya tienes los datos del comprador en tu sistema, no hace falta que los escriba otra vez. Envía prefill al crear la sesión y el checkout abre con esos campos ya puestos.

{ "amountMinor": 105000, "currency": "VES", "prefill": { "cardholderName": "María Rodríguez", "documentType": "V", "documentId": "12345678" } }

Campos

CampoTipoRequeridoDescripción
cardholderNamestringNoNombre del titular. Máx. 80 caracteres.
documentTypestringSolo si envías documentIdV, E, J, G o P (pasaporte).
documentIdstringNoNúmero del documento. Dígitos para V/E/J/G; alfanumérico para P. Entre 5 y 12 caracteres.

Puedes enviar solo documentType. El checkout deja preseleccionado el tipo y el comprador únicamente escribe el número. Lo que no puedes es enviar un número sin su tipo: es el tipo el que define cómo se valida el número.

Qué hacemos con lo que envías

Antes de guardarlo, normalizamos los valores. Es útil conocerlo porque la respuesta te devuelve el resultado, no lo que enviaste:

  • Los separadores del documento se eliminan. "12.345.678" se guarda como "12345678", y un pasaporte se guarda en mayúsculas.
  • Los espacios de más se colapsan y se recorta el nombre.
  • Los caracteres invisibles y de control se eliminan (saltos de línea, espacios de ancho cero, marcas de dirección de texto). Un nombre que quede vacío tras esa limpieza se rechaza con 400.
  • El límite de 80 caracteres se aplica después de limpiar.

Es un prellenado, no una imposición

Esto es lo más importante de la funcionalidad:

  • El comprador puede editar los tres campos. Nada queda bloqueado.
  • El cobro se procesa con lo que el comprador envió, no con lo que tú prellenaste. Si lo corrige, vale su corrección.
  • Si necesitas saber con qué documento se cobró finalmente, léelo de la transacción, no de la sesión.

Lo ya guardado tiene prioridad

Si la sesión usa una tarjeta guardada (cardToken) y esa tarjeta ya tiene titular o documento, lo guardado gana y tu prellenado se ignora. No es un error: la sesión se crea igual, simplemente ese campo no se prellena porque ni siquiera se le pregunta al comprador.

La regla se aplica campo por campo:

La tarjeta guardada tiene…Qué pasa con tu prefill
Titular y documentoSe ignora completo. Al comprador solo se le pide el CVV.
Documento, pero no titularSe ignoran documentType y documentId; el nombre sí se prellena.
Titular, pero no documentoSe ignora cardholderName; el documento sí se prellena.
Nada (o no hay cardToken)Se aplica completo.

Por eso prefill rinde sobre todo en el primer cobro, cuando la tarjeta todavía no existe en Seif. A partir del segundo, la tarjeta ya trae sus datos y no hay nada que prellenar.

Esto es deliberado y no se puede desactivar: un prefill que pisara el documento guardado sería una forma de cobrarle a alguien con un documento que nunca vio. Para corregir los datos de una tarjeta guardada, el comprador los edita en el checkout cuando se le piden; el prefill no sirve para eso.

El prellenado viaja al navegador del comprador, porque es donde se muestra. Envía únicamente los datos de la persona a la que le pasaste ese enlace de pago: no uses prefill para nada que esa persona no deba ver.

Errores propios de este flujo

HTTPCausaSolución
400prefill.documentId sin prefill.documentType.Envía siempre el tipo junto con el número.
400El documento no cumple el formato de su tipo.Dígitos para V/E/J/G; alfanumérico para P. Entre 5 y 12 caracteres.
400prefill.cardholderName vacío o de más de 80 caracteres.Omite el campo en vez de mandarlo vacío.
400Una clave desconocida dentro de prefill.Solo se aceptan cardholderName, documentType y documentId.

Cobrar una tarjeta guardada (solo CVV)

Cuando tu cliente ya tiene una tarjeta guardada, no vuelvas a pedirle el número. Crea la sesión con el cardToken de esa tarjeta y el checkout mostrará únicamente:

  • la tarjeta con la que va a pagar (marca y últimos 4 dígitos),
  • el campo CVV,
  • su documento de identidad.

El número y el vencimiento salen de la bóveda del lado de Seif. Tu servidor nunca toca datos de tarjeta, así que el flujo es 100 % PCI para ti.

curl -X POST https://api.seif.pagosripei.com/v1/sessions \ -H "Authorization: Bearer sk_test_tu_clave" \ -H "Content-Type: application/json" \ -d '{ "amountMinor": 105000, "currency": "VES", "cardToken": "tok_seif_Xa92...kQ", "description": "Pedido #1043" }'

La respuesta es idéntica a la de cualquier sesión: abre el checkoutUrl (o móntalo con seif-js) y el comprador solo confirma su CVV.

Si no envías customerRef, la sesión hereda el de la tarjeta. Y si tu pasarela pide OTP, el flujo del checkout es el mismo que en un pago normal.

Errores propios de este flujo

HTTPCausaSolución
400cardToken no empieza por tok_seif_.Usa el seifToken tal cual lo devuelve GET /v1/cards.
404La tarjeta no existe en tu comercio, o fue eliminada.Vuelve a listar las tarjetas del cliente y usa una activa.
409La tarjeta no admite este flujo (por ejemplo, quedó guardada en una pasarela que no pide CVV).Escríbenos a soporte: esa tarjeta se cobra con un flujo servidor a servidor.

El cardToken se valida al crear la sesión, no al pagar: un token inválido falla en tu servidor, nunca delante del comprador.


Consultar una sesión

GET /v1/sessions/{reference}
curl https://api.seif.pagosripei.com/v1/sessions/cs_8sKd2pQ1Aa \ -H "Authorization: Bearer sk_test_tu_clave"

Devuelve el mismo objeto, con el status actualizado.


Expirar una sesión

POST /v1/sessions/{reference}/expire

Autenticación: clave secreta (Authorization: Bearer sk_…). Sin cuerpo.

Cierra la sesión ya, antes de que se le acabe el tiempo o los intentos. Úsalo cuando el pedido deja de existir: se agotó, el cliente pagó por otra vía o lo anulaste. Desde ese momento la sesión ya no se puede pagar: si el comprador intenta pagar, el checkout le muestra la pantalla de sesión expirada.

curl -X POST https://api.seif.pagosripei.com/v1/sessions/cs_8sKd2pQ1Aa/expire \ -H "Authorization: Bearer sk_test_tu_clave"

Respuesta 200

El mismo objeto de la sesión, ya cerrada:

{ "reference": "cs_8sKd2pQ1Aa", "status": "expired", "closedReason": "merchant_request", "expiresAt": "2026-06-09T18:30:00.000Z", "...": "…" }
  • status pasa a expired y closedReason a merchant_request. expiresAt no cambia: sigue diciendo cuándo iba a vencer.
  • Se emite el webhook session.expired, con el mismo formato de siempre y reason: "merchant_request".
  • Si la sesión ya había vencido por tiempo, pero todavía no se había marcado, se cierra con closedReason: "expired": es lo que de verdad le pasó.

Puedes repetir la llamada sin problema. Si la sesión ya estaba cerrada (por tiempo, por intentos o por una llamada anterior), responde 200 con su estado actual y no emite otro webhook.

Cuándo no se puede

HTTPCausaQué hacer
404La sesión no existe en tu comercio.Revisa la reference.
409La sesión ya se pagó (completed).No hay nada que expirar: si hace falta, reembolsa el cobro.
409Hay un cobro en curso (processing, otp_requested, otp_confirmed).El banco ya lo tiene. Espera a que se resuelva y vuelve a llamar: si se rechaza, la sesión se podrá expirar.
409La sesión se creó con la otra clave (prueba o producción).Usa una clave del mismo modo que la creó.
503En una sesión de Stripe, no pudimos cancelar el pago en Stripe.No se cambió nada; vuelve a intentarlo.

Sesiones de Stripe (USD): el comprador paga directamente contra Stripe, así que al expirar la sesión cancelamos también su pago pendiente en Stripe. Si Stripe ya lo había cobrado, respondemos 409 en vez de dar la sesión por expirada. Si el comprador ya había abierto el checkout, esa transacción pasa a failed y recibes también charge.failed.


Listar sesiones

GET /v1/sessions

Autenticación: clave secreta (Authorization: Bearer sk_…).

Devuelve tus sesiones, de la más reciente a la más antigua, con cuántos intentos de cobro generó cada una. Sirve para revisar qué pasó con tus cobros: cuáles se pagaron, cuáles se rechazaron y cuáles nadie llegó a intentar.

Parámetros (query)

ParámetroTipoDescripción
pageenteroPágina, desde 1. Por defecto 1.
pageSizeenteroSesiones por página. Por defecto 10.
statusstringFiltra por estado. Usa los mismos valores de Estados de una sesión. expired trae todas las cerradas sin pagarse, sea cual sea su closedReason.
curl "https://api.seif.pagosripei.com/v1/sessions?status=expired&page=1&pageSize=20" \ -H "Authorization: Bearer sk_test_tu_clave"

Respuesta 200

{ "data": [ { "reference": "cs_8sKd2pQ1Aa", "status": "expired", "closedReason": "max_failures_reached", "amountMinor": 105000, "currency": "VES", "description": "Pedido #1042", "customerRef": "usuario_123", "createdAt": "2026-06-09T18:25:00.000Z", "expiresAt": "2026-06-09T18:30:00.000Z", "attemptCount": 1, "attemptsRemaining": 0, "lastTransaction": { "reference": "txn_…", "status": "declined" } } ], "total": 1, "page": 1, "pageSize": 20 }
CampoDescripción
statusEl mismo estado que devuelve GET /v1/sessions/{reference} para esa sesión.
closedReasonPor qué se cerró, o null. Igual que en el detalle.
attemptCountCuántos intentos de cobro generó. 0 significa que el comprador nunca llegó a intentar pagar.
attemptsRemainingIntentos que le quedan, o null si no tiene límite.
lastTransactionEl último intento de cobro (el mismo objeto que en Transacciones), o null si no hubo ninguno.

Los cobros con tarjeta guardada que haces servidor a servidor (POST /v1/charges) no tienen sesión, así que no aparecen aquí. Consúltalos en Transacciones.


Estados de una sesión

El listado y el detalle devuelven siempre el mismo estado para la misma sesión.

EstadoSignificado
createdLista para cobrar.
otp_requestedEl comprador está confirmando el pago con un código (pasarelas con segundo factor).
otp_confirmedEl código se confirmó y el cobro se está enviando.
processingHay un cobro en curso.
completedPagada con éxito.
failedUn cobro falló o fue rechazado, pero la sesión sigue viva y se puede reintentar.
expiredCerrada sin pagarse: se alcanzó expiresAt, agotó sus intentos o la expiraste tú. closedReason dice cuál.

Una sesión pasa a expired en cuanto llega expiresAt, sin que nadie tenga que volver a abrirla. La única excepción es una sesión con un cobro en curso (processing, otp_requested, otp_confirmed): vencer no corta un cobro que ya salió hacia el banco. Si ese cobro se rechaza, la sesión se cierra entonces. La misma regla aplica si la expiras tú: con un cobro en curso, la llamada responde 409.


Limitar los intentos

Por defecto una sesión se puede reintentar libremente hasta que expire. Si un cobro es rechazado, el comprador puede corregir los datos o probar otra tarjeta. Eso funciona bien para la mayoría, pero no para todos: hay integraciones que necesitan un número fijo de intentos y una respuesta definitiva, para que su propio sistema deje de esperar.

Para eso está maxFailures.

{ "amountMinor": 105000, "currency": "VES", "maxFailures": 0 }

Qué significa el número

maxFailures es cuántos fallos tolera la sesión, no cuántos intentos concede. El intento que rompe el límite también cuenta, así que el total de intentos es maxFailures + 1.

ValorIntentos totalesComportamiento
(omitido)IlimitadosReintentos libres hasta expiresAt. Es el comportamiento por defecto.
01Al primer fallo la sesión se cierra. No espera a que expire.
12Tolera un fallo; el segundo la cierra.
23Tolera dos fallos; el tercero la cierra.

0 no es lo mismo que omitir el campo. Omitirlo significa “sin límite”; 0 significa “cierra al primer fallo”. Si tu cliente HTTP descarta los ceros al serializar, revísalo: la diferencia entre los dos es total.

Qué cuenta como fallo

Cualquier intento que termine en error. No distinguimos entre un rechazo del banco y un dato mal escrito: si el cobro no salió, gastó un intento.

  • Rechazado por el banco emisor — cuenta.
  • Fallido en el procesador — cuenta.
  • Datos inválidos (400: falta el documento, falta el nombre del titular) — cuenta.

No consumen intentos las peticiones que nunca llegaron a ser un intento:

  • Una sesión ya cerrada, expirada o con un cobro en curso (409 / 410).
  • Los OTP incorrectos, que tienen su propio límite independiente.

Cómo se cierra

Una sesión puede terminar sin pagarse de tres maneras: por tiempo (expiresAt), por fallos (maxFailures) o porque la expiraste tú (POST /v1/sessions/{reference}/expire). La que ocurra primero.

Se cierre como se cierre, el resultado es el mismo:

  • Su estado pasa a expired, y closedReason dice de qué manera murió.
  • El checkout muestra la pantalla de sesión expirada, sin botón de reintento.
  • Un nuevo intento de cobro se rechaza: 410 si venció o la expiraste tú, 409 si agotó sus intentos.
  • Se emite el webhook session.expired, con el mismo formato en los tres casos. El campo reason dice de qué manera murió.

El webhook por tiempo sale al vencer la sesión, con unos segundos de margen, aunque el comprador haya cerrado la página y nadie la vuelva a abrir.

{ "event": "session.expired", "data": { "sessionReference": "cs_8sKd2pQ1Aa", "reason": "max_failures_reached", "failureCount": 1, "maxFailures": 0 } }
reasonSignificado
expiredSe alcanzó expiresAt sin completarse.
max_failures_reachedSe agotaron los intentos permitidos.
merchant_requestLa expiraste tú con POST /v1/sessions/{reference}/expire.

Es un solo evento con un solo formato: si hoy manejas session.expired, no tienes que cambiar nada para enterarte también de los otros cierres. reason está ahí solo si quieres distinguirlos.

En el checkout

Al comprador se le avisa cuando le queda un solo intento, para que no lo gaste reintentando la misma tarjeta. Antes de eso no se le dice nada: mostrar un contador desde el principio agrega presión sin cambiar nada.

Cerrar y volver a cobrar

Una sesión cerrada no se reabre. Si quieres darle otra oportunidad al comprador, crea una sesión nueva. Es deliberado: el sentido de maxFailures es que la decisión de reintentar sea tuya y explícita, no del comprador.


Errores

HTTPCausaSolución
400Falta amountMinor/currency, o amountMinor < 1, o returnUrl no es una URL válida, o expiresInMinutes está fuera de 2–60.Revisa el cuerpo según la tabla de campos.
401Clave secreta inválida o ausente.Verifica el header Authorization.
404La sesión consultada no existe.Revisa la reference.
409El comercio aún no está aprovisionado.Contacta a soporte para activar tu comercio.

La sesión es de un solo uso. Una vez completed (o expired), genera una nueva para un nuevo cobro.

Last updated on