VipterCentro de Ayuda

Uso medido (Meters)

Cómo cobrar por consumo dejando la cuenta con Vipter, en el formato de los Billing Meters de Stripe: crear un medidor, ponerle precio al uso en la oferta o en la suscripción, enviar eventos de uso, leer el período abierto y qué pasa cuando cierra el ciclo, con ejemplos en curl y Node.js, franquicia, tramos de precio, umbral de cobro y las diferencias con Stripe.

Admin o PropietarioTodos los planes

Con el uso medido, tu sistema solo le avisa a Vipter de cada consumo: una llamada, un GB, un envío. Vipter suma, aplica la franquicia y el precio que definiste, y cobra el total en la tarjeta guardada de la suscripción cuando cierra el ciclo. Es la alternativa al cobro adicional por uso, en el que tú calculas el monto y llamas a POST /v1/subscriptions/{id}/charges.

Los endpoints siguen el formato de los Billing Meters de Stripe (billing.meter, billing.meter_event, event_summaries). Lo que cambia está en Diferencias con Stripe. Cada endpoint, con todos los parámetros, está en la referencia de la API.

Cuándo usar

QuieresUsa
Mantener la cuenta en tu sistema y decidir cuándo y cuánto cobrar.Cobro en la tarjeta guardada: POST /v1/subscriptions/{id}/charges con el amount ya calculado.
Solo mandar los eventos y dejar que Vipter sume, aplique la franquicia, el precio y los tramos, y cobre al final del ciclo.Esta página.
Cobrar en el momento, en cada consumo.Cobro en la tarjeta guardada. El uso medido no tiene modo inmediato: acumula y cobra al cierre, o al alcanzar un umbral.

Los dos caminos generan el mismo tipo de cobro (subscription_charge) y el mismo pedido pagado. Una suscripción puede usar los dos.

El modelo

medidor (meter)  →  eventos de uso (meter_event)  →  ítem de uso (usage_item)  →  período (usage_period)  →  cobro (subscription_charge)
  • Medidor. Un tipo de consumo, identificado por un event_name (api_calls, storage_gb). Dice cómo se agregan los eventos: sum suma los valores, count cuenta los eventos, last se queda con el último valor.
  • Evento de uso. Un registro con el cliente (payload.customer_id) y el valor (payload.value). Al llegar, Vipter vincula el evento a la suscripción activa del cliente que tiene precio para ese medidor.
  • Ítem de uso. El precio de un medidor en una suscripción: monto por unidad, franquicia, tramos, redondeo y umbral. Nace de la oferta (configurado en el panel) o se define por la API en la suscripción.
  • Período. El ciclo actual de la suscripción (current_period_start → current_period_end). Mientras está abierto, el uso se recalcula a partir de los eventos en cada corrida. Cuando cierra, se convierte en un cobro.
  • Cobro. Un cobro en la suscripción con source: "usage", hecho una vez por período, con una línea por medidor. Se convierte en un pedido pagado con billing_reason: "usage".

Paso 1: crear el medidor

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" }
  }'
{
  "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
}

El event_name es lo que usan los eventos para encontrar el medidor: solo minúsculas, dígitos, _, . y -, hasta 100 caracteres, único en la tienda (repetirlo recibe 400 event_name_taken). No cambia después; el display_name cambia por POST /v1/billing/meters/{id} y es el nombre que aparece en la línea del pedido del comprador. Si tu sistema ya manda eventos a Stripe con stripe_customer_id en el payload, déjalo: Vipter acepta esa clave como alias de customer_id.

Paso 2: ponerle precio al uso

El precio vive en un ítem de uso, uno por medidor. Hay dos lugares para él:

En la oferta, por el panel. En la página de la oferta, la tarjeta de uso medido lista los medidores de la tienda y acepta el precio por unidad, la franquicia, los tramos, el redondeo y el umbral, por moneda. Toda suscripción de esa oferta hereda el ítem en la primera corrida del uso medido después de ser creada (la corrida ocurre cada 10 minutos). La herencia copia la fila de la moneda de la suscripción o, si no hay, la de la moneda de la tienda. Es una copia: cambiar el precio en la oferta después no altera las suscripciones que ya heredaron.

En la suscripción, por la API. Para un precio negociado o para una suscripción que no vino de una oferta con uso medido:

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,
    "rounding": "up",
    "label": "Llamadas más allá de la franquicia"
  }'
{
  "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": null,
  "label": "Llamadas más allá de la franquicia",
  "source": "api",
  "livemode": true,
  "created": 1791100900
}

