VipterCentral de Ajuda

Uso medido (Meters)

Como cobrar por consumo deixando a conta com o Vipter, no formato dos Billing Meters da Stripe: criar um medidor, dar preço ao uso na oferta ou na assinatura, enviar eventos de uso, ler o período aberto e o que acontece quando o ciclo fecha, com exemplos em curl e Node.js, franquia, faixas de preço, limiar de cobrança e as diferenças em relação à Stripe.

Admin ou DonoTodos os planos

Com o uso medido, o seu sistema só avisa o Vipter de cada consumo: uma chamada, um GB, um envio. O Vipter soma, aplica a franquia e o preço que você definiu, e cobra o total no cartão salvo da assinatura quando o ciclo fecha. É a alternativa à cobrança adicional por uso, em que você calcula o valor e chama POST /v1/subscriptions/{id}/charges.

Os endpoints seguem o formato dos Billing Meters da Stripe (billing.meter, billing.meter_event, event_summaries). O que muda está em Diferenças em relação à Stripe. Cada endpoint, com todos os parâmetros, está na referência da API.

Quando usar

Você querUse
Manter a contagem no seu sistema e decidir quando e quanto cobrar.Cobrança no cartão salvo: POST /v1/subscriptions/{id}/charges com o amount já calculado.
Só mandar os eventos e deixar o Vipter somar, aplicar a franquia, o preço e as faixas, e cobrar no fim do ciclo.Esta página.
Cobrar na hora, a cada consumo.Cobrança no cartão salvo. O uso medido não tem modo imediato: ele acumula e cobra no fechamento, ou ao atingir um limiar.

Os dois caminhos geram o mesmo tipo de cobrança (subscription_charge) e o mesmo pedido pago. Uma assinatura pode usar os dois.

O modelo

medidor (meter)  →  eventos de uso (meter_event)  →  item de uso (usage_item)  →  período (usage_period)  →  cobrança (subscription_charge)
  • Medidor. Um tipo de consumo, identificado por um event_name (api_calls, storage_gb). Diz como os eventos se agregam: sum soma os valores, count conta os eventos, last fica com o último valor.
  • Evento de uso. Um registro com o cliente (payload.customer_id) e o valor (payload.value). Na chegada, o Vipter liga o evento à assinatura ativa do cliente que tem preço para esse medidor.
  • Item de uso. O preço de um medidor numa assinatura: valor por unidade, franquia, faixas, arredondamento e limiar. Nasce da oferta (configurado no painel) ou é definido pela API na assinatura.
  • Período. O ciclo atual da assinatura (current_period_start → current_period_end). Enquanto está aberto, o uso é recalculado a partir dos eventos a cada rodada. Quando fecha, vira uma cobrança.
  • Cobrança. Uma cobrança na assinatura com source: "usage", feita uma vez por período, com uma linha por medidor. Ela vira um pedido pago com billing_reason: "usage".

Passo 1: criar o medidor

curl -X POST https://api.vipter.com/v1/billing/meters \
  -H "Authorization: Bearer vk_live_…" \
  -H "Content-Type: application/json" \
  -d '{
    "display_name": "Chamadas de API",
    "event_name": "api_calls",
    "default_aggregation": { "formula": "sum" }
  }'
{
  "id": "mtr_4f8e2c1a9b7d6e5f3a2b1c0d",
  "object": "billing.meter",
  "display_name": "Chamadas 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
}

O event_name é o que os eventos usam para achar o medidor: só minúsculas, dígitos, _, . e -, até 100 caracteres, único na loja (repetir recebe 400 event_name_taken). Ele não muda depois; o display_name muda por POST /v1/billing/meters/{id} e é o nome que aparece na linha do pedido do comprador. Se o seu sistema já manda eventos para a Stripe com stripe_customer_id no payload, mantenha: o Vipter aceita essa chave como apelido de customer_id.

Passo 2: dar preço ao uso

O preço mora num item de uso, um por medidor. Há dois lugares para ele:

