Referencia de la API (v1)
Cada endpoint de la API de Vipter con sus parámetros, un ejemplo de llamada y de respuesta, y la tabla de campos de cada objeto: cuenta, cliente, suscripción y sus acciones, cobro en la suscripción, uso medido (medidores, eventos de uso, ítems y períodos), pedido, oferta, producto, sesión de checkout, sesión del portal del cliente, eventos y endpoints de webhook.
Todos los endpoints están en https://api.vipter.com/v1, piden una clave de API y siguen las convenciones: dinero en enteros en la unidad mínima, fechas en segundos Unix, moneda en minúsculas, listas paginadas por cursor. Los endpoints GET piden el alcance read; los POST y DELETE piden el alcance write y aceptan una Idempotency-Key, que el cobro en la suscripción exige.
La descripción legible por máquina, en OpenAPI 3.1, está en GET https://api.vipter.com/v1/openapi.json. Sirve para generar clientes. Una colección Postman generada a partir de ella, con una carpeta por recurso y cuerpos de ejemplo, está en GET https://api.vipter.com/v1/postman.json; se importa en Postman, Bruno e Insomnia. Para agentes de IA, consulta Integrar con agentes de IA.
Los ejemplos usan IDs ficticios y montos en reales brasileños. El Authorization está acortado a vk_live_….
Cuenta
Consultar la cuenta
GET /v1/accountDevuelve la tienda y la clave que hicieron la llamada. Es la primera llamada de una integración nueva: si responde 200, la clave es correcta y la API está habilitada.
curl https://api.vipter.com/v1/account \
-H "Authorization: Bearer vk_live_…"{
"id": "a3f1c2d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
"object": "account",
"name": "Loja Demo",
"slug": "loja-demo",
"country": "BR",
"currency": "brl",
"timezone": "America/Sao_Paulo",
"api_key": {
"id": "ak_4f8e2c1a9b7d6e5f3a2b1c0d",
"name": "ERP",
"scopes": ["read"],
"default_version": "2026-11-01"
},
"api_version": "2026-11-01",
"livemode": true
}| Campo | Tipo | Contenido |
|---|---|---|
id | texto | ID de la tienda en Vipter. |
name, slug | texto | Nombre y slug de la tienda. slug es null si la tienda no tiene uno. |
country | texto | País de la tienda, ISO 3166-1 alfa-2. |
currency | texto | Moneda predeterminada de la tienda. |
timezone | texto | Zona horaria de la tienda, en formato IANA. |
api_key | objeto | La clave usada: id, name, scopes (read, write) y default_version, la versión de la API que la clave usa cuando la llamada no envía Vipter-Version. |
api_version | texto | La versión usada en esta llamada. |
livemode | booleano | true en producción. |
Clientes
Un cliente es quien compró o se suscribió en la tienda. Lo crea el checkout, el equipo en el panel o POST /v1/customers.
Listar clientes
GET /v1/customers| Parámetro | Tipo | Contenido |
|---|---|---|
email | texto | Solo clientes con ese correo, sin distinguir mayúsculas. |
limit, starting_after, ending_before | Paginación. |
curl "https://api.vipter.com/v1/customers?email=ana@example.com" \
-H "Authorization: Bearer vk_live_…"{
"object": "list",
"url": "https://api.vipter.com/v1/customers",
"has_more": false,
"data": [
{
"id": "cust_9d2e4f6a8b1c3d5e",
"object": "customer",
"email": "ana@example.com",
"name": "Ana Souza",
"phone": "+5511999990000",
"document": { "type": "cpf", "number_masked": "*******1234" },
"address": {
"line1": "Rua das Flores, 100",
"line2": "Apto 42",
"city": "São Paulo",
"state": "SP",
"postal_code": "01310-100",
"country": "BR"
},
"country": "BR",
"locale": "pt-BR",
"metadata": {},
"livemode": true,
"created": 1790186400
}
]
}Buscar un cliente
GET /v1/customers/{id}curl https://api.vipter.com/v1/customers/cust_9d2e4f6a8b1c3d5e \
-H "Authorization: Bearer vk_live_…"Devuelve el objeto customer. Un ID que no existe en la tienda recibe 404 resource_missing.
Crear un cliente
POST /v1/customersCrea el cliente en la tienda antes de la primera compra, para abrir una sesión de checkout con customer o para guardar metadata. Pide el alcance write. El proveedor de pagos exige nombre completo, teléfono y documento, por eso son obligatorios aquí.
| Parámetro | Tipo | Contenido |
|---|---|---|
email | texto | Obligatorio. Se guarda en minúsculas. |
name | texto | Obligatorio. Nombre completo, de 3 a 120 caracteres. |
phone | texto | Obligatorio. En formato internacional (+5511999990000), o un número nacional del país de la tienda. Un número que no es válido para el país recibe 400 parameter_invalid con param phone. |
document[type], document[number] | texto | Obligatorio. type es cpf, cnpj, passport o tax_id; number puede venir con puntos y guiones, que se eliminan. |
address | objeto | Dirección de facturación: line1, city, state, postal_code y country (ISO 3166-1 alfa-2) obligatorios dentro del objeto; line2, number y district opcionales. |
metadata | objeto | Datos libres, con los mismos límites del metadata de las sesiones de checkout. |
curl -X POST https://api.vipter.com/v1/customers \
-H "Authorization: Bearer vk_live_…" \
-H "Idempotency-Key: 2a7c0e4b-9d1f-4b3a-8e6c-5f2d7a1b0c9e" \
-H "Content-Type: application/json" \
-d '{
"email": "ana@example.com",
"name": "Ana Souza",
"phone": "+5511999990000",
"document": { "type": "cpf", "number": "123.456.789-09" },
"metadata": { "user_id": "user_8213" }
}'La respuesta es el objeto customer, con 201 cuando el cliente se creó. Si ya existe un cliente con ese correo en la tienda, la respuesta es 200 con el cliente existente, sin modificar ningún campo: para cambiar los datos, usa Modificar un cliente. Un cliente nuevo genera el evento customer.created.
code | HTTP | Significado |
|---|---|---|
parameter_missing, parameter_invalid | 400 | Falta un campo obligatorio o tiene el formato incorrecto. param dice cuál. |
customer_rejected | 400 | El proveedor de pagos rechazó el registro. El message trae el motivo que informó, como un documento inválido. |
Modificar un cliente
POST /v1/customers/{id}Acepta los mismos campos de Crear un cliente, todos opcionales. Solo se modifica lo que se envía, con una excepción: metadata reemplaza el mapa entero, como en Stripe. Para borrar una clave, envía el mapa sin ella. Un ID que no existe recibe 404 resource_missing antes de cualquier cambio.
curl -X POST https://api.vipter.com/v1/customers/cust_9d2e4f6a8b1c3d5e \
-H "Authorization: Bearer vk_live_…" \
-H "Content-Type: application/json" \
-d '{ "metadata": { "user_id": "user_8213", "plan": "pro" } }'Devuelve el objeto customer actualizado y genera el evento customer.updated. Los errores son los mismos de la creación.
El objeto customer
| Campo | Tipo | Contenido |
|---|---|---|
id | texto | cust_… |
object | texto | "customer" |
email | texto | Correo del cliente. Es lo que identifica a la persona en el checkout y en el portal del cliente. |
name | texto o null | Nombre indicado en el checkout. |
phone | texto o null | Teléfono en formato internacional, con + y el código del país. |
document | objeto o null | type (como cpf o cnpj) y number_masked, solo con los últimos cuatro dígitos. La API nunca devuelve el documento completo. |
address | objeto o null | Dirección de facturación: line1, line2, city, state, postal_code, country. Cada campo puede ser null. |
country | texto o null | País del cliente, ISO 3166-1 alfa-2. |
locale | texto o null | Idioma del cliente, como pt-BR, en o es. |
metadata | objeto | Los datos guardados por POST /v1/customers o POST /v1/customers/{id}. {} en los clientes creados por el checkout o por el panel. El metadata de una sesión de checkout va al pedido y a la suscripción, no al cliente. |
livemode | booleano | true en producción. |
created | entero | Cuándo se creó el cliente. |
Suscripciones
Listar suscripciones
GET /v1/subscriptions| Parámetro | Tipo | Contenido |
|---|---|---|
customer | texto | Solo suscripciones de ese cliente (cust_…). |
status | texto | Uno de trialing, active, past_due, paused, canceled, expired. Otro valor recibe 400 parameter_invalid. |
limit, starting_after, ending_before | Paginación. |
curl "https://api.vipter.com/v1/subscriptions?status=active&limit=1" \
-H "Authorization: Bearer vk_live_…"{
"object": "list",
"url": "https://api.vipter.com/v1/subscriptions",
"has_more": true,
"data": [
{
"id": "sub_3c7a9e1f5b2d8c4e",
"object": "subscription",
"status": "active",
"customer": "cust_9d2e4f6a8b1c3d5e",
"customer_email": "ana@example.com",
"offer": { "id": "ofr_6e2b8d4f1a9c3e7b", "name": "Plano Pro mensal" },
"product": { "id": "prd_1a5c9e3b7d2f6a8c", "name": "Plano Pro" },
"billing_cycle": "monthly",
"custom_billing_days": null,
"currency": "brl",
"amount": 9900,
"current_period_start": 1790186400,
"current_period_end": 1792778400,
"next_billing_at": 1792778400,
"trial_start": null,
"trial_end": null,
"cycles_completed": 1,
"cycle_limit": null,
"cancel_at_period_end": false,
"canceled_at": null,
"ended_at": null,
"cancellation_details": null,
"default_payment_method": { "id": "pm_8f3d1c7e2a5b9d4f", "type": "card" },
"installments": null,
"past_due_details": null,
"checkout_session": "cs_4b7e2d9a1c3f5e8b0d2a6c4e1f3b",
"client_reference_id": "user_8213",
"metadata": { "plan": "pro" },
"livemode": true,
"created": 1790186400
}
]
}Buscar una suscripción
GET /v1/subscriptions/{id}curl https://api.vipter.com/v1/subscriptions/sub_3c7a9e1f5b2d8c4e \
-H "Authorization: Bearer vk_live_…"Devuelve el objeto subscription.
El objeto subscription
| Campo | Tipo | Contenido |
|---|---|---|
id | texto | sub_… |
object | texto | "subscription" |
status | texto | trialing (en prueba gratis), active, past_due (el último cobro falló y Vipter está reintentando), paused, canceled, expired (llegó al límite de ciclos o al final sin renovar). |
customer | texto o null | cust_… del suscriptor. |
customer_email | texto o null | Correo del suscriptor, para no necesitar otra llamada. |
offer | objeto o null | La oferta actual: id (ofr_…) y name. |
product | objeto o null | El producto: id (prd_…) y name. |
billing_cycle | texto o null | El intervalo de cobro: daily, biweekly, monthly, quarterly, half_yearly, yearly o custom. |
custom_billing_days | entero o null | Con billing_cycle custom, el intervalo en días. |
currency | texto o null | Moneda de la suscripción. |
amount | entero o null | Monto de cada cobro, en la unidad mínima. |
current_period_start, current_period_end | entero o null | El período ya pagado. |
next_billing_at | entero o null | Cuándo está previsto el próximo cobro. null cuando no hay próximo. |
trial_start, trial_end | entero o null | El período de prueba gratis, si lo hubo. |
cycles_completed | entero | Cuántos cobros ya se hicieron. |
cycle_limit | entero o null | Cuántos cobros hace la suscripción en total, en las ofertas con número fijo de ciclos. null es sin límite. |
cancel_at_period_end | booleano | true cuando la cancelación se programó para el fin del período pagado. El status sigue active hasta entonces. |
canceled_at | entero o null | Cuándo se pidió la cancelación. |
ended_at | entero o null | Cuándo la suscripción dejó de valer. |
cancellation_details | objeto o null | reason (código del motivo), comment (texto libre) y source: quién canceló, como el panel, el portal del cliente o el proveedor. |
default_payment_method | objeto o null | La tarjeta guardada que paga las renovaciones: id y type (card). null cuando la suscripción paga por PIX u otro medio sin tarjeta guardada. |
installments | entero o null | En cuántas cuotas se hace cada cobro, cuando la oferta lo permite. |
past_due_details | objeto o null | Solo con status past_due: attempts (cuántos intentos ya fallaron), next_retry_at y since (cuándo falló el primero). |
checkout_session | texto o null | cs_… de la sesión de checkout que creó la suscripción. null en las suscripciones que vinieron de un enlace común o del panel. |
client_reference_id | texto o null | Tu identificador, copiado del client_reference_id de la sesión de checkout o guardado por POST /v1/subscriptions/{id}. |
metadata | objeto | El subscription_data[metadata] de la sesión de checkout o, cuando no se envió, el metadata de la sesión; o lo que guardaste por POST /v1/subscriptions/{id}. {} en las demás suscripciones. |
livemode | booleano | true en producción. |
created | entero | Cuándo empezó la suscripción. |
Acciones en la suscripción
Las mismas acciones que el equipo hace en la página de la suscripción en el panel, y que el suscriptor hace en el portal del cliente. Todas piden el alcance write, aceptan una Idempotency-Key y devuelven el objeto subscription ya actualizado: lee status, cancel_at_period_end, offer y next_billing_at en la respuesta en vez de esperar el webhook. El evento correspondiente sale igual que en una acción desde el panel; consulta el catálogo de eventos.
Un ID que no existe en la tienda recibe 404 resource_missing. Una acción que no encaja en el estado actual de la suscripción, como pausar una suscripción ya pausada o reanudar una que no está pausada, recibe 400 provider_error, con el motivo en el message.
Modificar una suscripción
POST /v1/subscriptions/{id}Guarda tu referencia y el metadata en la suscripción, o programa la cancelación para el fin del período pagado, como el cancel_at_period_end de Stripe.
| Parámetro | Tipo | Contenido |
|---|---|---|
client_reference_id | texto o null | Tu identificador, hasta 200 caracteres. null lo borra. |
metadata | objeto | Reemplaza el mapa entero, como en Stripe. Para borrar una clave, envía el mapa sin ella. Mismos límites que el metadata de las sesiones de checkout. |
cancel_at_period_end | booleano | true programa la cancelación: el status sigue active hasta current_period_end, y la suscripción no se renueva. false se rechaza con 400 parameter_invalid: para deshacer una cancelación programada, llama a POST …/reactivate. |
cancellation_details[reason] | texto | Con cancel_at_period_end: true: el motivo, uno de too_expensive, not_using, missing_features, switching, temporary, other. Otro valor se ignora. |
cancellation_details[comment] | texto | Con cancel_at_period_end: true: un comentario libre, hasta 500 caracteres. |
curl -X POST https://api.vipter.com/v1/subscriptions/sub_3c7a9e1f5b2d8c4e \
-H "Authorization: Bearer vk_live_…" \
-H "Content-Type: application/json" \
-d '{
"cancel_at_period_end": true,
"cancellation_details": { "reason": "switching", "comment": "Pasará al plan anual en enero." }
}'Devuelve el objeto subscription con cancel_at_period_end: true y cancellation_details con el motivo y el comentario. Cambiar client_reference_id o metadata no genera evento, y programar la cancelación tampoco: el objeto aparece con cancel_at_period_end: true en el próximo evento de la suscripción y, cuando termina el período, sale customer.subscription.deleted. Cuando la llamada trae la referencia y la programación juntas, la referencia se guarda primero; si la programación se rechaza, ya quedó guardada.
Cancelar ahora
DELETE /v1/subscriptions/{id}Cancela en el momento, sin esperar el fin del período pagado, como el DELETE de Stripe. El motivo va en la query string, con los mismos valores de Modificar una suscripción.
curl -X DELETE "https://api.vipter.com/v1/subscriptions/sub_3c7a9e1f5b2d8c4e?cancellation_details[reason]=not_using" \
-H "Authorization: Bearer vk_live_…"Devuelve el objeto subscription con status: "canceled" y genera customer.subscription.deleted (subscription.cancelled en el catálogo original). En cancellation_details, el source de una cancelación por la API viene como dashboard, el mismo valor que una cancelación por el equipo. El suscriptor recibe los mismos avisos que en una cancelación desde el panel.
Pausar y reanudar
POST /v1/subscriptions/{id}/pause
POST /v1/subscriptions/{id}/resumePausar interrumpe las renovaciones sin cancelar; reanudar vuelve a cobrar. El cuerpo de pause acepta reason, un texto libre de hasta 200 caracteres que queda en el historial de la suscripción; resume no tiene cuerpo.
curl -X POST https://api.vipter.com/v1/subscriptions/sub_3c7a9e1f5b2d8c4e/pause \
-H "Authorization: Bearer vk_live_…" \
-H "Content-Type: application/json" \
-d '{ "reason": "El cliente pidió una pausa de dos meses." }'Devuelven el objeto subscription con status: "paused" y, después, "active", y generan customer.subscription.paused y customer.subscription.resumed. Una suscripción pausada no acepta cobros sueltos.
Reactivar
POST /v1/subscriptions/{id}/reactivateDeshace una cancelación programada (cancel_at_period_end vuelve a false y la suscripción se renueva con normalidad) o reactiva una suscripción cancelada o vencida, cuando el proveedor de suscripciones lo permite; una reactivación que él rechaza recibe 400 provider_error. Sin cuerpo.
curl -X POST https://api.vipter.com/v1/subscriptions/sub_3c7a9e1f5b2d8c4e/reactivate \
-H "Authorization: Bearer vk_live_…"Devuelve el objeto subscription y genera customer.subscription.updated (subscription.reactivated en el catálogo original).
Cambiar de oferta
POST /v1/subscriptions/{id}/change_offerMueve la suscripción a otra oferta, como un upgrade del plan mensual al anual.
| Parámetro | Tipo | Contenido |
|---|---|---|
offer | texto | Obligatorio. El ofr_… de la oferta nueva. Solo el ID; el slug no se acepta. Una oferta que no existe en la tienda recibe 404 resource_missing con param offer. |
La oferta nueva tiene que pertenecer al mismo producto, o a la misma familia de productos, que la actual; de lo contrario, el cambio se rechaza con 400 provider_error.
curl -X POST https://api.vipter.com/v1/subscriptions/sub_3c7a9e1f5b2d8c4e/change_offer \
-H "Authorization: Bearer vk_live_…" \
-H "Content-Type: application/json" \
-d '{ "offer": "ofr_9a4c2e8b6d1f3a7c" }'Devuelve el objeto subscription con offer, amount, billing_cycle y next_billing_at ya de la oferta nueva, y genera customer.subscription.updated (subscription.upgraded o subscription.downgraded en el catálogo original, según el valor nuevo sea mayor o igual, o menor, que el anterior).
Cobros en la suscripción
Un cobro en la suscripción es un monto suelto cobrado ahora en la tarjeta guardada de una suscripción, fuera del ciclo: un excedente de uso, un adicional, un servicio extra. La fecha de la próxima renovación y el monto recurrente no cambian. Es el mismo cobro que el equipo hace en el panel con el botón Cobrar importe extra… de la suscripción (Cobro suelto); los dos aparecen en la misma lista, y el campo source dice de dónde vino cada uno (API o Panel en la página de la suscripción).
El cobro no le pide nada al suscriptor: Vipter cobra la tarjeta guardada sin el cliente presente, por el mismo proveedor que hizo el primer cobro de la suscripción. Cada cobro aprobado se convierte en un pedido pagado y cuenta como un pedido en la cuota del plan Vipter de la tienda. La guía para cobrar uso en un SaaS está en Cobro adicional por uso.
Cobrar la tarjeta guardada
POST /v1/subscriptions/{id}/chargesPide el alcance write y exige una Idempotency-Key: sin ella, la llamada recibe 400 idempotency_key_required antes que nada. Usa un valor que identifique el cobro que pretendes hacer, como el ID del período de uso en tu sistema. La misma clave nunca cobra dos veces: devuelve el mismo cobro, con 200 y el encabezado Idempotent-Replayed: true. A diferencia de las otras llamadas, la clave de un cobro no vence a las 24 horas: queda ligada al cobro para siempre, y repetirla meses después sigue devolviendo el mismo cobro.
| Parámetro | Tipo | Contenido |
|---|---|---|
amount | entero | Obligatorio. El total a cobrar, en la unidad mínima de la moneda (4990 son R$ 49,90). El mínimo es una unidad de la moneda (100 en brl o usd); si no, 400 amount_too_small. |
currency | texto | Opcional. Tiene que ser la moneda de la suscripción; si no, 400 currency_mismatch. Sin ella, se usa la moneda de la suscripción. |
description | texto | Hasta 140 caracteres. Es la descripción del cobro enviada al proveedor y, sin lines, el nombre del único ítem del pedido. Sin description, vale la descripción de la primera línea. |
lines[] | lista | De 1 a 50 ítems que explican el monto; se convierten en los ítems del pedido. Cada ítem: description (obligatorio, hasta 140 caracteres), quantity (entero, por defecto 1) y unit_amount (entero, en la unidad mínima). La suma de quantity × unit_amount tiene que ser igual a amount; si no, 400 lines_total_mismatch. Sin lines, el pedido tiene un solo ítem, con description y amount. |
metadata | objeto | Datos libres, con los mismos límites del metadata de las sesiones de checkout. Queda en el cobro y vuelve en los eventos subscription_charge.*. |
curl -X POST https://api.vipter.com/v1/subscriptions/sub_3c7a9e1f5b2d8c4e/charges \
-H "Authorization: Bearer vk_live_…" \
-H "Idempotency-Key: usage:user_8213:2026-09" \
-H "Content-Type: application/json" \
-d '{
"amount": 5750,
"currency": "brl",
"description": "Uso adicional de septiembre",
"lines": [
{ "description": "Llamadas más allá de la franquicia", "quantity": 2300, "unit_amount": 2 },
{ "description": "Almacenamiento adicional (GB)", "quantity": 23, "unit_amount": 50 }
],
"metadata": { "user_id": "user_8213", "period": "2026-09" }
}'La respuesta es 201 con el objeto subscription_charge:
{
"id": "sch_7e2a9c4b1d3f5a6e8b0c2d4f",
"object": "subscription_charge",
"status": "succeeded",
"subscription": "sub_3c7a9e1f5b2d8c4e",
"customer": "cust_9d2e4f6a8b1c3d5e",
"amount": 5750,
"currency": "brl",
"description": "Uso adicional de septiembre",
"lines": [
{ "description": "Llamadas más allá de la franquicia", "quantity": 2300, "unit_amount": 2, "amount": 4600 },
{ "description": "Almacenamiento adicional (GB)", "quantity": 23, "unit_amount": 50, "amount": 1150 }
],
"order": "ord_2f8c4e6a1b3d5f7e",
"transaction": "tx_9b1d3f5a7c2e4a6c",
"failure_code": null,
"failure_message": null,
"source": "api",
"metadata": { "user_id": "user_8213", "period": "2026-09" },
"livemode": true,
"created": 1790790400,
"settled_at": 1790790403
}Lo que pasa con un cobro aprobado:
- Se convierte en un pedido con
billing_reason: "manual",order_type: "api",recurrence: "unscheduled",subscriptioncompletado y laslinescomo ítems (kind: "charge"). Elsch_…del cobro queda enexternal_order_iddel pedido. - Salen los eventos
invoice.paid(con el pedido) ysubscription_charge.succeeded(con el cobro) en el catálogo 2026-11-01, yorder.paiden el original. - El suscriptor recibe el correo de confirmación de compra de la tienda, cuando está activo, y ve el pedido en el portal del cliente.
Un cobro que el proveedor deja en análisis vuelve con 201 y status: "pending", sin settled_at. Se concluye después, cuando el proveedor avisa, y entonces pasa a succeeded o failed y genera los eventos. Consúltalo por GET /v1/subscription_charges/{id} o espera subscription_charge.succeeded / subscription_charge.failed.
Tarjeta rechazada. La respuesta es 402 con type card_error, como en Stripe. El code es el código de rechazo informado por el proveedor, o declined cuando no informa ninguno; los códigos varían por proveedor, así que no dependas de una lista fija. El cobro rechazado viene entero en error.subscription_charge, con status: "failed", failure_code y failure_message:
{
"error": {
"type": "card_error",
"code": "insufficient_funds",
"message": "Saldo insuficiente.",
"doc_url": "https://docs.vipter.com/pt-br/developers/api-conventions#erros",
"subscription_charge": {
"id": "sch_7e2a9c4b1d3f5a6e8b0c2d4f",
"object": "subscription_charge",
"status": "failed",
"subscription": "sub_3c7a9e1f5b2d8c4e",
"customer": "cust_9d2e4f6a8b1c3d5e",
"amount": 5750,
"currency": "brl",
"description": "Uso adicional de septiembre",
"lines": [
{ "description": "Llamadas más allá de la franquicia", "quantity": 2300, "unit_amount": 2, "amount": 4600 },
{ "description": "Almacenamiento adicional (GB)", "quantity": 23, "unit_amount": 50, "amount": 1150 }
],
"order": "ord_2f8c4e6a1b3d5f7e",
"transaction": "tx_9b1d3f5a7c2e4a6c",
"failure_code": "insufficient_funds",
"failure_message": "Saldo insuficiente.",
"source": "api",
"metadata": { "user_id": "user_8213", "period": "2026-09" },
"livemode": true,
"created": 1790790400,
"settled_at": 1790790402
}
}
}Un rechazo genera subscription_charge.failed y, cuando el proveedor registró un pedido para el intento, invoice.payment_failed con ese pedido (status: "failed"). Vipter no reintenta por su cuenta: el cobro queda failed, y repetir la misma Idempotency-Key devuelve el mismo 402. Para cobrar otra vez, después de que el suscriptor cambie la tarjeta en el portal del cliente, por ejemplo, haz una llamada nueva con otra clave.
Errores de esta llamada, además de los errores generales:
code | HTTP | Significado |
|---|---|---|
idempotency_key_required | 400 | La llamada vino sin Idempotency-Key. El type es idempotency_error. |
resource_missing | 404 | La suscripción no existe en la tienda. |
subscription_not_chargeable | 400 | La suscripción está paused, canceled o expired. Solo active, trialing y past_due aceptan cobro. |
no_payment_method | 400 | La suscripción no tiene tarjeta guardada: paga por PIX u otro medio sin tarjeta. |
payment_method_not_chargeable | 400 | La tarjeta está guardada en un proveedor que no acepta cobros sin el cliente presente. Hoy, Mercado Pago. |
currency_mismatch | 400 | currency es distinta de la moneda de la suscripción. El message dice cuál es. |
amount_too_small | 400 | amount es menor que una unidad de la moneda. |
lines_total_mismatch | 400 | La suma de las líneas no coincide con amount. El message trae los dos valores. |
provider_error | 400 | El proveedor rechazó la solicitud de cobro antes de llegar a la tarjeta. El message trae el motivo. El cobro queda registrado como failed con esa clave; usa otra para intentar de nuevo. |
project_inactive | 403 | La tienda no puede cobrar porque la mensualidad de Vipter está atrasada. El type es permission_error. |
Listar los cobros de una suscripción
GET /v1/subscriptions/{id}/charges| Parámetro | Tipo | Contenido |
|---|---|---|
status | texto | pending, succeeded o failed. |
limit, starting_after, ending_before | Paginación. |
curl "https://api.vipter.com/v1/subscriptions/sub_3c7a9e1f5b2d8c4e/charges?status=succeeded" \
-H "Authorization: Bearer vk_live_…"Devuelve un objeto list de subscription_charge, del más nuevo al más antiguo, con los cobros hechos por la API y los hechos en el panel.
Buscar un cobro
GET /v1/subscription_charges/{id}curl https://api.vipter.com/v1/subscription_charges/sch_7e2a9c4b1d3f5a6e8b0c2d4f \
-H "Authorization: Bearer vk_live_…"Devuelve el objeto subscription_charge. Es la llamada para seguir un cobro que quedó pending.
El objeto del cobro
| Campo | Tipo | Contenido |
|---|---|---|
id | texto | sch_… |
object | texto | "subscription_charge" |
status | texto | pending (en análisis en el proveedor), succeeded (pagado) o failed (rechazado, o la solicitud de cobro fue rechazada). |
subscription | texto | sub_… de la suscripción cobrada. |
customer | texto o null | cust_… del suscriptor. |
amount | entero | El total cobrado, en la unidad mínima. |
currency | texto | La moneda, en minúsculas: siempre la de la suscripción. |
description | texto o null | La descripción enviada, o la de la primera línea. |
lines | lista | Las líneas enviadas: description, quantity, unit_amount y amount (unit_amount × quantity). [] cuando el cobro vino sin lines. |
order | texto o null | ord_… del pedido que el cobro generó. null cuando el proveedor no registró un pedido. |
transaction | texto o null | El ID de la transacción en el proveedor de pagos. |
failure_code | texto o null | Con status failed: el código de rechazo del proveedor, declined cuando no informó ninguno, o el código del error cuando la solicitud de cobro fue rechazada. |
failure_message | texto o null | Con status failed: el motivo, en el texto del proveedor. |
source | texto | De dónde vino el cobro: api (esta API), dashboard (el botón del panel) o usage (cobro por uso medido, hecho por Vipter al cierre de un período). |
metadata | objeto | Lo que enviaste. {} en los cobros hechos en el panel. |
livemode | booleano | true en producción. |
created | entero | Cuándo se pidió el cobro. |
settled_at | entero o null | Cuándo pasó a succeeded o failed. null mientras está pending. |
Uso medido
Cobro por consumo con la cuenta del lado de Vipter, en el formato de los Billing Meters de Stripe: un medidor por tipo de uso, eventos de uso por cliente, un ítem de uso que le pone precio al medidor en la suscripción, y un período por ciclo de la suscripción, que cierra y se convierte en un cobro en la suscripción con source: "usage". La guía, con ejemplos de precio y qué pasa al final del ciclo, está en Uso medido (Meters). Los GET piden el alcance read; los POST y DELETE, el alcance write.
Crear un medidor
POST /v1/billing/meters| Parámetro | Tipo | Contenido |
|---|---|---|
display_name | texto | Obligatorio. Nombre del medidor, de 1 a 250 caracteres. Aparece en la línea del pedido del comprador. |
event_name | texto | Obligatorio. El nombre que usan los eventos: solo a-z, 0-9, _, . y -, hasta 100 caracteres, único en la tienda. No cambia después. |
default_aggregation[formula] | texto | sum (suma los valores, predeterminado), count (cuenta los eventos, ignora el valor) o last (se queda con el último valor del período). |
customer_mapping[event_payload_key] | texto | La clave del payload que trae el cust_… del cliente. Predeterminado customer_id. customer_mapping[type] solo acepta by_id. |
value_settings[event_payload_key] | texto | La clave del payload que trae el valor. Predeterminado value. |
curl -X POST https://api.vipter.com/v1/billing/meters \
-H "Authorization: Bearer vk_live_…" \
-H "Content-Type: application/json" \
-d '{ "display_name": "Llamadas de API", "event_name": "api_calls", "default_aggregation": { "formula": "sum" } }'La respuesta es 201 con el objeto billing.meter. Un event_name que ya existe en la tienda recibe 400 event_name_taken.
Listar medidores
GET /v1/billing/meters| Parámetro | Tipo | Contenido |
|---|---|---|
status | texto | active o inactive. |
limit | entero | Tamaño de la página. |
Devuelve un objeto list de billing.meter, del más nuevo al más antiguo.
Buscar y modificar un medidor
GET /v1/billing/meters/{id}
POST /v1/billing/meters/{id}El POST acepta solo display_name; los demás campos del medidor no cambian. Un ID que no existe en la tienda recibe 404 resource_missing.
Desactivar un medidor
POST /v1/billing/meters/{id}/deactivateSin cuerpo. El medidor pasa a inactive con status_transitions.deactivated_at completado, y los eventos nuevos con su event_name reciben 400 meter_inactive. Los períodos abiertos siguen mostrando la cantidad del medidor, con precio cero. No hay reactivación por la API.
El objeto billing.meter
{
"id": "mtr_4f8e2c1a9b7d6e5f3a2b1c0d",
"object": "billing.meter",
"display_name": "Llamadas de API",
"event_name": "api_calls",
"default_aggregation": { "formula": "sum" },
"customer_mapping": { "type": "by_id", "event_payload_key": "customer_id" },
"value_settings": { "event_payload_key": "value" },
"status": "active",
"status_transitions": { "deactivated_at": null },
"livemode": true,
"created": 1791100800,
"updated": 1791100800
}| Campo | Tipo | Contenido |
|---|---|---|
id | texto | mtr_… |
object | texto | "billing.meter" |
display_name | texto | El nombre del medidor. |
event_name | texto | El nombre que usan los eventos. |
default_aggregation | objeto | formula: sum, count o last. |
customer_mapping | objeto | type (by_id) y event_payload_key, la clave del payload con el cliente. |
value_settings | objeto | event_payload_key, la clave del payload con el valor. |
status | texto | active o inactive. |
status_transitions | objeto | deactivated_at: cuándo se desactivó el medidor, o null. |
livemode | booleano | true en producción. |
created, updated | entero | Creación y última modificación. |
Registrar un evento de uso
POST /v1/billing/meter_events| Parámetro | Tipo | Contenido |
|---|---|---|
event_name | texto | Obligatorio. El event_name del medidor. |
payload | objeto | Obligatorio. Hasta 20 claves con valores texto, número o booleano. Tiene que traer el cliente en la clave del medidor (customer_id por defecto; stripe_customer_id se acepta como alias) y, fuera de los medidores count, el valor numérico en la clave del valor (value por defecto). subscription_id elige la suscripción cuando el cliente tiene más de una con precio para el medidor. |
identifier | texto | Hasta 100 caracteres. Idempotencia por medidor: el mismo identifier devuelve el evento ya guardado, con 200. Sin él, Vipter genera uno. |
timestamp | entero | Cuándo ocurrió el uso, en segundos Unix: hasta 35 días atrás y hasta 5 minutos adelante. Predeterminado: ahora. |
curl -X POST https://api.vipter.com/v1/billing/meter_events \
-H "Authorization: Bearer vk_live_…" \
-H "Content-Type: application/json" \
-d '{ "event_name": "api_calls", "identifier": "req_01J9X3K7M2", "payload": { "customer_id": "cust_9d2e4f6a8b1c3d5e", "value": 1 } }'La respuesta es 201 con el objeto billing.meter_event, o 200 con el evento ya guardado cuando el identifier se repite. Al llegar, el evento se vincula a la suscripción active, trialing o past_due del cliente que tiene un ítem de uso para el medidor (la más reciente, si hay más de una). Un evento sin suscripción se guarda con subscription: null, no se cobra, y dispara billing.meter.error_report_triggered, como máximo una vez por medidor y por hora. Consulta Eventos sin suscripción.
code | HTTP | Significado |
|---|---|---|
no_meter_found | 400 | Ningún medidor tiene ese event_name. |
meter_inactive | 400 | El medidor fue desactivado. |
invalid_payload | 400 | Falta el cliente, o el valor no es un número. param dice la clave (payload.customer_id, payload.value). |
timestamp_out_of_range | 400 | timestamp fuera de la ventana de 35 días atrás a 5 minutos adelante. |
resource_missing | 404 | El cliente del payload no existe en la tienda. param es la clave del cliente. |
Registrar eventos en lote
POST /v1/billing/meter_events/batch| Parámetro | Tipo | Contenido |
|---|---|---|
events[] | lista | Obligatorio. De 1 a 100 eventos, cada uno con los campos de Registrar un evento de uso. |
curl -X POST https://api.vipter.com/v1/billing/meter_events/batch \
-H "Authorization: Bearer vk_live_…" \
-H "Content-Type: application/json" \
-d '{
"events": [
{ "event_name": "api_calls", "identifier": "req_01J9X3K7M2", "payload": { "customer_id": "cust_9d2e4f6a8b1c3d5e", "value": 1 } },
{ "event_name": "api_calls", "identifier": "req_01J9X3K7M3", "payload": { "customer_id": "cust_0000000000000000", "value": 1 } }
]
}'{
"object": "billing.meter_event_batch",
"accepted": 1,
"duplicates": 0,
"errors": 1,
"results": [
{ "status": "accepted", "event": { "id": "mev_1a5c9e3b7d2f6a8c0e4b2d6f", "object": "billing.meter_event", "event_name": "api_calls", "identifier": "req_01J9X3K7M2", "payload": { "customer_id": "cust_9d2e4f6a8b1c3d5e", "value": 1 }, "customer": "cust_9d2e4f6a8b1c3d5e", "subscription": "sub_3c7a9e1f5b2d8c4e", "value": 1, "timestamp": 1791101000, "livemode": true, "created": 1791101000 } },
{ "status": "error", "error": { "code": "customer_not_found", "message": "No such customer: 'cust_0000000000000000'", "param": "payload.customer_id" } }
]
}Cada evento se acepta o se rechaza por su cuenta, en el orden enviado: results[i] responde a events[i] con status accepted (guardado ahora), duplicate (el identifier ya existía; event es el guardado) o error (error con code, message y param, los mismos códigos de la llamada unitaria, con customer_not_found en lugar de resource_missing). La respuesta es 200 siempre que al menos un evento fue aceptado o repetido, y 400 solo cuando ninguno entró. Un lote con un campo inválido en el cuerpo (como events vacío) recibe 400 parameter_invalid sin guardar nada.
El objeto billing.meter_event
| Campo | Tipo | Contenido |
|---|---|---|
id | texto | mev_… |
object | texto | "billing.meter_event" |
event_name | texto | El medidor. |
identifier | texto | Tu identifier, o el generado por Vipter. |
payload | objeto | El payload enviado. |
customer | texto | cust_… del cliente. |
subscription | texto o null | La suscripción en la que se cobrará el evento. null cuando ninguna suscripción activa del cliente tiene precio para el medidor. |
value | número | El valor del evento. 1 en los medidores count. |
timestamp | entero | Cuándo ocurrió el uso. Decide en qué período cae el evento. |
livemode | booleano | true en producción. |
created | entero | Cuándo llegó el evento. |
Consultar el uso agregado de un cliente
GET /v1/billing/meters/{id}/event_summaries| Parámetro | Tipo | Contenido |
|---|---|---|
customer | texto | Obligatorio. El cust_…. |
start_time, end_time | entero | Obligatorios. El intervalo, en segundos Unix: start_time inclusive, end_time exclusive. end_time tiene que ser mayor, y el intervalo puede abarcar hasta un año, si no 400 parameter_invalid. |
value_grouping_window | texto | hour o day: un resumen por hora o por día (en UTC), solo de los intervalos que tienen eventos. Sin él, un solo resumen. |
curl "https://api.vipter.com/v1/billing/meters/mtr_4f8e2c1a9b7d6e5f3a2b1c0d/event_summaries?customer=cust_9d2e4f6a8b1c3d5e&start_time=1790186400&end_time=1792778400&value_grouping_window=day" \
-H "Authorization: Bearer vk_live_…"{
"object": "list",
"url": "https://api.vipter.com/v1/billing/meters/mtr_4f8e2c1a9b7d6e5f3a2b1c0d/event_summaries",
"has_more": false,
"data": [
{ "object": "billing.meter_event_summary", "meter": "mtr_4f8e2c1a9b7d6e5f3a2b1c0d", "customer": "cust_9d2e4f6a8b1c3d5e", "start_time": 1790186400, "end_time": 1790208000, "aggregated_value": 412, "livemode": true },
{ "object": "billing.meter_event_summary", "meter": "mtr_4f8e2c1a9b7d6e5f3a2b1c0d", "customer": "cust_9d2e4f6a8b1c3d5e", "start_time": 1790208000, "end_time": 1790294400, "aggregated_value": 1180, "livemode": true }
]
}aggregated_value aplica la fórmula del medidor (sum, count o last) a los eventos del cliente en el intervalo, vinculados o no a una suscripción. La lista no está paginada.
Ítems de uso de una suscripción
GET /v1/subscriptions/{id}/usage_items
POST /v1/subscriptions/{id}/usage_items
DELETE /v1/subscriptions/{id}/usage_items/{itemId}Un ítem de uso le pone precio a un medidor en una suscripción: un ítem por medidor. Nace de la oferta (configurado en el panel, en la página de la oferta) en la primera corrida del uso medido después de crearse la suscripción, en hasta 10 minutos, con source: "offer", o se define aquí, con source: "api". Un ítem definido por la API nunca es sobrescrito por la herencia de la oferta. El POST del mismo meter reemplaza el ítem.
| Parámetro | Tipo | Contenido |
|---|---|---|
meter | texto | Obligatorio. El mtr_…. Un medidor que no existe recibe 404 resource_missing. |
currency | texto | Opcional. Tiene que ser la moneda de la suscripción, si no 400 currency_mismatch. |
unit_amount | número | Obligatorio. Precio de una unidad, en la menor unidad de la moneda, con fracciones: 0.4 es R$ 0,004. 0 o más. Con tiers, no entra en el cálculo. |
included_units | número | Franquicia: unidades del período que no se cobran. Predeterminado 0. |
tiers[] | lista | De 1 a 20 tramos graduados sobre las unidades más allá de la franquicia, cada uno con up_to (límite superior, inclusive; null en el último), unit_amount (por unidad en el tramo) y flat_amount (opcional, cobrado una vez cuando se usa el tramo). up_to tiene que ser creciente y el último null, si no 400 parameter_invalid con param tiers. |
rounding | texto | up (hacia arriba, predeterminado) o nearest (más cercano), aplicado al total de la línea, en centavos enteros. |
billing_threshold | entero | En centavos. El período cierra y cobra antes del fin del ciclo cuando el total acumulado lo alcanza. |
label | texto | Hasta 120 caracteres, para tu control. |
curl -X POST https://api.vipter.com/v1/subscriptions/sub_3c7a9e1f5b2d8c4e/usage_items \
-H "Authorization: Bearer vk_live_…" \
-H "Content-Type: application/json" \
-d '{ "meter": "mtr_4f8e2c1a9b7d6e5f3a2b1c0d", "unit_amount": 0.4, "included_units": 10000, "billing_threshold": 20000 }'El POST responde 201 con el objeto usage_item; el GET devuelve un objeto list de usage_item, sin paginación; el DELETE devuelve { "id": "usi_…", "object": "usage_item", "deleted": true }, o 404 resource_missing si el ítem no es de esa suscripción.
El objeto usage_item
{
"id": "usi_9d2e4f6a8b1c3d5e7f0a2b4c",
"object": "usage_item",
"meter": "mtr_4f8e2c1a9b7d6e5f3a2b1c0d",
"subscription": "sub_3c7a9e1f5b2d8c4e",
"offer": null,
"currency": "brl",
"unit_amount": 0.4,
"included_units": 10000,
"tiers": null,
"rounding": "up",
"billing_threshold": 20000,
"label": null,
"source": "api",
"livemode": true,
"created": 1791100900
}| Campo | Tipo | Contenido |
|---|---|---|
id | texto | usi_… |
object | texto | "usage_item" |
meter | texto | mtr_… del medidor. |
subscription | texto | sub_… de la suscripción. |
offer | null | Reservado. En los ítems de una suscripción viene siempre null. |
currency | texto | La moneda, en minúsculas: la de la suscripción. |
unit_amount, included_units, tiers, rounding, billing_threshold, label | Como se enviaron. tiers y billing_threshold vienen null cuando no hay. | |
source | texto | offer (heredado de la oferta), api o dashboard. |
livemode | booleano | true en producción. |
created | entero | Cuándo se creó el ítem. |
Consultar los períodos de uso
GET /v1/subscriptions/{id}/usagecurl https://api.vipter.com/v1/subscriptions/sub_3c7a9e1f5b2d8c4e/usage \
-H "Authorization: Bearer vk_live_…"Devuelve un objeto list con los 12 períodos más recientes de la suscripción, del más nuevo al más antiguo, sin paginación. El período open se calcula en el momento, a partir de los eventos; los closed vienen como quedaron al cierre. Una suscripción sin ítems de uso devuelve una lista vacía.
El objeto usage_period
{
"id": "usp_7b3e9f1c2a8d4e6f0b2d4f6a",
"object": "usage_period",
"subscription": "sub_3c7a9e1f5b2d8c4e",
"status": "closed",
"close_reason": "period_end",
"period_start": 1790186400,
"period_end": 1792778400,
"currency": "brl",
"lines": [
{ "meter": "mtr_4f8e2c1a9b7d6e5f3a2b1c0d", "event_name": "api_calls", "quantity": 12300, "included": 10000, "billable": 2300, "unit_amount": 0.4, "amount": 920 }
],
"amount_total": 920,
"charge": "sch_7e2a9c4b1d3f5a6e8b0c2d4f",
"computed_at": 1792778700,
"closed_at": 1792778700,
"livemode": true,
"created": 1790187000
}| Campo | Tipo | Contenido |
|---|---|---|
id | texto | usp_… |
object | texto | "usage_period" |
subscription | texto | sub_… de la suscripción. |
status | texto | open (acumulando), closing (en cobro) o closed. |
close_reason | texto o null | period_end (terminó el ciclo), threshold (alcanzó el billing_threshold) o subscription_ended (la suscripción dejó de ser cobrable). null mientras está abierto. |
period_start, period_end | entero | El intervalo del período: el ciclo de la suscripción, o el tramo que queda después de un cierre por umbral. |
currency | texto | La moneda, en minúsculas. |
lines | lista | Una por medidor con ítem: meter, event_name, quantity (el agregado), included (la franquicia), billable (quantity menos included), unit_amount y amount (en centavos). |
amount_total | entero | La suma de las líneas, en centavos. |
charge | texto o null | sch_… del cobro del período, aprobado o rechazado. null mientras está abierto, cuando el total fue cero, o cuando el cobro fue rechazado antes de llegar a la tarjeta. |
computed_at | entero o null | Cuándo se calcularon las líneas. En el período abierto, la hora de la llamada. |
closed_at | entero o null | Cuándo cerró el período. |
livemode | booleano | true en producción. |
created | entero | Cuándo abrió el período. |
Errores del uso medido
Además de los errores generales:
code | HTTP | Dónde | Significado |
|---|---|---|---|
event_name_taken | 400 | Crear un medidor | Ya existe un medidor con ese event_name. |
no_meter_found | 400 | Eventos | Ningún medidor tiene ese event_name. |
meter_inactive | 400 | Eventos | El medidor fue desactivado. |
invalid_payload | 400 | Eventos | Falta el cliente en el payload, o el valor no es un número. |
timestamp_out_of_range | 400 | Eventos | timestamp fuera de la ventana de 35 días atrás a 5 minutos adelante. |
customer_not_found | Lote | Solo dentro de results[]: el cliente no existe. En la llamada unitaria es 404 resource_missing. | |
currency_mismatch | 400 | Ítems de uso | currency es distinta de la moneda de la suscripción. |
parameter_invalid | 400 | Ítems de uso, resúmenes | tiers fuera de orden o sin el último tramo null; end_time antes de start_time o intervalo mayor a un año. |
resource_missing | 404 | Todos | Medidor, suscripción, ítem o cliente que no existe en la tienda. |
Pedidos
Un pedido es un cobro: una compra suelta, el primer cobro de una suscripción, una renovación, un cobro suelto en la suscripción hecho en el panel o por la API. Es lo que Stripe llama factura (invoice); el nombre sigue lo que el dueño de la tienda ve en el panel.
Listar pedidos
GET /v1/orders| Parámetro | Tipo | Contenido |
|---|---|---|
customer | texto | Solo pedidos de ese cliente (cust_…). |
subscription | texto | Solo pedidos de esa suscripción (sub_…). |
status | texto | Uno de pending, pre_authorized, authorized, failed, canceled, refund_pending, partially_refunded, refunded, charged_back. |
limit, starting_after, ending_before | Paginación. |
curl "https://api.vipter.com/v1/orders?subscription=sub_3c7a9e1f5b2d8c4e&limit=1" \
-H "Authorization: Bearer vk_live_…"{
"object": "list",
"url": "https://api.vipter.com/v1/orders",
"has_more": true,
"data": [
{
"id": "ord_7b3e9f1c2a8d4e6f",
"object": "order",
"status": "authorized",
"paid": true,
"status_source": "provider",
"billing_reason": "subscription_cycle",
"customer": "cust_9d2e4f6a8b1c3d5e",
"customer_email": "ana@example.com",
"subscription": "sub_3c7a9e1f5b2d8c4e",
"offer": "ofr_6e2b8d4f1a9c3e7b",
"order_type": "renewal",
"recurrence": "subsequent",
"currency": "brl",
"amount_total": 9900,
"amount_refunded": 0,
"amount_discount": 0,
"amount_shipping": 0,
"amount_interest": 0,
"installments": 1,
"payment_method": "credit_card",
"provider": "pagarme",
"coupon_codes": [],
"lines": [
{
"description": "Plano Pro mensal",
"quantity": 1,
"unit_amount": 9900,
"amount": 9900,
"offer": "ofr_6e2b8d4f1a9c3e7b",
"product": "prd_1a5c9e3b7d2f6a8c",
"kind": "main"
}
],
"shipping": null,
"external_order_id": null,
"checkout_session": null,
"client_reference_id": null,
"metadata": {},
"paid_at": 1790790412,
"livemode": true,
"created": 1790790400
}
]
}Buscar un pedido
GET /v1/orders/{id}curl https://api.vipter.com/v1/orders/ord_7b3e9f1c2a8d4e6f \
-H "Authorization: Bearer vk_live_…"Devuelve el objeto order.
El objeto order
| Campo | Tipo | Contenido |
|---|---|---|
id | texto | ord_… |
object | texto | "order" |
status | texto | Consulta Estado del pedido. |
paid | booleano | true cuando el dinero entró, aunque después se haya reembolsado total o parcialmente. Usa status para el detalle. |
status_source | texto | provider cuando el estado vino del proveedor de pagos; manual cuando alguien definió el estado en el panel. |
billing_reason | texto | Por qué existe el pedido: purchase (compra suelta), subscription_create (primer cobro de una suscripción), subscription_cycle (renovación), manual (cobro suelto en una suscripción, por el panel o por la API), usage (cobro por uso medido, al cierre de un período). |
customer, customer_email | texto o null | El comprador. |
subscription | texto o null | sub_… cuando el pedido pertenece a una suscripción. |
offer | texto o null | ofr_… de la oferta principal del pedido. |
order_type | texto o null | Cómo nació el pedido: checkout, renewal (renovación), api (cobro iniciado por el dueño de la tienda), trial_setup o card_setup (registro de tarjeta sin cobro). Puede ganar valores nuevos. |
recurrence | texto o null | initial, subsequent o unscheduled en los pedidos de suscripción; null en las compras sueltas. |
currency | texto | Moneda del pedido. |
amount_total | entero | El total cobrado, en la unidad mínima, con descuento, envío e intereses ya aplicados. |
amount_refunded | entero | Cuánto ya se reembolsó. |
amount_discount | entero | El descuento de cupones. |
amount_shipping | entero | El envío, en productos físicos. |
amount_interest | entero | Los intereses de las cuotas trasladados al comprador. |
installments | entero o null | En cuántas cuotas se pagó. |
payment_method | texto o null | credit_card, debit_card, pix, boleto o wallet. |
provider | texto o null | El proveedor que procesó, como pagarme, stripe, mercadopago o asaas. |
coupon_codes | lista de texto | Los cupones aplicados. |
lines | lista | Los ítems del pedido. Consulta Líneas del pedido. |
shipping | objeto o null | Solo en pedidos con entrega: status de la entrega, carrier, tracking_code, tracking_url y address, en el mismo formato que la dirección del cliente. |
external_order_id | texto o null | Un identificador tuyo, cuando el pedido vino con uno. En los pedidos de un cobro en la suscripción, el sch_… del cobro. |
checkout_session | texto o null | cs_… de la sesión de checkout que generó el pedido. Solo en el pedido pagado por la sesión: las renovaciones de la suscripción vienen con null, y las conectas con tu sistema por subscription. |
client_reference_id | texto o null | Tu identificador, copiado del client_reference_id de la sesión de checkout. |
metadata | objeto | El metadata de la sesión de checkout. {} en los demás pedidos. |
paid_at | entero o null | Cuándo se confirmó el pago. |
livemode | booleano | false cuando el pago pasó por una conexión de prueba del proveedor. |
created | entero | Cuándo se creó el pedido. |
Estado del pedido
status | paid | Significado |
|---|---|---|
pending | false | Esperando el pago: PIX generado y no pagado, boleto emitido, tarjeta en análisis. |
pre_authorized | false | Monto reservado en la tarjeta, todavía no capturado. |
authorized | true | Pagado. |
failed | false | El pago fue rechazado o venció. |
canceled | false | Cancelado antes de ser pagado. |
refund_pending | true | Reembolso pedido y todavía no confirmado por el proveedor. |
partially_refunded | true | Parte del monto fue devuelta. amount_refunded dice cuánto. |
refunded | true | Todo el monto fue devuelto. |
charged_back | true | El comprador disputó el cobro en el banco. |
Líneas del pedido
Cada ítem de lines:
| Campo | Tipo | Contenido |
|---|---|---|
description | texto o null | El nombre del ítem como apareció en el checkout. |
quantity | entero | Cantidad. |
unit_amount | entero o null | Precio unitario, en la unidad mínima. |
amount | entero o null | unit_amount por quantity. |
offer, product | texto o null | ofr_… y prd_… del ítem. |
kind | texto | El papel del ítem en el pedido: main (el ítem principal), bump (oferta adicional marcada en el checkout), composition (línea compuesta por el equipo en un enlace rápido) o charge (línea de un cobro en la suscripción). |
Ofertas
Una oferta es lo que el comprador puede pagar: un producto con precio, ciclo de cobro y condiciones. Es el equivalente al price de Stripe. Cada oferta tiene un enlace de checkout listo.
Listar ofertas
GET /v1/offers| Parámetro | Tipo | Contenido |
|---|---|---|
product | texto | Solo ofertas de ese producto (prd_…). |
active | true o false | Solo ofertas activas, o solo las que no están activas. |
type | texto | one_time o recurring. |
limit, starting_after, ending_before | Paginación. Las ofertas vienen en orden alfabético por nombre. |
curl "https://api.vipter.com/v1/offers?active=true&type=recurring" \
-H "Authorization: Bearer vk_live_…"{
"object": "list",
"url": "https://api.vipter.com/v1/offers",
"has_more": false,
"data": [
{
"id": "ofr_6e2b8d4f1a9c3e7b",
"object": "offer",
"name": "Plano Pro mensal",
"slug": "pro-mensal",
"product": { "id": "prd_1a5c9e3b7d2f6a8c", "name": "Plano Pro" },
"type": "recurring",
"billing_cycle": "monthly",
"custom_billing_days": null,
"cycle_limit": null,
"trial_days": 7,
"setup_charge": false,
"status": "active",
"active": true,
"prices": [
{ "id": "prc_2d8f4a6c1e9b3d7f", "currency": "brl", "unit_amount": 9900, "first_charge_amount": null, "default": true },
{ "id": "prc_7a1c3e5b9d2f4a6c", "currency": "usd", "unit_amount": 1900, "first_charge_amount": null, "default": false }
],
"checkout_url": "https://pay.vipter.com/pro-mensal",
"livemode": true,
"created": 1788300000
}
]
}Buscar una oferta
GET /v1/offers/{id}curl https://api.vipter.com/v1/offers/ofr_6e2b8d4f1a9c3e7b \
-H "Authorization: Bearer vk_live_…"Devuelve el objeto offer.
El objeto offer
| Campo | Tipo | Contenido |
|---|---|---|
id | texto | ofr_… |
object | texto | "offer" |
name | texto | Nombre de la oferta. |
slug | texto o null | El slug del enlace de checkout, cuando la oferta tiene uno. |
product | objeto | id (prd_…) y name del producto. |
type | texto | one_time (compra suelta) o recurring (suscripción). |
billing_cycle | texto o null | El intervalo de cobro en las ofertas recurrentes: daily, biweekly, monthly, quarterly, half_yearly, yearly o custom. null en las sueltas. |
custom_billing_days | entero o null | Con billing_cycle custom, el intervalo en días. |
cycle_limit | entero o null | Cuántos cobros hace la suscripción en total. null es sin límite. |
trial_days | entero o null | Días de prueba gratis. null cuando la oferta no tiene prueba. |
setup_charge | booleano | true cuando el primer cobro tiene un monto distinto a los demás (first_charge_amount en prices). |
status | texto | El estado en el catálogo, como active o inactive. |
active | booleano | true cuando status es active. Solo las ofertas activas aceptan compras. |
prices | lista | Un precio por moneda: id, currency, unit_amount, first_charge_amount (el monto del primer cobro cuando es distinto, si no null) y default (la moneda que el checkout usa cuando el comprador no elige). |
checkout_url | texto o null | El enlace de checkout de la oferta, en el dominio propio de la tienda cuando hay uno. null cuando el enlace está desactivado en la configuración de la oferta. Acepta los parámetros de URL. |
livemode | booleano | true en producción. |
created | entero o null | Cuándo se creó la oferta. |
Productos
Un producto agrupa ofertas: "Plan Pro" es el producto, "Plan Pro mensual" y "Plan Pro anual" son ofertas suyas.
Listar productos
GET /v1/products| Parámetro | Tipo | Contenido |
|---|---|---|
active | true o false | Solo productos activos, o solo los que no están activos. |
limit, starting_after, ending_before | Paginación. |
curl "https://api.vipter.com/v1/products?active=true" \
-H "Authorization: Bearer vk_live_…"{
"object": "list",
"url": "https://api.vipter.com/v1/products",
"has_more": false,
"data": [
{
"id": "prd_1a5c9e3b7d2f6a8c",
"object": "product",
"name": "Plano Pro",
"description": "Acesso completo à plataforma.",
"type": "digital",
"status": "active",
"active": true,
"product_family": null,
"metadata": {},
"livemode": true,
"created": 1788200000
}
]
}Buscar un producto
GET /v1/products/{id}curl https://api.vipter.com/v1/products/prd_1a5c9e3b7d2f6a8c \
-H "Authorization: Bearer vk_live_…"Devuelve el objeto product.
El objeto product
| Campo | Tipo | Contenido |
|---|---|---|
id | texto | prd_… |
object | texto | "product" |
name | texto | Nombre del producto. |
description | texto o null | Descripción. |
type | texto o null | El tipo de producto, como digital o physical. Puede ganar valores nuevos. |
status | texto | El estado en el catálogo, como active o inactive. |
active | booleano | true cuando el producto está activo y no fue eliminado. |
product_family | texto o null | El ID de la familia de productos, cuando el producto pertenece a una. |
metadata | objeto | Datos libres guardados en el producto. |
livemode | booleano | true en producción. |
created | entero o null | Cuándo se creó el producto. |
Sesiones de checkout
Una sesión de checkout es un checkout abierto por tu sistema para una oferta, con el comprador, tu referencia y el destino después del pago ya definidos. La respuesta trae la url a la que mandas al comprador. Cuando paga, la sesión pasa a apuntar a customer, order y subscription, y el evento checkout.session.completed sale en el catálogo 2026-11-01. Es el equivalente de la Checkout Session de Stripe. El paso a paso con código está en SaaS: del registro al dashboard.
La sesión fija la oferta, el paquete, la moneda y el cupón: el comprador no cambia ninguno de ellos en la página. Con customer o customer_email, el campo de correo viene completado y bloqueado. Crear la sesión no crea nada en el proveedor de pagos; eso pasa cuando el comprador completa el formulario, como en un enlace de checkout común.
Crear una sesión
POST /v1/checkout/sessionsPide el alcance write. Envía una Idempotency-Key para repetir la llamada después de un error de red sin crear dos sesiones.
| Parámetro | Tipo | Contenido |
|---|---|---|
offer | texto | Obligatorio, salvo que venga line_items. La oferta: ofr_… o el slug del enlace de checkout. Una oferta que no existe o es de otra tienda recibe 404 resource_missing con param offer. |
line_items[0][price], line_items[0][quantity] | texto, entero | Alias en el formato de Stripe: price es la oferta y quantity es el paquete. Solo un ítem. Cuando offer o pack también vienen, ellos valen. |
pack | entero | El paquete, en unidades (1 a 999). La oferta tiene que tener un paquete con esa cantidad, si no 400 pack_unavailable. |
currency | texto | Moneda de la sesión, ISO 4217 (brl, usd). Tiene que existir en prices de la oferta, si no 400 currency_unsupported, con las monedas disponibles en el message. Sin ella, el checkout abre en la moneda predeterminada de la oferta y el comprador puede cambiarla, como en un enlace común. |
customer | texto | cust_… de un cliente de la tienda. Su correo viene completado y bloqueado; nombre, teléfono y documento vienen completados. Un ID que no existe recibe 404 resource_missing con param customer. |
customer_email | texto | Correo del comprador, cuando todavía no es cliente. El campo viene completado y bloqueado: el checkout solo acepta pagar con ese correo. Se guarda en minúsculas. |
customer_name | texto | Nombre para completar el formulario, de 2 a 120 caracteres. El comprador puede cambiarlo. |
client_reference_id | texto | Tu identificador, hasta 200 caracteres: el ID del usuario en tu sistema. Se copia al pedido y a la suscripción, y filtra la lista de sesiones. |
metadata | objeto | Hasta 50 claves; clave de hasta 40 caracteres, valor de hasta 500. Los números y booleanos se guardan como texto; null elimina la clave. Queda en la sesión y se copia al pedido. |
subscription_data[metadata] | objeto | El metadata de la suscripción que la sesión cree, en las ofertas recurrentes. Sin él, la suscripción recibe el metadata de la sesión. Mismos límites. |
discounts[0][coupon] | texto | El código de un cupón activo de la tienda, sin distinguir mayúsculas. Viene aplicado en el checkout. Un cupón inexistente o inactivo recibe 400 coupon_invalid. Solo un cupón. |
success_url | texto | Adónde va el comprador después de pagar. https://, hasta 2000 caracteres (http://localhost se acepta en desarrollo). El texto {CHECKOUT_SESSION_ID} se reemplaza por el id de la sesión. Consulta Después del pago. |
cancel_url | texto | Se convierte en el enlace de volver arriba del checkout. También es adónde va el comprador si abre la sesión después de que venció. Mismas reglas de formato. |
redirect_delay | entero | Segundos que la página de gracias de Vipter queda en pantalla antes de ir a success_url: de 0 a 30, predeterminado 5. |
expires_at | entero | Cuándo vence la sesión, en segundos Unix: entre 30 minutos y 24 horas a partir de ahora, si no 400 parameter_invalid. Predeterminado: 24 horas. |
locale | texto | Idioma del comprador, guardado en la sesión y devuelto en el objeto: en, pt, es, fr, de, it, ja, ko, ru o zh. |
mode | texto | payment o subscription. Opcional: Vipter lo deduce por el tipo de la oferta y lo devuelve en el objeto. Un valor distinto del tipo de la oferta recibe 400 mode_mismatch. |
allow_promotion_codes | booleano | Aceptado por compatibilidad con Stripe e ignorado por ahora. |
curl -X POST https://api.vipter.com/v1/checkout/sessions \
-H "Authorization: Bearer vk_live_…" \
-H "Idempotency-Key: 9c1f0a52-7e4b-4d3a-9b8e-2f6c1d0a7e45" \
-H "Content-Type: application/json" \
-d '{
"offer": "ofr_6e2b8d4f1a9c3e7b",
"customer_email": "ana@example.com",
"client_reference_id": "user_8213",
"metadata": { "plan": "pro" },
"success_url": "https://app.example.com/billing/success?session_id={CHECKOUT_SESSION_ID}",
"cancel_url": "https://app.example.com/billing"
}'La respuesta es 201 con el objeto checkout.session:
{
"id": "cs_4b7e2d9a1c3f5e8b0d2a6c4e1f3b",
"object": "checkout.session",
"status": "open",
"payment_status": "unpaid",
"url": "https://pay.vipter.com/c/cs_4b7e2d9a1c3f5e8b0d2a6c4e1f3b",
"mode": "subscription",
"offer": { "id": "ofr_6e2b8d4f1a9c3e7b", "name": "Plano Pro mensal" },
"pack": null,
"currency": null,
"amount_total": 9900,
"customer": null,
"customer_email": "ana@example.com",
"customer_name": null,
"client_reference_id": "user_8213",
"metadata": { "plan": "pro" },
"subscription_data": { "metadata": {} },
"discounts": [],
"success_url": "https://app.example.com/billing/success?session_id={CHECKOUT_SESSION_ID}",
"cancel_url": "https://app.example.com/billing",
"redirect_delay": 5,
"locale": null,
"order": null,
"subscription": null,
"expires_at": 1790272800,
"completed_at": null,
"livemode": true,
"created": 1790186400
}Manda al comprador a url. La dirección está en https://pay.vipter.com/c/{id} o, cuando la tienda tiene un dominio propio activo, en ese dominio.
Errores de esta llamada, además de los errores generales:
code | HTTP | Significado |
|---|---|---|
resource_missing | 404 | La oferta (param offer) o el cliente (param customer) no existe en la tienda. |
offer_unavailable | 400 | La oferta existe pero no se puede vender ahora: enlace de checkout desactivado, oferta archivada, sin precio, o la tienda no está en condiciones de vender. El message dice el motivo. |
pack_unavailable | 400 | La oferta no tiene un paquete con la cantidad pedida en pack. |
currency_unsupported | 400 | La oferta no tiene precio en la moneda pedida. |
mode_mismatch | 400 | mode no coincide con el tipo de la oferta. |
coupon_invalid | 400 | El cupón no existe o está inactivo. param es discounts[0].coupon. |
selling_blocked | 403 | La tienda no puede vender porque la mensualidad de Vipter está atrasada. El type es permission_error. |
Buscar una sesión
GET /v1/checkout/sessions/{id}curl https://api.vipter.com/v1/checkout/sessions/cs_4b7e2d9a1c3f5e8b0d2a6c4e1f3b \
-H "Authorization: Bearer vk_live_…"Devuelve el objeto checkout.session. Es la llamada que tu página de éxito hace con el id que llegó en la success_url: revisa status y payment_status en lugar de confiar solo en la redirección. Después del pago, customer, order y, en las ofertas recurrentes, subscription vienen completados. subscription puede llegar unos segundos después de order, cuando se procesa el aviso del proveedor; si todavía viene null, consulta de nuevo o espera el webhook.
Listar sesiones
GET /v1/checkout/sessions| Parámetro | Tipo | Contenido |
|---|---|---|
customer | texto | Solo sesiones de ese cliente (cust_…), incluidas las que ganaron el cliente al pagarse. |
client_reference_id | texto | Solo sesiones creadas con ese client_reference_id, coincidencia exacta. |
status | texto | open, complete o expired. |
payment_status | texto | unpaid, paid o pending. |
limit, starting_after, ending_before | Paginación. |
curl "https://api.vipter.com/v1/checkout/sessions?client_reference_id=user_8213&status=complete" \
-H "Authorization: Bearer vk_live_…"Devuelve un objeto list de checkout.session, de la más nueva a la más antigua.
Expirar una sesión
POST /v1/checkout/sessions/{id}/expireCierra una sesión abierta antes del plazo: el comprador que abra la url después de eso va a cancel_url, o ve una página de enlace no disponible. Útil cuando el usuario desistió en tu sistema o eligió otro plan. Pide el alcance write.
curl -X POST https://api.vipter.com/v1/checkout/sessions/cs_4b7e2d9a1c3f5e8b0d2a6c4e1f3b/expire \
-H "Authorization: Bearer vk_live_…"Devuelve el objeto con status: "expired" y genera el evento checkout.session.expired. Una sesión que ya está complete o expired recibe 400 checkout_session_not_open, con el estado actual en el message.
El objeto checkout.session
| Campo | Tipo | Contenido |
|---|---|---|
id | texto | cs_… |
object | texto | "checkout.session" |
status | texto | open, complete o expired. Consulta Estado de la sesión. |
payment_status | texto | unpaid, paid o pending. |
url | texto o null | La dirección del checkout. Solo mientras status es open; después viene null. |
mode | texto o null | payment en una oferta única, subscription en una recurrente. |
offer | objeto | id (ofr_…) y name de la oferta. |
pack | entero o null | El paquete fijado, en unidades. |
currency | texto o null | La moneda fijada al crear. null cuando la sesión dejó elegir al comprador. |
amount_total | entero o null | El precio de la oferta en la moneda de la sesión (o en la predeterminada), en la unidad mínima: el monto del primer cobro, cuando es distinto. Viene antes de cupón, envío y adicionales, y null en las sesiones con paquete. El monto cobrado de hecho está en amount_total del pedido. |
customer | texto o null | cust_…: el que pasaste, o el cliente creado cuando el comprador pagó. |
customer_email, customer_name | texto o null | Lo que pasaste; con customer, el correo y el nombre del cliente. |
client_reference_id | texto o null | Tu identificador. |
metadata | objeto | Lo que enviaste. |
subscription_data | objeto | metadata: lo que enviaste en subscription_data[metadata], o {}. |
discounts | lista | [{ "coupon": "CODIGO" }] cuando la sesión tiene cupón; si no []. |
success_url, cancel_url | texto o null | Como los enviaste, con el {CHECKOUT_SESSION_ID} todavía sin reemplazar. |
redirect_delay | entero | Segundos antes de la redirección, de 0 a 30. |
locale | texto o null | El idioma enviado. |
order | texto o null | ord_… del pedido que generó la sesión. Se completa cuando el comprador paga o genera un PIX. |
subscription | texto o null | sub_… de la suscripción creada, en las ofertas recurrentes. |
expires_at | entero | Cuándo vence la sesión. |
completed_at | entero o null | Cuándo la sesión pasó a complete. |
livemode | booleano | true en producción. |
created | entero | Cuándo se creó la sesión. |
Estado de la sesión
status | payment_status | Significado |
|---|---|---|
open | unpaid | El comprador todavía no pagó. Una tarjeta rechazada deja la sesión abierta: puede intentar de nuevo en la misma página. |
complete | paid | Pagado: tarjeta aprobada, o PIX pagado. order está completado. |
complete | pending | El comprador generó un PIX, o la tarjeta quedó en análisis, y el dinero todavía no entró. order está completado con status: "pending". La sesión queda así hasta que el pago se confirme (paid) o falle (unpaid). |
complete | unpaid | El pago pendiente no ocurrió: el PIX venció o el cobro fue rechazado después del análisis. La sesión no se reabre; crea otra. |
expired | unpaid | El plazo pasó sin pago, o alguien llamó a POST …/expire. |
Cada cambio genera un evento del catálogo 2026-11-01: checkout.session.completed cuando la sesión pasa a complete (con paid o pending), checkout.session.async_payment_succeeded cuando un pago pendiente se confirma, checkout.session.async_payment_failed cuando falla, y checkout.session.expired. Una sesión solo se completa una vez.
Después del pago
- El comprador paga en la página de Vipter y ve la página de gracias de la tienda.
- Con el pago confirmado, la página cuenta
redirect_delaysegundos y va asuccess_url, con{CHECKOUT_SESSION_ID}reemplazado por elidde la sesión. Mientras un PIX no se paga, la página queda esperando y no redirige. - La
success_urlde la sesión tiene prioridad sobre la URL de éxito del enlace rápido, la de la oferta y la predeterminada de la tienda. Sinsuccess_url, vale la siguiente de la lista.
Cuando el producto tiene archivos para descargar, la página no redirige sola: muestra los archivos y un botón para seguir a la success_url.
Un comprador que abre la url de una sesión ya pagada es llevado a la página de gracias del pedido. Una sesión vencida lleva a cancel_url o, sin ella, a una página de enlace no disponible. Las sesiones vencidas se marcan como expired por una rutina periódica, y también en el momento, si alguien abre el enlace.
Portal del cliente
Crear una sesión del portal
POST /v1/billing_portal/sessionsGenera un enlace de entrada directa al portal del cliente de la tienda para un cliente, sin el paso del código por correo. Sirve para el botón "administrar suscripción" de tu sistema: el cliente cambia la tarjeta, cancela o ve los cobros en el portal de Vipter. Pide el alcance write.
| Parámetro | Tipo | Contenido |
|---|---|---|
customer | texto | Obligatorio. cust_… del cliente. |
return_url | texto | Aceptado por compatibilidad con Stripe y devuelto en el objeto. El portal todavía no tiene un botón para volver a él. |
curl -X POST https://api.vipter.com/v1/billing_portal/sessions \
-H "Authorization: Bearer vk_live_…" \
-H "Content-Type: application/json" \
-d '{ "customer": "cust_9d2e4f6a8b1c3d5e" }'{
"id": "bps_3e9a1c7f5b2d4e6a8c0b",
"object": "billing_portal.session",
"url": "https://portal.vipter.com/loja-demo/verify?token=EJEMPLO-TOKEN",
"customer": "cust_9d2e4f6a8b1c3d5e",
"return_url": null,
"expires_at": 1790187300,
"livemode": true,
"created": 1790186400
}| Campo | Tipo | Contenido |
|---|---|---|
id | texto | bps_…. La sesión no se puede consultar después. |
object | texto | "billing_portal.session" |
url | texto | El enlace de entrada. Vale por 15 minutos y sirve para una entrada: al abrirse, crea la sesión del portal en el navegador del cliente y deja de funcionar. Genera un enlace nuevo en cada clic, en el momento del clic, y redirige al cliente a él; no lo guardes ni lo envíes por correo. |
customer | texto | El cliente. |
return_url | texto o null | Lo que enviaste. |
expires_at | entero | Cuándo deja de valer el enlace. |
livemode | booleano | true en producción. |
created | entero | Cuándo se creó la sesión. |
El enlace abre en la dirección del portal de la tienda: el dominio propio, cuando hay uno activo, o portal.vipter.com/{slug}.
code | HTTP | Significado |
|---|---|---|
resource_missing | 404 | El cliente no existe en la tienda. |
portal_disabled | 400 | El portal del cliente está desactivado en la configuración de la tienda. Consulta Portal del cliente. |
portal_unavailable | 400 | La tienda todavía no tiene una dirección de portal: falta un slug o un dominio propio activo. |
Eventos
Todo evento que Vipter genera queda guardado y se puede consultar por la API, en los dos catálogos: sirve para revisar qué recibió tu endpoint, recuperar lo que no recibió mientras estuvo desactivado y pedir un reenvío por código. El cuerpo de cada evento es el mismo sobre que llega al endpoint, y api_version dice de qué catálogo es. Los eventos de prueba no aparecen en la lista.
Listar eventos
GET /v1/events| Parámetro | Tipo | Contenido |
|---|---|---|
type | texto | Un tipo exacto, como invoice.paid, o un patrón con *, como invoice.* o customer.subscription.*. |
created[gte], created[gt], created[lte], created[lt] | entero | Solo eventos creados a partir de, después de, hasta o antes de ese momento, en segundos Unix. |
limit, starting_after, ending_before | Paginación. El cursor es el id de un evento. |
curl "https://api.vipter.com/v1/events?type=invoice.*&created[gte]=1790726400" \
-H "Authorization: Bearer vk_live_…"{
"object": "list",
"url": "https://api.vipter.com/v1/events",
"has_more": false,
"data": [
{
"id": "evt_5a7c9e1b3d5f7a9c1e3b5d7f",
"object": "event",
"type": "invoice.paid",
"api_version": "2026-11-01",
"created": 1790790413,
"livemode": true,
"pending_webhooks": 0,
"request": { "id": null, "idempotency_key": null },
"data": {
"object": { "id": "ord_7b3e9f1c2a8d4e6f", "object": "order", "status": "authorized", "paid": true, "…": "…" }
}
}
]
}La lista viene del más nuevo al más antiguo y junta los dos catálogos: el mismo hecho aparece dos veces cuando la tienda tiene endpoints en las dos versiones, una con cada nombre. Un evento de la versión 2026-09-01 viene sin pending_webhooks ni request, como en el sobre de esa versión.
Buscar un evento
GET /v1/events/{id}curl https://api.vipter.com/v1/events/evt_5a7c9e1b3d5f7a9c1e3b5d7f \
-H "Authorization: Bearer vk_live_…"Devuelve el objeto event, el mismo JSON que el endpoint recibió en el POST. En la versión 2026-11-01, pending_webhooks se calcula en el momento de la consulta: cuántas entregas del evento todavía no funcionaron. Un evento de prueba se puede buscar por el id que devuelve la llamada de prueba. Un id que no existe en la tienda recibe 404 resource_missing.
Reenviar un evento
POST /v1/events/{id}/resendVuelve a poner el evento en la cola de entrega, con el mismo id y el mismo created, y lo envía en el momento. Pide el alcance write.
| Parámetro | Tipo | Contenido |
|---|---|---|
webhook_endpoint | texto | El id de un endpoint. Con él, solo ese endpoint recibe el evento: su entrega vuelve a pending con los intentos en cero, o se crea si el endpoint nunca tuvo una, como un endpoint creado después del evento o que estaba desactivado cuando salió. Sin él, se reenvía toda entrega que el evento ya tiene, incluso las que ya habían funcionado. |
curl -X POST https://api.vipter.com/v1/events/evt_5a7c9e1b3d5f7a9c1e3b5d7f/resend \
-H "Authorization: Bearer vk_live_…" \
-H "Content-Type: application/json" \
-d '{ "webhook_endpoint": "0b7e2c4a-9d1f-4e3b-8a6c-5f2d1e0c9b8a" }'{
"object": "event_resend",
"event": "evt_5a7c9e1b3d5f7a9c1e3b5d7f",
"webhook_endpoint": "0b7e2c4a-9d1f-4e3b-8a6c-5f2d1e0c9b8a",
"deliveries": 1
}deliveries es cuántas entregas se pusieron en la cola. Sin webhook_endpoint, es el número de endpoints que ya tenían una entrega del evento; 0 cuando ninguno la tenía. El endpoint tiene que recibir el catálogo del evento: un evento invoice.paid (2026-11-01) no se puede enviar a un endpoint 2026-09-01. Reenviar a un endpoint desactivado cierra la entrega con endpoint disabled; actívalo antes. Si la entrega falla, sigue el calendario de reintentos desde el principio. Tu control de idempotencia trata el reenvío como repetición.
code | HTTP | Significado |
|---|---|---|
resource_missing | 404 | El evento, o el endpoint en webhook_endpoint, no existe en la tienda. |
invalid_event_type | 400 | El endpoint recibe otro catálogo. El message dice cuál es el del evento y cuál es el del endpoint. |
Endpoints de webhook
Los mismos endpoints de la página GeneralIntegracionesAutomatizacionesWebhooks del panel, creados y administrados por código. Un endpoint creado por la API aparece en el panel como cualquier otro, y las reglas son las de Recibir eventos en tu sistema: URL https://, un catálogo por endpoint, reintentos y desactivación automática. El id de un endpoint es un UUID; trátalo como texto opaco.
Listar endpoints
GET /v1/webhook_endpointscurl https://api.vipter.com/v1/webhook_endpoints \
-H "Authorization: Bearer vk_live_…"Devuelve un objeto list con todos los endpoints de la tienda, del más antiguo al más nuevo, sin filtros. El secret no viene en la lista.
Crear un endpoint
POST /v1/webhook_endpointsPide el alcance write. La respuesta es 201 y trae el secret (whsec_…) una sola vez: guárdalo en tu servidor para verificar la firma. Después, solo rotar el secreto genera otro.
| Parámetro | Tipo | Contenido |
|---|---|---|
url | texto | Obligatorio. Dirección https:// de tu servidor, hasta 2000 caracteres, en un host público. Un host de la red interna (localhost, 10.x, 192.168.x, nombres .internal o .local) recibe 400 private_host; una dirección sin https:// recibe 400 invalid_url. |
description | texto | Un recordatorio para el equipo, hasta 200 caracteres. |
enabled_events | lista | Los tipos que recibe el endpoint, hasta 100: nombres del catálogo de la versión, como invoice.paid, o patrones con *, como customer.subscription.*. Omitida, vacía o ["*"], el endpoint recibe todos los eventos del catálogo, incluso los que se creen en el futuro. Un nombre que no existe en la versión recibe 400 invalid_event_type. |
api_version | texto | El catálogo que recibe el endpoint: 2026-11-01 (predeterminado) o 2026-09-01. No cambia después de creado. |
curl -X POST https://api.vipter.com/v1/webhook_endpoints \
-H "Authorization: Bearer vk_live_…" \
-H "Content-Type: application/json" \
-d '{
"url": "https://app.example.com/webhooks/vipter",
"description": "Facturación del SaaS",
"enabled_events": ["checkout.session.*", "customer.subscription.*", "invoice.paid"]
}'{
"id": "0b7e2c4a-9d1f-4e3b-8a6c-5f2d1e0c9b8a",
"object": "webhook_endpoint",
"url": "https://app.example.com/webhooks/vipter",
"description": "Facturación del SaaS",
"enabled_events": ["checkout.session.*", "customer.subscription.*", "invoice.paid"],
"api_version": "2026-11-01",
"status": "enabled",
"disabled_reason": null,
"consecutive_failures": 0,
"created_via": "api",
"secret": "whsec_EJEMPLO0000000000000000000000000000",
"livemode": true,
"created": 1790186400
}El endpoint nace activo y empieza a recibir los eventos creados a partir de ahí; los eventos anteriores no le llegan, salvo por un reenvío con webhook_endpoint.
Buscar un endpoint
GET /v1/webhook_endpoints/{id}curl https://api.vipter.com/v1/webhook_endpoints/0b7e2c4a-9d1f-4e3b-8a6c-5f2d1e0c9b8a \
-H "Authorization: Bearer vk_live_…"Devuelve el objeto webhook_endpoint, sin secret. Un id que no existe en la tienda recibe 404 resource_missing.
Modificar un endpoint
POST /v1/webhook_endpoints/{id}Pide el alcance write. Solo cambian los campos enviados.
| Parámetro | Tipo | Contenido |
|---|---|---|
url | texto | La dirección nueva, con las mismas reglas de la creación. Las entregas siguientes ya van a ella. |
description | texto o null | null borra la descripción. |
enabled_events | lista | Reemplaza la lista entera. ["*"] vuelve a recibir todos los eventos del catálogo. Desde el panel esta lista no se puede cambiar; por la API, sí. |
status | texto | enabled o disabled. Es el interruptor Activo del panel: enabled pone consecutive_failures en cero y borra disabled_reason, lo que vuelve a encender un endpoint desactivado automáticamente. Mientras está disabled, los eventos nuevos no se guardan para él. |
Un api_version en el cuerpo se ignora: la versión no cambia. Para pasar al otro catálogo, crea un endpoint nuevo y quita el anterior.
curl -X POST https://api.vipter.com/v1/webhook_endpoints/0b7e2c4a-9d1f-4e3b-8a6c-5f2d1e0c9b8a \
-H "Authorization: Bearer vk_live_…" \
-H "Content-Type: application/json" \
-d '{ "enabled_events": ["*"], "status": "enabled" }'Devuelve el objeto webhook_endpoint actualizado.
Quitar un endpoint
DELETE /v1/webhook_endpoints/{id}Borra el endpoint, el secreto y su historial de entregas; las entregas pendientes se detienen. Pide el alcance write.
{ "id": "0b7e2c4a-9d1f-4e3b-8a6c-5f2d1e0c9b8a", "object": "webhook_endpoint", "deleted": true }Rotar el secreto
POST /v1/webhook_endpoints/{id}/rotate_secretGenera un secreto nuevo y devuelve el objeto webhook_endpoint con él en secret, una sola vez. El secreto anterior sigue firmando por 24 horas: en ese período, cada entrega trae dos v1= en el encabezado Vipter-Signature, y tu servidor puede cambiar la variable de entorno sin perder eventos. Consulta Rotar el secreto. Pide el alcance write.
curl -X POST https://api.vipter.com/v1/webhook_endpoints/0b7e2c4a-9d1f-4e3b-8a6c-5f2d1e0c9b8a/rotate_secret \
-H "Authorization: Bearer vk_live_…"Enviar un evento de prueba
POST /v1/webhook_endpoints/{id}/testCrea un evento customer.created de prueba y lo entrega solo a este endpoint, en su catálogo: un objeto customer en el formato de la versión del endpoint, con "test": true de más y livemode: false. Es lo mismo que el botón Enviar evento de prueba de la tarjeta del endpoint en el panel. El evento no pasa por automatizaciones, correos, facturas, áreas de miembros ni píxeles, no llega a los otros endpoints y no aparece en GET /v1/events. El formato está en el catálogo. Pide el alcance write.
curl -X POST https://api.vipter.com/v1/webhook_endpoints/0b7e2c4a-9d1f-4e3b-8a6c-5f2d1e0c9b8a/test \
-H "Authorization: Bearer vk_live_…"{
"object": "webhook_test",
"webhook_endpoint": "0b7e2c4a-9d1f-4e3b-8a6c-5f2d1e0c9b8a",
"event": "evt_1c3e5a7b9d0f2e4c6a8b0d2f"
}La entrega sale en el momento. Síguela en GET /v1/webhook_endpoints/{id}/deliveries o en Entregas recientes en el panel. Si tu servidor falla, sigue el calendario de reintentos de un evento real y cuenta para la desactivación automática. Con el endpoint desactivado, la entrega termina en el momento con endpoint disabled.
Listar las entregas de un endpoint
GET /v1/webhook_endpoints/{id}/deliveries| Parámetro | Tipo | Contenido |
|---|---|---|
status | texto | pending, delivering, succeeded, failed o exhausted. |
limit, starting_after, ending_before | Paginación. El cursor es el id de una entrega. |
curl "https://api.vipter.com/v1/webhook_endpoints/0b7e2c4a-9d1f-4e3b-8a6c-5f2d1e0c9b8a/deliveries?status=failed" \
-H "Authorization: Bearer vk_live_…"{
"object": "list",
"url": "https://api.vipter.com/v1/webhook_endpoints/0b7e2c4a-9d1f-4e3b-8a6c-5f2d1e0c9b8a/deliveries",
"has_more": false,
"data": [
{
"id": "6f1d2c3b-4a5e-4f6a-9b8c-7d6e5f4a3b2c",
"object": "webhook_delivery",
"event": "evt_5a7c9e1b3d5f7a9c1e3b5d7f",
"webhook_endpoint": "0b7e2c4a-9d1f-4e3b-8a6c-5f2d1e0c9b8a",
"status": "failed",
"attempts": 2,
"next_attempt_at": 1790790780,
"last_attempt_at": 1790790480,
"delivered_at": null,
"last_response_status": 500,
"last_error": "HTTP 500",
"last_duration_ms": 184,
"created": 1790790413
}
]
}Devuelve un objeto list de webhook_delivery, de la más nueva a la más antigua: una entrega por evento que el endpoint recibió. Lo que significa cada estado está en Reintentos, desactivación y reenvío.
El objeto del endpoint
| Campo | Tipo | Contenido |
|---|---|---|
id | texto | UUID del endpoint. |
object | texto | "webhook_endpoint" |
url | texto | La dirección que recibe los eventos. |
description | texto o null | La descripción. |
enabled_events | lista | Los tipos y patrones que recibe el endpoint. ["*"] cuando recibe todos los eventos del catálogo. |
api_version | texto | 2026-11-01 o 2026-09-01: el catálogo que recibe el endpoint. |
status | texto | enabled o disabled. |
disabled_reason | texto o null | El motivo, cuando Vipter desactivó el endpoint por su cuenta: auto-disabled after 20 exhausted deliveries. null en los demás casos, incluso cuando el equipo apagó el interruptor. |
consecutive_failures | entero | Entregas seguidas que agotaron los intentos. En 20, el endpoint se desactiva; una entrega succeeded lo pone en cero. |
created_via | texto | dashboard o api: dónde se creó el endpoint. |
secret | texto | El secreto de firma, whsec_…. Solo en la respuesta de crear y de rotar el secreto. |
livemode | booleano | true en producción. |
created | entero | Cuándo se creó el endpoint. |
El objeto de la entrega
| Campo | Tipo | Contenido |
|---|---|---|
id | texto | UUID de la entrega. |
object | texto | "webhook_delivery" |
event | texto | evt_… del evento entregado. Búscalo en GET /v1/events/{id}. |
webhook_endpoint | texto | El endpoint. |
status | texto | pending (en cola), delivering (enviándose), succeeded (tu servidor respondió 2xx), failed (el último intento falló y hay otro programado) o exhausted (se acabaron los intentos, o el endpoint estaba desactivado). |
attempts | entero | Cuántos intentos ya se hicieron, de hasta 8. Vuelve a 0 en un reenvío. |
next_attempt_at | entero o null | Cuándo está programado el próximo intento. Solo con status pending o failed. |
last_attempt_at | entero o null | Cuándo fue el último intento. |
delivered_at | entero o null | Cuándo tu servidor respondió 2xx. |
last_response_status | entero o null | El código HTTP de la última respuesta. null cuando no hubo respuesta. |
last_error | texto o null | El error del último intento: HTTP 500, timeout, endpoint disabled o el mensaje de red. null cuando funcionó. |
last_duration_ms | entero o null | Cuánto tardó el último intento, en milisegundos. |
created | entero | Cuándo se creó la entrega. |
Próximamente
Lo que todavía no existe en la API: un filtro por client_reference_id en GET /v1/subscriptions y en GET /v1/customers. Para encontrar la suscripción de un usuario tuyo, usa el campo subscription de la sesión de checkout que la creó, o guarda el sub_… que llega en el webhook. El uso medido, antes listado aquí, ya está disponible: consulta Uso medido.
Qué hacer después
- Sigue la guía SaaS: del registro al dashboard, con código para crear la sesión, recibir el webhook y cobrar el uso a fin de mes.
- Lee las convenciones de la API antes de escribir el cliente: errores, paginación, límites.
- Para cobrar por consumo sin mantener la cuenta de tu lado, sigue Uso medido (Meters).
- Para enterarte de una venta al instante, en vez de consultar la API, recibe webhooks. Crea el endpoint por código en Endpoints de webhook y revisa qué llegó en Eventos.
Convenciones de la API
La dirección base, el encabezado de versión, el formato de solicitud y respuesta, cómo se representan dinero, fechas e IDs, la tabla de errores, idempotencia, paginación, límites de solicitudes, el encabezado Request-Id y qué cambia respecto a Stripe.
SaaS: del registro al dashboard
Guía con código para cobrarle a un usuario de tu SaaS por Vipter: crear la sesión de checkout con el ID del usuario, redirigir, confirmar el pago por la API o por el webhook checkout.session.completed, tratar el PIX, la idempotencia, cobrar el uso del mes en la tarjeta guardada, cancelar y cambiar de plan, qué guardar y cómo convivir con Stripe.