unit_amount es el precio de una unidad, en la menor unidad de la moneda, con fracciones: 0.4 es R$ 0,004 por llamada; 50 es R$ 0,50 por GB. La moneda es siempre la de la suscripción (otra recibe 400 currency_mismatch). Enviar de nuevo el mismo meter reemplaza el ítem; un ítem que definiste por la API nunca es sobrescrito por la herencia de la oferta. DELETE /v1/subscriptions/{id}/usage_items/{itemId} lo quita.

Paso 3: enviar los eventos

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 }
  }'
{
  "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
}
  • identifier es la idempotencia del evento, por medidor. Repetir el mismo identifier devuelve el evento ya guardado, con 200, sin contar dos veces. Usa el ID de la solicitud, del job o del registro en tu base de datos. Sin identifier, Vipter genera uno, y cada llamada cuenta.
  • payload.value es el valor sumado (sum) o guardado (last). En un medidor count, el valor se ignora y cada evento vale 1.
  • timestamp es opcional, en segundos Unix: hasta 35 días atrás y hasta 5 minutos adelante, si no 400 timestamp_out_of_range. Sin él, vale la hora de llegada. El timestamp decide en qué período cae el evento.
  • subscription en la respuesta dice a qué se vinculó el evento. null es un evento sin suscripción.

En volumen, usa el lote: hasta 100 eventos por llamada en POST /v1/billing/meter_events/batch. Cada ítem se acepta o se rechaza por su cuenta, y results[i] responde a events[i] con status accepted, duplicate o error. La respuesta es 200 siempre que al menos un ítem entró; 400 solo cuando ninguno entró.

usage-events.mjs
const API = 'https://api.vipter.com/v1';
const headers = { Authorization: `Bearer ${process.env.VIPTER_API_KEY}`, 'Content-Type': 'application/json' };

// Un evento por consumo. `identifier` es el ID del registro de tu lado: reenviar nunca cuenta dos veces.
export async function reportUsage(record) {
  const res = await fetch(`${API}/billing/meter_events`, {
    method: 'POST',
    headers,
    body: JSON.stringify({
      event_name: 'api_calls',
      identifier: record.id,
      timestamp: Math.floor(record.at.getTime() / 1000), // opcional; hasta 35 días atrás
      payload: { customer_id: record.vipterCustomerId, value: record.calls },
    }),
  });
  const json = await res.json();
  if (res.ok) return json; // 201 nuevo, 200 repetido
  // 400 con error.code: no_meter_found, meter_inactive, invalid_payload, timestamp_out_of_range. 404: el cliente no existe.
  throw Object.assign(new Error(json.error.message), { code: json.error.code, status: res.status });
}

// En lote: hasta 100 por llamada. Trata `results` ítem por ítem; `duplicate` es normal en un reenvío.
export async function reportUsageBatch(records) {
  const res = await fetch(`${API}/billing/meter_events/batch`, {
    method: 'POST',
    headers,
    body: JSON.stringify({
      events: records.map((r) => ({ event_name: 'api_calls', identifier: r.id, payload: { customer_id: r.vipterCustomerId, value: r.calls } })),
    }),
  });
  const json = await res.json(); // { accepted, duplicates, errors, results[] }
  json.results.forEach((r, i) => {
    if (r.status === 'error') console.warn('evento rechazado', records[i].id, r.error.code, r.error.message);
  });
  return json;
}

Guarda el cust_… de cada usuario cuando nace la suscripción: viene en customer de la sesión de checkout y de la suscripción. Un customer_id que no existe en la tienda recibe 404 resource_missing.

Paso 4: leer el uso del período

curl https://api.vipter.com/v1/subscriptions/sub_3c7a9e1f5b2d8c4e/usage \
  -H "Authorization: Bearer vk_live_…"
{
  "object": "list",
  "url": "https://api.vipter.com/v1/subscriptions/sub_3c7a9e1f5b2d8c4e/usage",
  "has_more": false,
  "data": [
    {
      "id": "usp_7b3e9f1c2a8d4e6f0b2d4f6a",
      "object": "usage_period",
      "subscription": "sub_3c7a9e1f5b2d8c4e",
      "status": "open",
      "close_reason": null,
      "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": null,
      "computed_at": 1791101100,
      "closed_at": null,
      "livemode": true,
      "created": 1790187000
    }
  ]
}

La lista trae los 12 períodos más recientes, del más nuevo al más antiguo. El período abierto se calcula en el momento de la llamada, a partir de los eventos; los cerrados vienen como quedaron al cierre, con charge apuntando al cobro. Cada línea es un medidor: quantity es el agregado, included la franquicia, billable lo que la superó, amount el monto de la línea en centavos. Usa esta llamada para mostrar el consumo del mes en tu producto, en vez de sumar de tu lado.