Na oferta, pelo painel. Na página da oferta, o card de uso medido lista os medidores da loja e aceita o preço por unidade, a franquia, as faixas, o arredondamento e o limiar, por moeda. Toda assinatura dessa oferta herda o item na primeira rodada do uso medido depois de ser criada (a rodada acontece a cada 10 minutos). A herança copia a linha da moeda da assinatura ou, se não houver, a da moeda da loja. É uma cópia: mudar o preço na oferta depois não altera as assinaturas que já herdaram.

Na assinatura, pela API. Para um preço negociado ou para uma assinatura que não veio de uma oferta com 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": "Chamadas além da franquia"
  }'
{
  "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": "Chamadas além da franquia",
  "source": "api",
  "livemode": true,
  "created": 1791100900
}

unit_amount é o preço de uma unidade, na menor unidade da moeda, com frações: 0.4 é R$ 0,004 por chamada; 50 é R$ 0,50 por GB. A moeda é sempre a da assinatura (outra recebe 400 currency_mismatch). Enviar de novo o mesmo meter substitui o item; um item que você definiu pela API nunca é sobrescrito pela herança da oferta. DELETE /v1/subscriptions/{id}/usage_items/{itemId} remove.

Passo 3: enviar os 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 é a idempotência do evento, por medidor. Repetir o mesmo identifier devolve o evento já gravado, com 200, sem contar duas vezes. Use o ID da requisição, do job ou do registro no seu banco. Sem identifier, o Vipter gera um, e cada chamada conta.
  • payload.value é o valor somado (sum) ou guardado (last). Num medidor count, o valor é ignorado e cada evento vale 1.
  • timestamp é opcional, em segundos Unix: até 35 dias atrás e até 5 minutos à frente, senão 400 timestamp_out_of_range. Sem ele, vale a hora da chegada. É o timestamp que decide em qual período o evento cai.
  • subscription na resposta diz a quem o evento foi ligado. null é um evento sem assinatura.

Em volume, use o lote: até 100 eventos por chamada em POST /v1/billing/meter_events/batch. Cada item é aceito ou recusado por conta própria, e results[i] responde events[i] com status accepted, duplicate ou error. A resposta é 200 sempre que ao menos um item entrou; 400 só quando nenhum entrou.

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

// Um evento por consumo. `identifier` é o ID do registro no seu lado: reenviar nunca conta duas vezes.
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; até 35 dias atrás
      payload: { customer_id: record.vipterCustomerId, value: record.calls },
    }),
  });
  const json = await res.json();
  if (res.ok) return json; // 201 novo, 200 repetido
  // 400 com error.code: no_meter_found, meter_inactive, invalid_payload, timestamp_out_of_range. 404: cliente não existe.
  throw Object.assign(new Error(json.error.message), { code: json.error.code, status: res.status });
}

// Em lote: até 100 por chamada. Trate `results` item a item; `duplicate` é normal num reenvio.
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 recusado', records[i].id, r.error.code, r.error.message);
  });
  return json;
}

Guarde o cust_… de cada usuário quando a assinatura nasce: ele vem em customer da sessão de checkout e da assinatura. Um customer_id que não existe na loja recebe 404 resource_missing.

Passo 4: ler o uso do 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
    }
  ]
}

A lista traz os 12 períodos mais recentes, do mais novo para o mais antigo. O período aberto é calculado na hora da chamada, a partir dos eventos; os fechados vêm como ficaram no fechamento, com charge apontando para a cobrança. Cada linha é um medidor: quantity é o agregado, included a franquia, billable o que passou dela, amount o valor da linha em centavos. Use esta chamada para mostrar o consumo do mês no seu produto, em vez de somar do seu lado.

Para o agregado de um cliente num intervalo qualquer, inclusive por hora ou por dia, use GET /v1/billing/meters/{id}/event_summaries?customer=&start_time=&end_time=&value_grouping_window=day. Ele soma todos os eventos do cliente nesse medidor, ligados ou não a uma assinatura.

O que acontece no fim do ciclo

Uma rotina roda a cada 10 minutos e, para cada assinatura com itens de uso:

  1. Abre o período do ciclo atual, se ainda não existe: de current_period_start a current_period_end da assinatura. Só assinaturas active, trialing ou past_due têm período aberto.
  2. Recalcula o período aberto a partir dos eventos e grava lines e amount_total.
  3. Fecha o período quando o ciclo termina (close_reason: "period_end"), quando o valor acumulado atinge o limiar (threshold) ou quando a assinatura deixa de ser cobrável (subscription_ended).
  4. Cobra o período fechado com amount_total maior que zero, uma vez só, no cartão salvo da assinatura: uma cobrança na assinatura com source: "usage", Idempotency-Key usage:{id do período}, descrição Uso 2026-09-23 a 2026-10-23 e uma linha por medidor com valor, com o display_name do medidor. Um período com valor zero fecha sem cobrança e sem pedido.

A cobrança segue as regras das cobranças na assinatura:

  • Aprovada: saem invoice.paid, com o pedido (billing_reason: "usage", external_order_id com o sch_…), e subscription_charge.succeeded, com a cobrança (metadata.kind: "usage" e metadata.usage_period com o usp_…). O período fica closed com charge preenchido.
  • Recusada pelo cartão: saem subscription_charge.failed e, quando o provedor registrou um pedido, invoice.payment_failed. O Vipter não tenta de novo sozinho; o período fica fechado com charge apontando para a cobrança recusada. Para cobrar o valor depois de o assinante trocar o cartão, use POST /v1/subscriptions/{id}/charges com o amount_total do período.
  • Em análise: a cobrança fica pending e os eventos saem quando o provedor decide.
  • Recusada antes de chegar ao cartão (assinatura sem cartão salvo, cartão num provedor que não aceita cobrança sem o cliente, valor abaixo de uma unidade da moeda, loja com a mensalidade em atraso): o período fecha com charge: null e o uso não é cobrado depois. Em subscription_ended o uso já consumido ainda é cobrado no cartão salvo (é a fatura final da assinatura); só fica sem cobrança se o cartão não estiver mais disponível.

A renovação da assinatura não muda: a mensalidade continua sendo cobrada na data e no valor da oferta, e o uso vem numa cobrança separada. Cada cobrança de uso aprovada conta como um pedido pago na cota do plano Vipter da loja; os eventos de uso não contam.

Exemplos de preço

Os cálculos abaixo valem para uma linha. billable é o agregado menos included_units; o preço se aplica só a billable. O arredondamento acontece uma vez, no total da linha, para centavos inteiros: up arredonda para cima (padrão), nearest para o mais próximo.

Preço fixo com franquia

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

12.300 chamadas: billable 2.300 × 0,4 = 920 centavos, R$ 9,20. 9.000 chamadas: billable 0, linha com amount 0.

Faixas graduadas