Para el agregado de un cliente en cualquier intervalo, incluso por hora o por día, usa GET /v1/billing/meters/{id}/event_summaries?customer=&start_time=&end_time=&value_grouping_window=day. Suma todos los eventos del cliente en ese medidor, vinculados o no a una suscripción.

Qué pasa al final del ciclo

Una rutina corre cada 10 minutos y, para cada suscripción con ítems de uso:

  1. Abre el período del ciclo actual, si todavía no existe: de current_period_start a current_period_end de la suscripción. Solo las suscripciones active, trialing o past_due tienen período abierto.
  2. Recalcula el período abierto a partir de los eventos y guarda lines y amount_total.
  3. Cierra el período cuando termina el ciclo (close_reason: "period_end"), cuando el monto acumulado alcanza el umbral (threshold) o cuando la suscripción deja de ser cobrable (subscription_ended).
  4. Cobra el período cerrado con amount_total mayor que cero, una sola vez, en la tarjeta guardada de la suscripción: un cobro en la suscripción con source: "usage", Idempotency-Key usage:{id del período}, descripción Uso 2026-09-23 a 2026-10-23 y una línea por medidor con monto, con el display_name del medidor. Un período con monto cero cierra sin cobro y sin pedido.

El cobro sigue las reglas de los cobros en la suscripción:

  • Aprobado: salen invoice.paid, con el pedido (billing_reason: "usage", external_order_id con el sch_…), y subscription_charge.succeeded, con el cobro (metadata.kind: "usage" y metadata.usage_period con el usp_…). El período queda closed con charge completado.
  • Rechazado por la tarjeta: salen subscription_charge.failed y, cuando el proveedor registró un pedido, invoice.payment_failed. Vipter no reintenta por su cuenta; el período queda cerrado con charge apuntando al cobro rechazado. Para cobrar el monto después de que el suscriptor cambie la tarjeta, usa POST /v1/subscriptions/{id}/charges con el amount_total del período.
  • En análisis: el cobro queda pending y los eventos salen cuando el proveedor decide.
  • Rechazado antes de llegar a la tarjeta (suscripción sin tarjeta guardada, tarjeta en un proveedor que no acepta cobros sin el cliente, monto menor a una unidad de la moneda, tienda con la mensualidad atrasada): el período cierra con charge: null y el uso no se cobra después. En subscription_ended el uso ya consumido se cobra igual en la tarjeta guardada (es la factura final de la suscripción); solo queda sin cobro si la tarjeta ya no está disponible.

La renovación de la suscripción no cambia: la mensualidad se sigue cobrando en la fecha y por el monto de la oferta, y el uso viene en un cobro separado. Cada cobro de uso aprobado cuenta como un pedido pagado en la cuota del plan Vipter de la tienda; los eventos de uso no cuentan.

Ejemplos de precio

Los cálculos de abajo valen para una línea. billable es el agregado menos included_units; el precio se aplica solo a billable. El redondeo ocurre una vez, en el total de la línea, a centavos enteros: up redondea hacia arriba (predeterminado), nearest al más cercano.

Precio fijo con franquicia

{ "meter": "mtr_…", "unit_amount": 0.4, "included_units": 10000 }

12.300 llamadas: billable 2.300 × 0,4 = 920 centavos, R$ 9,20. 9.000 llamadas: billable 0, línea con amount 0.

Tramos graduados

{
  "meter": "mtr_…",
  "unit_amount": 0,
  "included_units": 0,
  "tiers": [
    { "up_to": 1000, "unit_amount": 0 },
    { "up_to": 10000, "unit_amount": 0.5 },
    { "up_to": null, "unit_amount": 0.3 }
  ]
}

Cada tramo cubre las unidades entre el up_to anterior y el suyo, y el último tiene que tener up_to: null. Con tiers, el unit_amount del ítem no entra en el cálculo (envía 0). 25.000 unidades: 1.000 × 0 + 9.000 × 0,5 + 15.000 × 0,3 = 4.500 + 4.500 = 9.000 centavos, R$ 90,00. Un tramo puede tener flat_amount, en centavos, cobrado una vez cuando alguna unidad cae en él. Los tramos cuentan a partir de billable: con included_units: 1000, el primer tramo empieza en la unidad 1.001 del consumo.

Redondeo

unit_amount: 0.4 y 23 unidades: 9,2 centavos. Con rounding: "up", la línea vale 10 centavos; con nearest, 9. El redondeo es por línea; el total del período es la suma de las líneas ya redondeadas.

Agregaciones

sum suma value (GB transferidos). count cuenta eventos e ignora value (llamadas, envíos). last se queda con el value del evento de timestamp más reciente del período (asientos activos, GB almacenados): manda el total actual, no la diferencia.

Umbral de cobro

billing_threshold, en centavos, cierra y cobra el período antes del fin del ciclo cuando el monto acumulado lo alcanza:

{ "meter": "mtr_…", "unit_amount": 0.4, "included_units": 10000, "billing_threshold": 20000 }

Cuando amount_total llega a R$ 200,00, el período cierra con close_reason: "threshold", se cobra, y un período nuevo abre desde ese momento hasta el fin del ciclo. La verificación ocurre en cada corrida, así que el cobro puede pasarse un poco del umbral. Con varios ítems en la suscripción, vale el menor umbral entre ellos, sobre el total del período. Cada cierre es un cobro y un pedido.

Eventos sin suscripción

Al llegar, el evento se vincula a la suscripción del cliente que está active, trialing o past_due y tiene un ítem de uso para el medidor; con más de una, a la más reciente. payload.subscription_id elige una de ellas; si la elegida no tiene ítem para el medidor, el evento queda sin suscripción. La vinculación se hace una vez: un evento que llegó antes de que la suscripción tuviera el ítem no se cobra después.

Un evento sin suscripción se guarda (subscription: null), aparece en event_summaries y no se cobra. Mientras esto ocurre, Vipter manda billing.meter.error_report_triggered en el catálogo 2026-11-01, como máximo una vez por medidor y por hora, con reason.error_count eventos sin suscripción en la hora actual. Las causas más comunes:

  • La suscripción es nueva y todavía no heredó el ítem de la oferta: la herencia ocurre en la corrida siguiente, en hasta 10 minutos. Empieza a mandar eventos después de que GET /v1/subscriptions/{id}/usage_items devuelva el ítem, o crea el ítem por la API en el momento.
  • El ítem existe solo en la oferta anterior: una suscripción hereda una vez; después de un cambio de oferta, revisa los ítems.
  • El customer_id es de otro cliente, o la suscripción paga por PIX y no tiene tarjeta guardada (recibe el evento, pero el cobro falla al cierre).

Desactivar un medidor

POST /v1/billing/meters/{id}/deactivate deja de aceptar eventos (400 meter_inactive) en el momento. Los períodos abiertos siguen mostrando la quantity del medidor, pero el precio pasa a cero. Los ítems de uso que apuntan a él siguen existiendo; quítalos si ya no quieres verlos. No hay reactivación por la API.

Límites

QuéLímite
timestamp del eventoHasta 35 días atrás y 5 minutos adelante.
Eventos por lote1 a 100.
payloadHasta 20 claves; valores texto, número o booleano.
event_name, identifierHasta 100 caracteres. event_name: a-z, 0-9, _, ., -.
display_name, labelHasta 250 y 120 caracteres.
tiers1 a 20 tramos, up_to creciente, el último null.
Intervalo de event_summariesHasta un año.
GET …/usageLos 12 períodos más recientes.
LlamadasLos límites de solicitudes de la API.

Lo que ve el comprador

El cobro del período se convierte en un pedido con la descripción Uso <inicio> a <fin> y una línea por medidor, con el display_name del medidor y el monto de la línea. El suscriptor ve el pedido en el portal del cliente, junto a las renovaciones, y recibe el correo de confirmación de compra de la tienda, cuando está activo. En el panel, el cobro aparece en la página de la suscripción con el origen Uso, y la página de la suscripción muestra el uso del período abierto.

Si tu producto muestra el consumo en tiempo real, lee GET /v1/subscriptions/{id}/usage en vez de rehacer la cuenta: es el mismo cálculo que va al cobro.

Diferencias con Stripe

  • No existe precio medido (price con recurring.usage_type: "metered"). El precio vive en el ítem de uso de la oferta (por el panel) o de la suscripción (/usage_items), con unit_amount fraccionario, included_units, tiers, rounding y billing_threshold en el mismo objeto.
  • No hay factura. El período cerrado se convierte en un cobro en la suscripción con source: "usage", cobrado en el momento en la tarjeta guardada, y un pedido con billing_reason: "usage". Los eventos son invoice.paid y subscription_charge.*, no invoice.created ni invoice.finalized.
  • La franquicia es un campo. included_units reemplaza el tramo gratuito; los tiers cuentan después de ella.
  • La vinculación con la suscripción se hace al llegar el evento, no al cierre. Un evento sin suscripción no se cobra después.
  • billing.meter.error_report_triggered tiene un solo tipo de error (no_subscription_for_customer) y sale como máximo una vez por medidor y por hora.
  • event_summaries devuelve una list sin paginación, solo con los intervalos que tienen eventos.
  • El payload acepta stripe_customer_id como alias de customer_id; el valor tiene que ser el cust_… de Vipter.
  • No existen meter_event_adjustments ni reactivación de medidor.

Qué hacer después

¿Te ayudó esta página?

En esta página

Idioma