{
  "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 faixa cobre as unidades entre o up_to anterior e o seu, e a última precisa ter up_to: null. Com tiers, o unit_amount do item não entra no cálculo (envie 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. Uma faixa pode ter flat_amount, em centavos, cobrado uma vez quando alguma unidade cai nela. As faixas contam a partir de billable: com included_units: 1000, a primeira faixa começa na unidade 1.001 do consumo.

Arredondamento

unit_amount: 0.4 e 23 unidades: 9,2 centavos. Com rounding: "up", a linha vale 10 centavos; com nearest, 9. O arredondamento é por linha; o total do período é a soma das linhas já arredondadas.

Agregações

sum soma value (GB transferidos). count conta eventos e ignora value (chamadas, envios). last fica com o value do evento de timestamp mais recente do período (assentos ativos, GB armazenados): mande o total atual, não a diferença.

Limiar de cobrança

billing_threshold, em centavos, fecha e cobra o período antes do fim do ciclo quando o valor acumulado o atinge:

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

Quando amount_total chega a R$ 200,00, o período fecha com close_reason: "threshold", é cobrado, e um período novo abre daquele momento até o fim do ciclo. A verificação acontece a cada rodada, então a cobrança pode passar um pouco do limiar. Com vários itens na assinatura, vale o menor limiar entre eles, sobre o total do período. Cada fechamento é uma cobrança e um pedido.

Eventos sem assinatura

Na chegada, o evento é ligado à assinatura do cliente que está active, trialing ou past_due e tem um item de uso para o medidor; com mais de uma, à mais recente. payload.subscription_id escolhe uma delas; se a escolhida não tem item para o medidor, o evento fica sem assinatura. A ligação é feita uma vez: um evento que chegou antes de a assinatura ter o item não é cobrado depois.

Um evento sem assinatura é gravado (subscription: null), aparece em event_summaries e não é cobrado. Enquanto isso acontece, o Vipter manda billing.meter.error_report_triggered no catálogo 2026-11-01, no máximo uma vez por medidor e por hora, com reason.error_count eventos sem assinatura na hora atual. As causas mais comuns:

  • A assinatura é nova e ainda não herdou o item da oferta: a herança acontece na rodada seguinte, em até 10 minutos. Comece a mandar eventos depois que GET /v1/subscriptions/{id}/usage_items devolver o item, ou crie o item pela API na hora.
  • O item existe só na oferta antiga: uma assinatura herda uma vez; depois de uma troca de oferta, confira os itens.
  • O customer_id é de outro cliente, ou a assinatura paga por PIX e não tem cartão salvo (ela recebe o evento, mas a cobrança falha no fechamento).

Desativar um medidor

POST /v1/billing/meters/{id}/deactivate para de aceitar eventos (400 meter_inactive) na hora. Os períodos abertos continuam mostrando a quantity do medidor, mas o preço passa a zero. Os itens de uso que apontam para ele continuam existindo; remova-os se não quer mais vê-los. Não há reativação pela API.

Limites

O quêLimite
timestamp do eventoAté 35 dias atrás e 5 minutos à frente.
Eventos por lote1 a 100.
payloadAté 20 chaves; valores texto, número ou booleano.
event_name, identifierAté 100 caracteres. event_name: a-z, 0-9, _, ., -.
display_name, labelAté 250 e 120 caracteres.
tiers1 a 20 faixas, up_to crescente, a última null.
Intervalo de event_summariesAté um ano.
GET …/usageOs 12 períodos mais recentes.
ChamadasOs limites de requisições da API.

O que o comprador vê

A cobrança do período vira um pedido com a descrição Uso <início> a <fim> e uma linha por medidor, com o display_name do medidor e o valor da linha. O assinante vê o pedido na área do cliente, junto das renovações, e recebe o e-mail de confirmação de compra da loja, quando ele está ativo. No painel, a cobrança aparece na página da assinatura com a origem Uso, e a página da assinatura mostra o uso do período aberto.

Se o seu produto mostra o consumo em tempo real, leia GET /v1/subscriptions/{id}/usage em vez de refazer a conta: é o mesmo cálculo que vai para a cobrança.

Diferenças em relação à Stripe

  • Não existe preço medido (price com recurring.usage_type: "metered"). O preço mora no item de uso da oferta (pelo painel) ou da assinatura (/usage_items), com unit_amount fracionário, included_units, tiers, rounding e billing_threshold no mesmo objeto.
  • Não há fatura. O período fechado vira uma cobrança na assinatura com source: "usage", cobrada na hora no cartão salvo, e um pedido com billing_reason: "usage". Os eventos são invoice.paid e subscription_charge.*, não invoice.created e invoice.finalized.
  • A franquia é um campo. included_units substitui a faixa gratuita; as tiers contam depois dela.
  • A ligação com a assinatura é feita na chegada do evento, não no fechamento. Um evento sem assinatura não é cobrado depois.
  • billing.meter.error_report_triggered tem um só tipo de erro (no_subscription_for_customer) e sai no máximo uma vez por medidor e por hora.
  • event_summaries devolve uma list sem paginação, só com os intervalos que têm eventos.
  • O payload aceita stripe_customer_id como apelido de customer_id; o valor precisa ser o cust_… do Vipter.
  • Não existem meter_event_adjustments nem reativação de medidor.

O que fazer a seguir

Esta página ajudou?

Nesta página

Idioma