Referência da API (v1)
Cada endpoint da API do Vipter com os parâmetros, um exemplo de chamada e de resposta, e a tabela de campos de cada objeto: conta, cliente, assinatura e suas ações, cobrança na assinatura, uso medido (medidores, eventos de uso, itens e períodos), pedido, oferta, produto, sessão de checkout, sessão do portal do cliente, eventos e endpoints de webhook.
Todos os endpoints ficam em https://api.vipter.com/v1, pedem uma chave de API e seguem as convenções: dinheiro em inteiros na menor unidade, datas em segundos Unix, moeda em minúsculas, listas paginadas por cursor. Os endpoints GET pedem o escopo read; os POST e DELETE pedem o escopo write e aceitam uma Idempotency-Key, que a cobrança na assinatura exige.
A descrição legível por máquina, em OpenAPI 3.1, está em GET https://api.vipter.com/v1/openapi.json. Ela serve para gerar clientes. Uma coleção Postman gerada dela, com uma pasta por recurso e corpos de exemplo, está em GET https://api.vipter.com/v1/postman.json; importa no Postman, no Bruno e no Insomnia. Para agentes de IA, veja Integrar com agentes de IA.
Os exemplos usam IDs fictícios e valores em reais. O Authorization foi encurtado para vk_live_….
Conta
Consultar a conta
GET /v1/accountDevolve a loja e a chave que fizeram a chamada. É a primeira chamada de uma integração nova: se ela responde 200, a chave está certa e a API está liberada.
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 | Conteúdo |
|---|---|---|
id | texto | ID da loja no Vipter. |
name, slug | texto | Nome e slug da loja. slug é null se a loja não tem um. |
country | texto | País da loja, ISO 3166-1 alfa-2. |
currency | texto | Moeda padrão da loja. |
timezone | texto | Fuso horário da loja, no formato IANA. |
api_key | objeto | A chave usada: id, name, scopes (read, write) e default_version, a versão da API que a chave usa quando a chamada não envia Vipter-Version. |
api_version | texto | A versão usada nesta chamada. |
livemode | booleano | true em produção. |
Clientes
Um cliente é quem comprou ou assinou na loja. Ele é criado pelo checkout, pela equipe no painel ou por POST /v1/customers.
Listar clientes
GET /v1/customers| Parâmetro | Tipo | Conteúdo |
|---|---|---|
email | texto | Só clientes com esse e-mail, sem diferenciar maiúsculas. |
limit, starting_after, ending_before | Paginação. |
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 um cliente
GET /v1/customers/{id}curl https://api.vipter.com/v1/customers/cust_9d2e4f6a8b1c3d5e \
-H "Authorization: Bearer vk_live_…"Devolve o objeto customer. Um ID que não existe na loja recebe 404 resource_missing.
Criar um cliente
POST /v1/customersCria o cliente na loja antes da primeira compra, para abrir uma sessão de checkout com customer ou para gravar metadata. Pede o escopo write. O provedor de pagamento exige nome completo, telefone e documento, por isso eles são obrigatórios aqui.
| Parâmetro | Tipo | Conteúdo |
|---|---|---|
email | texto | Obrigatório. Guardado em minúsculas. |
name | texto | Obrigatório. Nome completo, de 3 a 120 caracteres. |
phone | texto | Obrigatório. Em formato internacional (+5511999990000), ou um número nacional do país da loja. Um número que não é válido para o país recebe 400 parameter_invalid com param phone. |
document[type], document[number] | texto | Obrigatório. type é cpf, cnpj, passport ou tax_id; number pode vir com pontos e traços, que são removidos. |
address | objeto | Endereço de cobrança: line1, city, state, postal_code e country (ISO 3166-1 alfa-2) obrigatórios dentro do objeto; line2, number e district opcionais. |
metadata | objeto | Dados livres, com os mesmos limites do metadata das sessões 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" }
}'A resposta é o objeto customer, com 201 quando o cliente foi criado. Se já existe um cliente com esse e-mail na loja, a resposta é 200 com o cliente existente, sem alterar nenhum campo: para mudar os dados, use Alterar um cliente. Um cliente novo gera o evento customer.created.
code | HTTP | Significado |
|---|---|---|
parameter_missing, parameter_invalid | 400 | Campo obrigatório ausente ou com formato errado. param diz qual. |
customer_rejected | 400 | O provedor de pagamento recusou o cadastro. A message traz o motivo informado por ele, como um documento inválido. |
Alterar um cliente
POST /v1/customers/{id}Aceita os mesmos campos de Criar um cliente, todos opcionais. Só o que vier é alterado, com uma exceção: metadata substitui o mapa inteiro, como na Stripe. Para apagar uma chave, envie o mapa sem ela. Um ID que não existe recebe 404 resource_missing antes de qualquer alteração.
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" } }'Devolve o objeto customer atualizado e gera o evento customer.updated. Os erros são os mesmos da criação.
O objeto customer
| Campo | Tipo | Conteúdo |
|---|---|---|
id | texto | cust_… |
object | texto | "customer" |
email | texto | E-mail do cliente. É o que identifica a pessoa no checkout e na área do cliente. |
name | texto ou null | Nome informado no checkout. |
phone | texto ou null | Telefone em formato internacional, com + e o código do país. |
document | objeto ou null | type (como cpf ou cnpj) e number_masked, só com os últimos quatro dígitos. A API nunca devolve o documento inteiro. |
address | objeto ou null | Endereço de cobrança: line1, line2, city, state, postal_code, country. Cada campo pode ser null. |
country | texto ou null | País do cliente, ISO 3166-1 alfa-2. |
locale | texto ou null | Idioma do cliente, como pt-BR, en ou es. |
metadata | objeto | Os dados gravados por POST /v1/customers ou POST /v1/customers/{id}. {} nos clientes criados pelo checkout ou pelo painel. O metadata de uma sessão de checkout vai para o pedido e para a assinatura, não para o cliente. |
livemode | booleano | true em produção. |
created | inteiro | Quando o cliente foi criado. |
Assinaturas
Listar assinaturas
GET /v1/subscriptions| Parâmetro | Tipo | Conteúdo |
|---|---|---|
customer | texto | Só assinaturas desse cliente (cust_…). |
status | texto | Um de trialing, active, past_due, paused, canceled, expired. Outro valor recebe 400 parameter_invalid. |
limit, starting_after, ending_before | Paginação. |
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 uma assinatura
GET /v1/subscriptions/{id}curl https://api.vipter.com/v1/subscriptions/sub_3c7a9e1f5b2d8c4e \
-H "Authorization: Bearer vk_live_…"Devolve o objeto subscription.
O objeto subscription
| Campo | Tipo | Conteúdo |
|---|---|---|
id | texto | sub_… |
object | texto | "subscription" |
status | texto | trialing (em teste grátis), active, past_due (a última cobrança falhou e o Vipter está tentando de novo), paused, canceled, expired (chegou ao limite de ciclos ou ao fim sem renovar). |
customer | texto ou null | cust_… do assinante. |
customer_email | texto ou null | E-mail do assinante, para não precisar de outra chamada. |
offer | objeto ou null | A oferta atual: id (ofr_…) e name. |
product | objeto ou null | O produto: id (prd_…) e name. |
billing_cycle | texto ou null | O intervalo de cobrança: daily, biweekly, monthly, quarterly, half_yearly, yearly ou custom. |
custom_billing_days | inteiro ou null | Com billing_cycle custom, o intervalo em dias. |
currency | texto ou null | Moeda da assinatura. |
amount | inteiro ou null | Valor de cada cobrança, na menor unidade. |
current_period_start, current_period_end | inteiro ou null | O período já pago. |
next_billing_at | inteiro ou null | Quando a próxima cobrança está prevista. null quando não há próxima. |
trial_start, trial_end | inteiro ou null | O período de teste grátis, se houve. |
cycles_completed | inteiro | Quantas cobranças já foram feitas. |
cycle_limit | inteiro ou null | Quantas cobranças a assinatura faz no total, nas ofertas com número fixo de ciclos. null é sem limite. |
cancel_at_period_end | booleano | true quando o cancelamento foi agendado para o fim do período pago. O status continua active até lá. |
canceled_at | inteiro ou null | Quando o cancelamento foi pedido. |
ended_at | inteiro ou null | Quando a assinatura deixou de valer. |
cancellation_details | objeto ou null | reason (código do motivo), comment (texto livre) e source: quem cancelou, como o painel, a área do cliente ou o provedor. |
default_payment_method | objeto ou null | O cartão salvo que paga as renovações: id e type (card). null quando a assinatura paga por PIX ou outro meio sem cartão salvo. |
installments | inteiro ou null | Em quantas parcelas cada cobrança é feita, quando a oferta permite. |
past_due_details | objeto ou null | Só com status past_due: attempts (quantas tentativas já falharam), next_retry_at e since (quando a primeira falhou). |
checkout_session | texto ou null | cs_… da sessão de checkout que criou a assinatura. null nas assinaturas vindas de um link comum ou do painel. |
client_reference_id | texto ou null | O seu identificador, copiado do client_reference_id da sessão de checkout ou gravado por POST /v1/subscriptions/{id}. |
metadata | objeto | O subscription_data[metadata] da sessão de checkout ou, quando ele não veio, o metadata da sessão; ou o que você gravou por POST /v1/subscriptions/{id}. {} nas outras assinaturas. |
livemode | booleano | true em produção. |
created | inteiro | Quando a assinatura começou. |
Ações na assinatura
As mesmas ações que a equipe faz na página da assinatura no painel, e que o assinante faz na área do cliente. Todas pedem o escopo write, aceitam uma Idempotency-Key e devolvem o objeto subscription já atualizado: leia status, cancel_at_period_end, offer e next_billing_at na resposta em vez de esperar o webhook. O evento correspondente sai do mesmo jeito que numa ação pelo painel; veja o catálogo de eventos.
Um ID que não existe na loja recebe 404 resource_missing. Uma ação que não cabe no estado atual da assinatura, como pausar uma assinatura já pausada ou retomar uma que não está pausada, recebe 400 provider_error, com o motivo na message.
Alterar uma assinatura
POST /v1/subscriptions/{id}Grava a sua referência e o metadata na assinatura, ou agenda o cancelamento para o fim do período pago, como o cancel_at_period_end da Stripe.
| Parâmetro | Tipo | Conteúdo |
|---|---|---|
client_reference_id | texto ou null | O seu identificador, até 200 caracteres. null apaga. |
metadata | objeto | Substitui o mapa inteiro, como na Stripe. Para apagar uma chave, envie o mapa sem ela. Mesmos limites do metadata das sessões de checkout. |
cancel_at_period_end | booleano | true agenda o cancelamento: o status continua active até current_period_end, e a assinatura não renova. false é recusado com 400 parameter_invalid: para desfazer um cancelamento agendado, chame POST …/reactivate. |
cancellation_details[reason] | texto | Com cancel_at_period_end: true: o motivo, um de too_expensive, not_using, missing_features, switching, temporary, other. Outro valor é ignorado. |
cancellation_details[comment] | texto | Com cancel_at_period_end: true: um comentário livre, até 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": "Vai migrar para o plano anual em janeiro." }
}'Devolve o objeto subscription com cancel_at_period_end: true e cancellation_details com o motivo e o comentário. Alterar client_reference_id ou metadata não gera evento, e agendar o cancelamento também não: o objeto aparece com cancel_at_period_end: true no próximo evento da assinatura e, quando o período termina, sai customer.subscription.deleted. Quando a chamada traz a referência e o agendamento juntos, a referência é gravada primeiro; se o agendamento for recusado, ela já ficou salva.
Cancelar agora
DELETE /v1/subscriptions/{id}Cancela na hora, sem esperar o fim do período pago, como o DELETE da Stripe. O motivo vai na query string, com os mesmos valores de Alterar uma assinatura.
curl -X DELETE "https://api.vipter.com/v1/subscriptions/sub_3c7a9e1f5b2d8c4e?cancellation_details[reason]=not_using" \
-H "Authorization: Bearer vk_live_…"Devolve o objeto subscription com status: "canceled" e gera customer.subscription.deleted (subscription.cancelled no catálogo original). Em cancellation_details, o source de um cancelamento pela API vem como dashboard, o mesmo valor de um cancelamento pela equipe. O assinante recebe os mesmos avisos de um cancelamento pelo painel.
Pausar e retomar
POST /v1/subscriptions/{id}/pause
POST /v1/subscriptions/{id}/resumePausar interrompe as renovações sem cancelar; retomar volta a cobrar. O corpo de pause aceita reason, um texto livre de até 200 caracteres que fica no histórico da assinatura; resume não tem corpo.
curl -X POST https://api.vipter.com/v1/subscriptions/sub_3c7a9e1f5b2d8c4e/pause \
-H "Authorization: Bearer vk_live_…" \
-H "Content-Type: application/json" \
-d '{ "reason": "Cliente pediu uma pausa de dois meses." }'Devolvem o objeto subscription com status: "paused" e, depois, "active", e geram customer.subscription.paused e customer.subscription.resumed. Uma assinatura pausada não aceita cobranças avulsas.
Reativar
POST /v1/subscriptions/{id}/reactivateDesfaz um cancelamento agendado (cancel_at_period_end volta a false e a assinatura renova normalmente) ou reativa uma assinatura cancelada ou expirada, quando o provedor de assinaturas permite; uma reativação que ele recusa recebe 400 provider_error. Sem corpo.
curl -X POST https://api.vipter.com/v1/subscriptions/sub_3c7a9e1f5b2d8c4e/reactivate \
-H "Authorization: Bearer vk_live_…"Devolve o objeto subscription e gera customer.subscription.updated (subscription.reactivated no catálogo original).
Trocar de oferta
POST /v1/subscriptions/{id}/change_offerMove a assinatura para outra oferta, como um upgrade do plano mensal para o anual.
| Parâmetro | Tipo | Conteúdo |
|---|---|---|
offer | texto | Obrigatório. O ofr_… da oferta nova. Só o ID; o slug não é aceito. Uma oferta que não existe na loja recebe 404 resource_missing com param offer. |
A oferta nova precisa pertencer ao mesmo produto, ou à mesma família de produtos, da oferta atual; fora disso, a troca é recusada com 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" }'Devolve o objeto subscription com offer, amount, billing_cycle e next_billing_at já da oferta nova, e gera customer.subscription.updated (subscription.upgraded ou subscription.downgraded no catálogo original, conforme o valor novo seja maior ou igual, ou menor, que o anterior).
Cobranças na assinatura
Uma cobrança na assinatura é um valor avulso cobrado agora no cartão salvo de uma assinatura, fora do ciclo: um excedente de uso, um adicional, um serviço extra. A data da próxima renovação e o valor recorrente não mudam. É a mesma cobrança que a equipe faz no painel pelo botão Cobrar valor extra… da assinatura (Cobrança avulsa); as duas aparecem na mesma lista, e o campo source diz de onde cada uma veio (API ou Painel na página da assinatura).
A cobrança não pede nada ao assinante: o Vipter cobra o cartão salvo sem o cliente presente, pelo mesmo provedor que fez a primeira cobrança da assinatura. Cada cobrança aprovada vira um pedido pago e conta como um pedido na cota do plano Vipter da loja. O roteiro para cobrar uso num SaaS está em Cobrança adicional por uso.
Cobrar o cartão salvo
POST /v1/subscriptions/{id}/chargesPede o escopo write e exige uma Idempotency-Key: sem ela, a chamada recebe 400 idempotency_key_required antes de qualquer coisa. Use um valor que identifique a cobrança que você pretende fazer, como o ID do período de uso no seu sistema. A mesma chave nunca cobra duas vezes: ela devolve a mesma cobrança, com 200 e o cabeçalho Idempotent-Replayed: true. Diferente das outras chamadas, a chave de uma cobrança não vence em 24 horas: ela fica ligada à cobrança para sempre, e repeti-la meses depois ainda devolve a mesma cobrança.
| Parâmetro | Tipo | Conteúdo |
|---|---|---|
amount | inteiro | Obrigatório. O total a cobrar, na menor unidade da moeda (4990 é R$ 49,90). O mínimo é uma unidade da moeda (100 em brl ou usd), senão 400 amount_too_small. |
currency | texto | Opcional. Precisa ser a moeda da assinatura, senão 400 currency_mismatch. Sem ela, a moeda da assinatura é usada. |
description | texto | Até 140 caracteres. É a descrição da cobrança enviada ao provedor e, sem lines, o nome do único item do pedido. Sem description, vale a descrição da primeira linha. |
lines[] | lista | De 1 a 50 itens que explicam o valor; viram os itens do pedido. Cada item: description (obrigatório, até 140 caracteres), quantity (inteiro, padrão 1) e unit_amount (inteiro, na menor unidade). A soma de quantity × unit_amount precisa ser igual a amount, senão 400 lines_total_mismatch. Sem lines, o pedido tem um item só, com description e amount. |
metadata | objeto | Dados livres, com os mesmos limites do metadata das sessões de checkout. Fica na cobrança e volta nos 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 setembro",
"lines": [
{ "description": "Chamadas além da franquia", "quantity": 2300, "unit_amount": 2 },
{ "description": "Armazenamento adicional (GB)", "quantity": 23, "unit_amount": 50 }
],
"metadata": { "user_id": "user_8213", "period": "2026-09" }
}'A resposta é 201 com o 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 setembro",
"lines": [
{ "description": "Chamadas além da franquia", "quantity": 2300, "unit_amount": 2, "amount": 4600 },
{ "description": "Armazenamento 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
}O que acontece com uma cobrança aprovada:
- Ela vira um pedido com
billing_reason: "manual",order_type: "api",recurrence: "unscheduled",subscriptionpreenchido e aslinescomo itens (kind: "charge"). Osch_…da cobrança fica emexternal_order_iddo pedido. - Saem os eventos
invoice.paid(com o pedido) esubscription_charge.succeeded(com a cobrança) no catálogo 2026-11-01, eorder.paidno original. - O assinante recebe o e-mail de confirmação de compra da loja, quando ele está ativo, e vê o pedido na área do cliente.
Uma cobrança que o provedor deixa em análise volta com 201 e status: "pending", sem settled_at. Ela é concluída depois, quando o provedor avisa, e aí passa a succeeded ou failed e gera os eventos. Consulte-a por GET /v1/subscription_charges/{id} ou espere subscription_charge.succeeded / subscription_charge.failed.
Cartão recusado. A resposta é 402 com type card_error, como na Stripe. O code é o código de recusa informado pelo provedor, ou declined quando ele não informa um; os códigos variam por provedor, então não dependa de uma lista fixa. A cobrança recusada vem inteira em error.subscription_charge, com status: "failed", failure_code e 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 setembro",
"lines": [
{ "description": "Chamadas além da franquia", "quantity": 2300, "unit_amount": 2, "amount": 4600 },
{ "description": "Armazenamento 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
}
}
}Uma recusa gera subscription_charge.failed e, quando o provedor registrou um pedido para a tentativa, invoice.payment_failed com esse pedido (status: "failed"). O Vipter não tenta de novo sozinho: a cobrança fica failed, e repetir a mesma Idempotency-Key devolve o mesmo 402. Para cobrar outra vez, depois de o assinante trocar o cartão na área do cliente, por exemplo, faça uma chamada nova com outra chave.
Erros desta chamada, além dos erros gerais:
code | HTTP | Significado |
|---|---|---|
idempotency_key_required | 400 | A chamada veio sem Idempotency-Key. O type é idempotency_error. |
resource_missing | 404 | A assinatura não existe na loja. |
subscription_not_chargeable | 400 | A assinatura está paused, canceled ou expired. Só active, trialing e past_due aceitam cobrança. |
no_payment_method | 400 | A assinatura não tem cartão salvo: paga por PIX ou outro meio sem cartão. |
payment_method_not_chargeable | 400 | O cartão está guardado num provedor que não aceita cobrança sem o cliente presente. Hoje, o Mercado Pago. |
currency_mismatch | 400 | currency é diferente da moeda da assinatura. A message diz qual é. |
amount_too_small | 400 | amount é menor que uma unidade da moeda. |
lines_total_mismatch | 400 | A soma das linhas não bate com amount. A message traz os dois valores. |
provider_error | 400 | O provedor recusou o pedido de cobrança antes de chegar ao cartão. A message traz o motivo. A cobrança fica registrada como failed com essa chave; use outra para tentar de novo. |
project_inactive | 403 | A loja não pode cobrar porque a mensalidade do Vipter está em atraso. O type é permission_error. |
Listar as cobranças de uma assinatura
GET /v1/subscriptions/{id}/charges| Parâmetro | Tipo | Conteúdo |
|---|---|---|
status | texto | pending, succeeded ou failed. |
limit, starting_after, ending_before | Paginação. |
curl "https://api.vipter.com/v1/subscriptions/sub_3c7a9e1f5b2d8c4e/charges?status=succeeded" \
-H "Authorization: Bearer vk_live_…"Devolve um objeto list de subscription_charge, da mais nova para a mais antiga, com as cobranças feitas pela API e as feitas no painel.
Buscar uma cobrança
GET /v1/subscription_charges/{id}curl https://api.vipter.com/v1/subscription_charges/sch_7e2a9c4b1d3f5a6e8b0c2d4f \
-H "Authorization: Bearer vk_live_…"Devolve o objeto subscription_charge. É a chamada para acompanhar uma cobrança que ficou pending.
O objeto da cobrança
| Campo | Tipo | Conteúdo |
|---|---|---|
id | texto | sch_… |
object | texto | "subscription_charge" |
status | texto | pending (em análise no provedor), succeeded (paga) ou failed (recusada, ou o pedido de cobrança foi recusado). |
subscription | texto | sub_… da assinatura cobrada. |
customer | texto ou null | cust_… do assinante. |
amount | inteiro | O total cobrado, na menor unidade. |
currency | texto | A moeda, em minúsculas: sempre a da assinatura. |
description | texto ou null | A descrição enviada, ou a da primeira linha. |
lines | lista | As linhas enviadas: description, quantity, unit_amount e amount (unit_amount × quantity). [] quando a cobrança veio sem lines. |
order | texto ou null | ord_… do pedido que a cobrança gerou. null quando o provedor não registrou um pedido. |
transaction | texto ou null | O ID da transação no provedor de pagamento. |
failure_code | texto ou null | Com status failed: o código de recusa do provedor, declined quando ele não informou um, ou o código do erro quando o pedido de cobrança foi recusado. |
failure_message | texto ou null | Com status failed: o motivo, no texto do provedor. |
source | texto | De onde a cobrança veio: api (esta API), dashboard (o botão do painel) ou usage (cobrança por uso medido, feita pelo Vipter no fechamento de um período). |
metadata | objeto | O que você enviou. {} nas cobranças feitas no painel. |
livemode | booleano | true em produção. |
created | inteiro | Quando a cobrança foi pedida. |
settled_at | inteiro ou null | Quando ela passou a succeeded ou failed. null enquanto pending. |
Uso medido
Cobrança por consumo com a conta do lado do Vipter, no formato dos Billing Meters da Stripe: um medidor por tipo de uso, eventos de uso por cliente, um item de uso que dá preço ao medidor na assinatura, e um período por ciclo da assinatura, que fecha e vira uma cobrança na assinatura com source: "usage". O roteiro, com exemplos de preço e o que acontece no fim do ciclo, está em Uso medido (Meters). Os GET pedem o escopo read; os POST e DELETE, o escopo write.
Criar um medidor
POST /v1/billing/meters| Parâmetro | Tipo | Conteúdo |
|---|---|---|
display_name | texto | Obrigatório. Nome do medidor, de 1 a 250 caracteres. Aparece na linha do pedido do comprador. |
event_name | texto | Obrigatório. O nome que os eventos usam: só a-z, 0-9, _, . e -, até 100 caracteres, único na loja. Não muda depois. |
default_aggregation[formula] | texto | sum (soma os valores, padrão), count (conta os eventos, ignora o valor) ou last (fica com o último valor do período). |
customer_mapping[event_payload_key] | texto | A chave do payload que traz o cust_… do cliente. Padrão customer_id. customer_mapping[type] só aceita by_id. |
value_settings[event_payload_key] | texto | A chave do payload que traz o valor. Padrão value. |
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" } }'A resposta é 201 com o objeto billing.meter. Um event_name que já existe na loja recebe 400 event_name_taken.
Listar medidores
GET /v1/billing/meters| Parâmetro | Tipo | Conteúdo |
|---|---|---|
status | texto | active ou inactive. |
limit | inteiro | Tamanho da página. |
Devolve um objeto list de billing.meter, do mais novo para o mais antigo.
Buscar e alterar um medidor
GET /v1/billing/meters/{id}
POST /v1/billing/meters/{id}O POST aceita só display_name; os outros campos do medidor não mudam. Um ID que não existe na loja recebe 404 resource_missing.
Desativar um medidor
POST /v1/billing/meters/{id}/deactivateSem corpo. O medidor passa a inactive com status_transitions.deactivated_at preenchido, e novos eventos com o seu event_name recebem 400 meter_inactive. Os períodos abertos continuam mostrando a quantidade do medidor, com preço zero. Não há reativação pela API.
O objeto billing.meter
{
"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
}| Campo | Tipo | Conteúdo |
|---|---|---|
id | texto | mtr_… |
object | texto | "billing.meter" |
display_name | texto | O nome do medidor. |
event_name | texto | O nome que os eventos usam. |
default_aggregation | objeto | formula: sum, count ou last. |
customer_mapping | objeto | type (by_id) e event_payload_key, a chave do payload com o cliente. |
value_settings | objeto | event_payload_key, a chave do payload com o valor. |
status | texto | active ou inactive. |
status_transitions | objeto | deactivated_at: quando o medidor foi desativado, ou null. |
livemode | booleano | true em produção. |
created, updated | inteiro | Criação e última alteração. |
Registrar um evento de uso
POST /v1/billing/meter_events| Parâmetro | Tipo | Conteúdo |
|---|---|---|
event_name | texto | Obrigatório. O event_name do medidor. |
payload | objeto | Obrigatório. Até 20 chaves com valores texto, número ou booleano. Precisa trazer o cliente na chave do medidor (customer_id por padrão; stripe_customer_id é aceito como apelido) e, fora dos medidores count, o valor numérico na chave do valor (value por padrão). subscription_id escolhe a assinatura quando o cliente tem mais de uma com preço para o medidor. |
identifier | texto | Até 100 caracteres. Idempotência por medidor: o mesmo identifier devolve o evento já gravado, com 200. Sem ele, o Vipter gera um. |
timestamp | inteiro | Quando o uso aconteceu, em segundos Unix: até 35 dias atrás e até 5 minutos à frente. Padrão: agora. |
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 } }'A resposta é 201 com o objeto billing.meter_event, ou 200 com o evento já gravado quando o identifier se repete. Na chegada, o evento é ligado à assinatura active, trialing ou past_due do cliente que tem um item de uso para o medidor (a mais recente, se houver mais de uma). Um evento sem assinatura é gravado com subscription: null, não é cobrado, e dispara billing.meter.error_report_triggered, no máximo uma vez por medidor e por hora. Veja Eventos sem assinatura.
code | HTTP | Significado |
|---|---|---|
no_meter_found | 400 | Nenhum medidor tem esse event_name. |
meter_inactive | 400 | O medidor foi desativado. |
invalid_payload | 400 | Falta o cliente, ou o valor não é um número. param diz a chave (payload.customer_id, payload.value). |
timestamp_out_of_range | 400 | timestamp fora da janela de 35 dias atrás a 5 minutos à frente. |
resource_missing | 404 | O cliente do payload não existe na loja. param é a chave do cliente. |
Registrar eventos em lote
POST /v1/billing/meter_events/batch| Parâmetro | Tipo | Conteúdo |
|---|---|---|
events[] | lista | Obrigatório. De 1 a 100 eventos, cada um com os campos de Registrar um 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 é aceito ou recusado por conta própria, na ordem enviada: results[i] responde events[i] com status accepted (gravado agora), duplicate (o identifier já existia; event é o gravado) ou error (error com code, message e param, os mesmos códigos da chamada unitária, com customer_not_found no lugar de resource_missing). A resposta é 200 sempre que ao menos um evento foi aceito ou repetido, e 400 só quando nenhum entrou. Um lote com um campo inválido no corpo (como events vazio) recebe 400 parameter_invalid sem gravar nada.
O objeto billing.meter_event
| Campo | Tipo | Conteúdo |
|---|---|---|
id | texto | mev_… |
object | texto | "billing.meter_event" |
event_name | texto | O medidor. |
identifier | texto | O seu identifier, ou o gerado pelo Vipter. |
payload | objeto | O payload enviado. |
customer | texto | cust_… do cliente. |
subscription | texto ou null | A assinatura em que o evento será cobrado. null quando nenhuma assinatura ativa do cliente tem preço para o medidor. |
value | número | O valor do evento. 1 nos medidores count. |
timestamp | inteiro | Quando o uso aconteceu. Decide em qual período o evento cai. |
livemode | booleano | true em produção. |
created | inteiro | Quando o evento chegou. |
Consultar o uso agregado de um cliente
GET /v1/billing/meters/{id}/event_summaries| Parâmetro | Tipo | Conteúdo |
|---|---|---|
customer | texto | Obrigatório. O cust_…. |
start_time, end_time | inteiro | Obrigatórios. O intervalo, em segundos Unix: start_time inclusive, end_time exclusive. end_time precisa ser maior, e o intervalo pode ter até um ano, senão 400 parameter_invalid. |
value_grouping_window | texto | hour ou day: um resumo por hora ou por dia (em UTC), só dos intervalos que têm eventos. Sem ele, um resumo só. |
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 a fórmula do medidor (sum, count ou last) aos eventos do cliente no intervalo, ligados ou não a uma assinatura. A lista não é paginada.
Itens de uso de uma assinatura
GET /v1/subscriptions/{id}/usage_items
POST /v1/subscriptions/{id}/usage_items
DELETE /v1/subscriptions/{id}/usage_items/{itemId}Um item de uso dá preço a um medidor numa assinatura: um item por medidor. Ele nasce da oferta (configurado no painel, na página da oferta) na primeira rodada do uso medido depois de a assinatura ser criada, em até 10 minutos, com source: "offer", ou é definido aqui, com source: "api". Um item definido pela API nunca é sobrescrito pela herança da oferta. O POST do mesmo meter substitui o item.
| Parâmetro | Tipo | Conteúdo |
|---|---|---|
meter | texto | Obrigatório. O mtr_…. Um medidor que não existe recebe 404 resource_missing. |
currency | texto | Opcional. Precisa ser a moeda da assinatura, senão 400 currency_mismatch. |
unit_amount | número | Obrigatório. Preço de uma unidade, na menor unidade da moeda, com frações: 0.4 é R$ 0,004. 0 ou mais. Com tiers, não entra no cálculo. |
included_units | número | Franquia: unidades do período que não são cobradas. Padrão 0. |
tiers[] | lista | De 1 a 20 faixas graduadas sobre as unidades além da franquia, cada uma com up_to (limite superior, inclusive; null na última), unit_amount (por unidade na faixa) e flat_amount (opcional, cobrado uma vez quando a faixa é usada). up_to precisa ser crescente e o último null, senão 400 parameter_invalid com param tiers. |
rounding | texto | up (para cima, padrão) ou nearest (mais próximo), aplicado ao total da linha, em centavos inteiros. |
billing_threshold | inteiro | Em centavos. O período fecha e cobra antes do fim do ciclo quando o total acumulado o atinge. |
label | texto | Até 120 caracteres, para o seu controle. |
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 }'O POST responde 201 com o objeto usage_item; o GET devolve um objeto list de usage_item, sem paginação; o DELETE devolve { "id": "usi_…", "object": "usage_item", "deleted": true }, ou 404 resource_missing se o item não é dessa assinatura.
O 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 | Conteúdo |
|---|---|---|
id | texto | usi_… |
object | texto | "usage_item" |
meter | texto | mtr_… do medidor. |
subscription | texto | sub_… da assinatura. |
offer | null | Reservado. Nos itens de uma assinatura vem sempre null. |
currency | texto | A moeda, em minúsculas: a da assinatura. |
unit_amount, included_units, tiers, rounding, billing_threshold, label | Como enviados. tiers e billing_threshold vêm null quando não há. | |
source | texto | offer (herdado da oferta), api ou dashboard. |
livemode | booleano | true em produção. |
created | inteiro | Quando o item foi criado. |
Consultar os períodos de uso
GET /v1/subscriptions/{id}/usagecurl https://api.vipter.com/v1/subscriptions/sub_3c7a9e1f5b2d8c4e/usage \
-H "Authorization: Bearer vk_live_…"Devolve um objeto list com os 12 períodos mais recentes da assinatura, do mais novo para o mais antigo, sem paginação. O período open é calculado na hora, a partir dos eventos; os closed vêm como ficaram no fechamento. Uma assinatura sem itens de uso devolve uma lista vazia.
O 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 | Conteúdo |
|---|---|---|
id | texto | usp_… |
object | texto | "usage_period" |
subscription | texto | sub_… da assinatura. |
status | texto | open (acumulando), closing (sendo cobrado) ou closed. |
close_reason | texto ou null | period_end (o ciclo terminou), threshold (atingiu o billing_threshold) ou subscription_ended (a assinatura deixou de ser cobrável). null enquanto aberto. |
period_start, period_end | inteiro | O intervalo do período: o ciclo da assinatura, ou o trecho dele depois de um fechamento por limiar. |
currency | texto | A moeda, em minúsculas. |
lines | lista | Uma por medidor com item: meter, event_name, quantity (o agregado), included (a franquia), billable (quantity menos included), unit_amount e amount (em centavos). |
amount_total | inteiro | A soma das linhas, em centavos. |
charge | texto ou null | sch_… da cobrança do período, aprovada ou recusada. null enquanto aberto, quando o total foi zero, ou quando a cobrança foi recusada antes de chegar ao cartão. |
computed_at | inteiro ou null | Quando as linhas foram calculadas. No período aberto, a hora da chamada. |
closed_at | inteiro ou null | Quando o período fechou. |
livemode | booleano | true em produção. |
created | inteiro | Quando o período abriu. |
Erros do uso medido
Além dos erros gerais:
code | HTTP | Onde | Significado |
|---|---|---|---|
event_name_taken | 400 | Criar um medidor | Já existe um medidor com esse event_name. |
no_meter_found | 400 | Eventos | Nenhum medidor tem esse event_name. |
meter_inactive | 400 | Eventos | O medidor foi desativado. |
invalid_payload | 400 | Eventos | Falta o cliente no payload, ou o valor não é um número. |
timestamp_out_of_range | 400 | Eventos | timestamp fora da janela de 35 dias atrás a 5 minutos à frente. |
customer_not_found | Lote | Só dentro de results[]: o cliente não existe. Na chamada unitária é 404 resource_missing. | |
currency_mismatch | 400 | Itens de uso | currency é diferente da moeda da assinatura. |
parameter_invalid | 400 | Itens de uso, resumos | tiers fora de ordem ou sem a última faixa null; end_time antes de start_time ou intervalo maior que um ano. |
resource_missing | 404 | Todos | Medidor, assinatura, item ou cliente que não existe na loja. |
Pedidos
Um pedido é uma cobrança: uma compra avulsa, a primeira cobrança de uma assinatura, uma renovação, uma cobrança avulsa na assinatura feita no painel ou pela API. É o que a Stripe chama de fatura (invoice); o nome segue o que o lojista vê no painel.
Listar pedidos
GET /v1/orders| Parâmetro | Tipo | Conteúdo |
|---|---|---|
customer | texto | Só pedidos desse cliente (cust_…). |
subscription | texto | Só pedidos dessa assinatura (sub_…). |
status | texto | Um de pending, pre_authorized, authorized, failed, canceled, refund_pending, partially_refunded, refunded, charged_back. |
limit, starting_after, ending_before | Paginação. |
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 um pedido
GET /v1/orders/{id}curl https://api.vipter.com/v1/orders/ord_7b3e9f1c2a8d4e6f \
-H "Authorization: Bearer vk_live_…"Devolve o objeto order.
O objeto order
| Campo | Tipo | Conteúdo |
|---|---|---|
id | texto | ord_… |
object | texto | "order" |
status | texto | Veja Status do pedido. |
paid | booleano | true quando o dinheiro entrou, mesmo que depois tenha sido estornado no todo ou em parte. Use status para o detalhe. |
status_source | texto | provider quando o status veio do provedor de pagamento; manual quando alguém definiu o status no painel. |
billing_reason | texto | Por que o pedido existe: purchase (compra avulsa), subscription_create (primeira cobrança de uma assinatura), subscription_cycle (renovação), manual (cobrança avulsa numa assinatura, pelo painel ou pela API), usage (cobrança por uso medido, no fechamento de um período). |
customer, customer_email | texto ou null | O comprador. |
subscription | texto ou null | sub_… quando o pedido pertence a uma assinatura. |
offer | texto ou null | ofr_… da oferta principal do pedido. |
order_type | texto ou null | Como o pedido nasceu: checkout, renewal (renovação), api (cobrança iniciada pelo lojista), trial_setup ou card_setup (cadastro de cartão sem cobrança). Pode ganhar valores novos. |
recurrence | texto ou null | initial, subsequent ou unscheduled nos pedidos de assinatura; null nas compras avulsas. |
currency | texto | Moeda do pedido. |
amount_total | inteiro | O total cobrado, na menor unidade, com desconto, frete e juros já aplicados. |
amount_refunded | inteiro | Quanto já foi estornado. |
amount_discount | inteiro | O desconto de cupons. |
amount_shipping | inteiro | O frete, em produtos físicos. |
amount_interest | inteiro | Os juros do parcelamento repassados ao comprador. |
installments | inteiro ou null | Em quantas parcelas foi pago. |
payment_method | texto ou null | credit_card, debit_card, pix, boleto ou wallet. |
provider | texto ou null | O provedor que processou, como pagarme, stripe, mercadopago ou asaas. |
coupon_codes | lista de texto | Os cupons aplicados. |
lines | lista | Os itens do pedido. Veja Linhas do pedido. |
shipping | objeto ou null | Só em pedidos com entrega: status da entrega, carrier, tracking_code, tracking_url e address, no mesmo formato do endereço do cliente. |
external_order_id | texto ou null | Um identificador seu, quando o pedido veio com um. Nos pedidos de uma cobrança na assinatura, o sch_… da cobrança. |
checkout_session | texto ou null | cs_… da sessão de checkout que gerou o pedido. Só no pedido pago pela sessão: as renovações da assinatura vêm com null, e você as liga ao seu sistema por subscription. |
client_reference_id | texto ou null | O seu identificador, copiado do client_reference_id da sessão de checkout. |
metadata | objeto | O metadata da sessão de checkout. {} nos outros pedidos. |
paid_at | inteiro ou null | Quando o pagamento foi confirmado. |
livemode | booleano | false quando o pagamento passou por uma conexão de teste do provedor. |
created | inteiro | Quando o pedido foi criado. |
Status do pedido
status | paid | Significado |
|---|---|---|
pending | false | Aguardando pagamento: PIX gerado e não pago, boleto emitido, cartão em análise. |
pre_authorized | false | Valor reservado no cartão, ainda não capturado. |
authorized | true | Pago. |
failed | false | O pagamento foi recusado ou expirou. |
canceled | false | Cancelado antes de ser pago. |
refund_pending | true | Estorno pedido e ainda não confirmado pelo provedor. |
partially_refunded | true | Parte do valor foi devolvida. amount_refunded diz quanto. |
refunded | true | Todo o valor foi devolvido. |
charged_back | true | O comprador contestou a cobrança no banco. |
Linhas do pedido
Cada item de lines:
| Campo | Tipo | Conteúdo |
|---|---|---|
description | texto ou null | O nome do item como apareceu no checkout. |
quantity | inteiro | Quantidade. |
unit_amount | inteiro ou null | Preço unitário, na menor unidade. |
amount | inteiro ou null | unit_amount vezes quantity. |
offer, product | texto ou null | ofr_… e prd_… do item. |
kind | texto | O papel do item no pedido: main (o item principal), bump (oferta adicional marcada no checkout), composition (linha composta pela equipe num link rápido) ou charge (linha de uma cobrança na assinatura). |
Ofertas
Uma oferta é o que o comprador pode pagar: um produto com preço, ciclo de cobrança e condições. É o equivalente ao price da Stripe. Cada oferta tem um link de checkout pronto.
Listar ofertas
GET /v1/offers| Parâmetro | Tipo | Conteúdo |
|---|---|---|
product | texto | Só ofertas desse produto (prd_…). |
active | true ou false | Só ofertas ativas, ou só as que não estão ativas. |
type | texto | one_time ou recurring. |
limit, starting_after, ending_before | Paginação. Ofertas vêm em ordem alfabética de nome. |
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 uma oferta
GET /v1/offers/{id}curl https://api.vipter.com/v1/offers/ofr_6e2b8d4f1a9c3e7b \
-H "Authorization: Bearer vk_live_…"Devolve o objeto offer.
O objeto offer
| Campo | Tipo | Conteúdo |
|---|---|---|
id | texto | ofr_… |
object | texto | "offer" |
name | texto | Nome da oferta. |
slug | texto ou null | O slug do link de checkout, quando a oferta tem um. |
product | objeto | id (prd_…) e name do produto. |
type | texto | one_time (compra avulsa) ou recurring (assinatura). |
billing_cycle | texto ou null | O intervalo de cobrança nas ofertas recorrentes: daily, biweekly, monthly, quarterly, half_yearly, yearly ou custom. null nas avulsas. |
custom_billing_days | inteiro ou null | Com billing_cycle custom, o intervalo em dias. |
cycle_limit | inteiro ou null | Quantas cobranças a assinatura faz no total. null é sem limite. |
trial_days | inteiro ou null | Dias de teste grátis. null quando a oferta não tem teste. |
setup_charge | booleano | true quando a primeira cobrança tem um valor diferente das demais (first_charge_amount em prices). |
status | texto | O status no catálogo, como active ou inactive. |
active | booleano | true quando status é active. Só ofertas ativas aceitam compras. |
prices | lista | Um preço por moeda: id, currency, unit_amount, first_charge_amount (o valor da primeira cobrança quando é diferente, senão null) e default (a moeda que o checkout usa quando o comprador não escolhe). |
checkout_url | texto ou null | O link de checkout da oferta, no domínio próprio da loja quando há um. null quando o link está desligado nas configurações da oferta. Aceita os parâmetros de URL. |
livemode | booleano | true em produção. |
created | inteiro ou null | Quando a oferta foi criada. |
Produtos
Um produto agrupa ofertas: "Plano Pro" é o produto, "Plano Pro mensal" e "Plano Pro anual" são ofertas dele.
Listar produtos
GET /v1/products| Parâmetro | Tipo | Conteúdo |
|---|---|---|
active | true ou false | Só produtos ativos, ou só os que não estão ativos. |
limit, starting_after, ending_before | Paginação. |
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 um produto
GET /v1/products/{id}curl https://api.vipter.com/v1/products/prd_1a5c9e3b7d2f6a8c \
-H "Authorization: Bearer vk_live_…"Devolve o objeto product.
O objeto product
| Campo | Tipo | Conteúdo |
|---|---|---|
id | texto | prd_… |
object | texto | "product" |
name | texto | Nome do produto. |
description | texto ou null | Descrição. |
type | texto ou null | O tipo do produto, como digital ou physical. Pode ganhar valores novos. |
status | texto | O status no catálogo, como active ou inactive. |
active | booleano | true quando o produto está ativo e não foi excluído. |
product_family | texto ou null | O ID da família de produtos, quando o produto pertence a uma. |
metadata | objeto | Dados livres gravados no produto. |
livemode | booleano | true em produção. |
created | inteiro ou null | Quando o produto foi criado. |
Sessões de checkout
Uma sessão de checkout é um checkout aberto pelo seu sistema para uma oferta, com o comprador, a sua referência e o destino depois do pagamento já definidos. A resposta traz a url para onde você manda o comprador. Quando ele paga, a sessão passa a apontar para customer, order e subscription, e o evento checkout.session.completed sai no catálogo 2026-11-01. É o equivalente da Checkout Session da Stripe. O passo a passo com código está em SaaS: do cadastro ao dashboard.
A sessão fixa a oferta, o pacote, a moeda e o cupom: o comprador não troca nenhum deles na página. Com customer ou customer_email, o campo de e-mail vem preenchido e travado. Criar a sessão não cria nada no provedor de pagamento; isso acontece quando o comprador preenche o formulário, como num link de checkout comum.
Criar uma sessão
POST /v1/checkout/sessionsPede o escopo write. Envie uma Idempotency-Key para repetir a chamada depois de um erro de rede sem criar duas sessões.
| Parâmetro | Tipo | Conteúdo |
|---|---|---|
offer | texto | Obrigatório, a menos que line_items venha. A oferta: ofr_… ou o slug do link de checkout. Uma oferta que não existe ou é de outra loja recebe 404 resource_missing com param offer. |
line_items[0][price], line_items[0][quantity] | texto, inteiro | Apelido no formato da Stripe: price é a oferta e quantity é o pacote. Só um item. Quando offer ou pack também vêm, eles valem. |
pack | inteiro | O pacote, em unidades (1 a 999). A oferta precisa ter um pacote com essa quantidade, senão 400 pack_unavailable. |
currency | texto | Moeda da sessão, ISO 4217 (brl, usd). Precisa existir em prices da oferta, senão 400 currency_unsupported, com as moedas disponíveis na message. Sem ela, o checkout abre na moeda padrão da oferta e o comprador pode trocar, como num link comum. |
customer | texto | cust_… de um cliente da loja. O e-mail dele vem preenchido e travado; nome, telefone e documento vêm preenchidos. Um ID que não existe recebe 404 resource_missing com param customer. |
customer_email | texto | E-mail do comprador, quando ele ainda não é cliente. O campo vem preenchido e travado: o checkout só aceita pagar com esse e-mail. Guardado em minúsculas. |
customer_name | texto | Nome para preencher o formulário, de 2 a 120 caracteres. O comprador pode alterar. |
client_reference_id | texto | O seu identificador, até 200 caracteres: o ID do usuário no seu sistema. É copiado para o pedido e para a assinatura, e filtra a lista de sessões. |
metadata | objeto | Até 50 chaves; chave com até 40 caracteres, valor com até 500. Números e booleanos são guardados como texto; null remove a chave. Fica na sessão e é copiado para o pedido. |
subscription_data[metadata] | objeto | O metadata da assinatura que a sessão criar, nas ofertas recorrentes. Sem ele, a assinatura recebe o metadata da sessão. Mesmos limites. |
discounts[0][coupon] | texto | O código de um cupom ativo da loja, sem diferenciar maiúsculas. Vem aplicado no checkout. Cupom inexistente ou inativo recebe 400 coupon_invalid. Só um cupom. |
success_url | texto | Para onde o comprador vai depois de pagar. https://, até 2000 caracteres (http://localhost é aceito em desenvolvimento). O texto {CHECKOUT_SESSION_ID} é trocado pelo id da sessão. Veja Depois do pagamento. |
cancel_url | texto | Vira o link de voltar no topo do checkout. Também é para onde o comprador vai se abrir a sessão depois de ela expirar. Mesmas regras de formato. |
redirect_delay | inteiro | Segundos que a página de obrigado do Vipter fica na tela antes de ir para success_url: de 0 a 30, padrão 5. |
expires_at | inteiro | Quando a sessão expira, em segundos Unix: entre 30 minutos e 24 horas a partir de agora, senão 400 parameter_invalid. Padrão: 24 horas. |
locale | texto | Idioma do comprador, gravado na sessão e devolvido no objeto: en, pt, es, fr, de, it, ja, ko, ru ou zh. |
mode | texto | payment ou subscription. Opcional: o Vipter deduz pelo tipo da oferta e devolve no objeto. Um valor diferente do tipo da oferta recebe 400 mode_mismatch. |
allow_promotion_codes | booleano | Aceito por compatibilidade com a Stripe e ignorado por enquanto. |
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"
}'A resposta é 201 com o 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
}Mande o comprador para url. O endereço fica em https://pay.vipter.com/c/{id} ou, quando a loja tem um domínio próprio ativo, nesse domínio.
Erros desta chamada, além dos erros gerais:
code | HTTP | Significado |
|---|---|---|
resource_missing | 404 | A oferta (param offer) ou o cliente (param customer) não existe na loja. |
offer_unavailable | 400 | A oferta existe mas não pode ser vendida agora: link de checkout desligado, oferta arquivada, sem preço, ou a loja não está em condição de vender. A message diz o motivo. |
pack_unavailable | 400 | A oferta não tem um pacote com a quantidade pedida em pack. |
currency_unsupported | 400 | A oferta não tem preço na moeda pedida. |
mode_mismatch | 400 | mode não bate com o tipo da oferta. |
coupon_invalid | 400 | O cupom não existe ou está inativo. param é discounts[0].coupon. |
selling_blocked | 403 | A loja está impedida de vender porque a mensalidade do Vipter está em atraso. O type é permission_error. |
Buscar uma sessão
GET /v1/checkout/sessions/{id}curl https://api.vipter.com/v1/checkout/sessions/cs_4b7e2d9a1c3f5e8b0d2a6c4e1f3b \
-H "Authorization: Bearer vk_live_…"Devolve o objeto checkout.session. É a chamada que a sua página de sucesso faz com o id que chegou na success_url: confira status e payment_status em vez de confiar só no redirecionamento. Depois do pagamento, customer, order e, nas ofertas recorrentes, subscription vêm preenchidos. subscription pode chegar alguns segundos depois de order, quando o aviso do provedor é processado; se ele ainda vier null, consulte de novo ou espere o webhook.
Listar sessões
GET /v1/checkout/sessions| Parâmetro | Tipo | Conteúdo |
|---|---|---|
customer | texto | Só sessões desse cliente (cust_…), incluindo as que ganharam o cliente ao serem pagas. |
client_reference_id | texto | Só sessões criadas com esse client_reference_id, igualdade exata. |
status | texto | open, complete ou expired. |
payment_status | texto | unpaid, paid ou pending. |
limit, starting_after, ending_before | Paginação. |
curl "https://api.vipter.com/v1/checkout/sessions?client_reference_id=user_8213&status=complete" \
-H "Authorization: Bearer vk_live_…"Devolve um objeto list de checkout.session, da mais nova para a mais antiga.
Expirar uma sessão
POST /v1/checkout/sessions/{id}/expireFecha uma sessão aberta antes do prazo: o comprador que abrir a url depois disso vai para cancel_url, ou vê uma página de link indisponível. Útil quando o usuário desistiu no seu sistema ou escolheu outro plano. Pede o escopo write.
curl -X POST https://api.vipter.com/v1/checkout/sessions/cs_4b7e2d9a1c3f5e8b0d2a6c4e1f3b/expire \
-H "Authorization: Bearer vk_live_…"Devolve o objeto com status: "expired" e gera o evento checkout.session.expired. Uma sessão que já está complete ou expired recebe 400 checkout_session_not_open, com o status atual na message.
O objeto checkout.session
| Campo | Tipo | Conteúdo |
|---|---|---|
id | texto | cs_… |
object | texto | "checkout.session" |
status | texto | open, complete ou expired. Veja Status da sessão. |
payment_status | texto | unpaid, paid ou pending. |
url | texto ou null | O endereço do checkout. Só enquanto status é open; depois vem null. |
mode | texto ou null | payment numa oferta avulsa, subscription numa recorrente. |
offer | objeto | id (ofr_…) e name da oferta. |
pack | inteiro ou null | O pacote fixado, em unidades. |
currency | texto ou null | A moeda fixada na criação. null quando a sessão deixou o comprador escolher. |
amount_total | inteiro ou null | O preço da oferta na moeda da sessão (ou na moeda padrão), na menor unidade: o valor da primeira cobrança, quando ela é diferente. Vem antes de cupom, frete e adicionais, e null nas sessões com pacote. O valor cobrado de fato está em amount_total do pedido. |
customer | texto ou null | cust_…: o que você passou, ou o cliente criado quando o comprador pagou. |
customer_email, customer_name | texto ou null | O que você passou; com customer, o e-mail e o nome do cliente. |
client_reference_id | texto ou null | O seu identificador. |
metadata | objeto | O que você enviou. |
subscription_data | objeto | metadata: o que você enviou em subscription_data[metadata], ou {}. |
discounts | lista | [{ "coupon": "CODIGO" }] quando a sessão tem cupom; senão []. |
success_url, cancel_url | texto ou null | Como você enviou, com o {CHECKOUT_SESSION_ID} ainda por trocar. |
redirect_delay | inteiro | Segundos antes do redirecionamento, de 0 a 30. |
locale | texto ou null | O idioma enviado. |
order | texto ou null | ord_… do pedido que a sessão gerou. Preenchido quando o comprador paga ou gera um PIX. |
subscription | texto ou null | sub_… da assinatura criada, nas ofertas recorrentes. |
expires_at | inteiro | Quando a sessão expira. |
completed_at | inteiro ou null | Quando a sessão passou a complete. |
livemode | booleano | true em produção. |
created | inteiro | Quando a sessão foi criada. |
Status da sessão
status | payment_status | Significado |
|---|---|---|
open | unpaid | O comprador ainda não pagou. Um cartão recusado deixa a sessão aberta: ele pode tentar de novo na mesma página. |
complete | paid | Pago: cartão aprovado, ou PIX pago. order está preenchido. |
complete | pending | O comprador gerou um PIX, ou o cartão ficou em análise, e o dinheiro ainda não entrou. order está preenchido com status: "pending". A sessão fica assim até o pagamento confirmar (paid) ou falhar (unpaid). |
complete | unpaid | O pagamento pendente não aconteceu: o PIX expirou ou a cobrança foi recusada depois da análise. A sessão não reabre; crie outra. |
expired | unpaid | O prazo passou sem pagamento, ou alguém chamou POST …/expire. |
Cada mudança gera um evento do catálogo 2026-11-01: checkout.session.completed quando a sessão passa a complete (com paid ou pending), checkout.session.async_payment_succeeded quando um pagamento pendente confirma, checkout.session.async_payment_failed quando ele falha, e checkout.session.expired. Uma sessão só completa uma vez.
Depois do pagamento
- O comprador paga na página do Vipter e vê a página de obrigado da loja.
- Com o pagamento confirmado, a página conta
redirect_delaysegundos e vai parasuccess_url, com{CHECKOUT_SESSION_ID}trocado peloidda sessão. Enquanto um PIX não é pago, a página fica aguardando e não redireciona. - A
success_urlda sessão tem prioridade sobre a URL de sucesso do link rápido, a da oferta e a padrão da loja. Semsuccess_url, vale a próxima da lista.
Quando o produto tem arquivos para download, a página não redireciona sozinha: mostra os arquivos e um botão para seguir para a success_url.
Um comprador que abre a url de uma sessão já paga é levado para a página de obrigado do pedido. Uma sessão expirada leva para cancel_url ou, sem ela, para uma página de link indisponível. As sessões vencidas são marcadas como expired por uma rotina periódica, e também na hora, se alguém abrir o link.
Portal do cliente
Criar uma sessão do portal
POST /v1/billing_portal/sessionsGera um link de entrada direta na área do cliente da loja para um cliente, sem o passo do código por e-mail. Serve para o botão "gerenciar assinatura" do seu sistema: o cliente troca o cartão, cancela ou vê as cobranças no portal do Vipter. Pede o escopo write.
| Parâmetro | Tipo | Conteúdo |
|---|---|---|
customer | texto | Obrigatório. cust_… do cliente. |
return_url | texto | Aceito por compatibilidade com a Stripe e devolvido no objeto. O portal ainda não tem um botão de voltar para ele. |
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=EXEMPLO-TOKEN",
"customer": "cust_9d2e4f6a8b1c3d5e",
"return_url": null,
"expires_at": 1790187300,
"livemode": true,
"created": 1790186400
}| Campo | Tipo | Conteúdo |
|---|---|---|
id | texto | bps_…. A sessão não pode ser consultada depois. |
object | texto | "billing_portal.session" |
url | texto | O link de entrada. Vale por 15 minutos e serve para uma entrada: ao ser aberto, cria a sessão do portal no navegador do cliente e deixa de funcionar. Gere um link novo a cada clique, na hora do clique, e redirecione o cliente para ele; não guarde nem envie por e-mail. |
customer | texto | O cliente. |
return_url | texto ou null | O que você enviou. |
expires_at | inteiro | Quando o link deixa de valer. |
livemode | booleano | true em produção. |
created | inteiro | Quando a sessão foi criada. |
O link abre no endereço do portal da loja: o domínio próprio, quando há um ativo, ou portal.vipter.com/{slug}.
code | HTTP | Significado |
|---|---|---|
resource_missing | 404 | O cliente não existe na loja. |
portal_disabled | 400 | A área do cliente está desligada nas configurações da loja. Veja Área do cliente. |
portal_unavailable | 400 | A loja ainda não tem um endereço de portal: falta um slug ou um domínio próprio ativo. |
Eventos
Todo evento que o Vipter gera fica guardado e pode ser consultado pela API, nos dois catálogos: serve para conferir o que o seu endpoint recebeu, recuperar o que ele não recebeu enquanto esteve desativado e pedir um reenvio por código. O corpo de cada evento é o mesmo envelope que chega ao endpoint, e api_version diz de qual catálogo ele é. Os eventos de teste não aparecem na lista.
Listar eventos
GET /v1/events| Parâmetro | Tipo | Conteúdo |
|---|---|---|
type | texto | Um tipo exato, como invoice.paid, ou um padrão com *, como invoice.* ou customer.subscription.*. |
created[gte], created[gt], created[lte], created[lt] | inteiro | Só eventos criados a partir de, depois de, até ou antes desse momento, em segundos Unix. |
limit, starting_after, ending_before | Paginação. O cursor é o id de um 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, "…": "…" }
}
}
]
}A lista vem do mais novo para o mais antigo e junta os dois catálogos: o mesmo fato aparece duas vezes quando a loja tem endpoints nas duas versões, uma com cada nome. Um evento da versão 2026-09-01 vem sem pending_webhooks e request, como no envelope dessa versão.
Buscar um evento
GET /v1/events/{id}curl https://api.vipter.com/v1/events/evt_5a7c9e1b3d5f7a9c1e3b5d7f \
-H "Authorization: Bearer vk_live_…"Devolve o objeto event, o mesmo JSON que o endpoint recebeu no POST. Na versão 2026-11-01, pending_webhooks é calculado na hora da consulta: quantas entregas do evento ainda não deram certo. Um evento de teste pode ser buscado pelo id que a chamada de teste devolve. Um id que não existe na loja recebe 404 resource_missing.
Reenviar um evento
POST /v1/events/{id}/resendColoca o evento de novo na fila de entrega, com o mesmo id e o mesmo created, e envia na hora. Pede o escopo write.
| Parâmetro | Tipo | Conteúdo |
|---|---|---|
webhook_endpoint | texto | O id de um endpoint. Com ele, só esse endpoint recebe o evento: a entrega dele volta a pending com as tentativas zeradas, ou é criada se o endpoint nunca teve uma, como um endpoint criado depois do evento ou que estava desativado quando ele saiu. Sem ele, toda entrega que o evento já tem é reenviada, inclusive as que já tinham dado certo. |
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 é quantas entregas foram colocadas na fila. Sem webhook_endpoint, é o número de endpoints que já tinham uma entrega do evento; 0 quando nenhum tinha. O endpoint precisa receber o catálogo do evento: um evento invoice.paid (2026-11-01) não pode ser enviado a um endpoint 2026-09-01. Reenviar para um endpoint desativado encerra a entrega com endpoint disabled; ative-o antes. Se a entrega falhar, ela segue o calendário de tentativas desde o início. O seu controle de idempotência trata o reenvio como repetição.
code | HTTP | Significado |
|---|---|---|
resource_missing | 404 | O evento, ou o endpoint em webhook_endpoint, não existe na loja. |
invalid_event_type | 400 | O endpoint recebe outro catálogo. A message diz qual é o do evento e qual é o do endpoint. |
Endpoints de webhook
Os mesmos endpoints da página GeralIntegraçõesAutomaçõesWebhooks do painel, criados e geridos por código. Um endpoint criado pela API aparece no painel como qualquer outro, e as regras são as de Receber eventos no seu sistema: URL https://, um catálogo por endpoint, novas tentativas e desativação automática. O id de um endpoint é um UUID; trate-o como texto opaco.
Listar endpoints
GET /v1/webhook_endpointscurl https://api.vipter.com/v1/webhook_endpoints \
-H "Authorization: Bearer vk_live_…"Devolve um objeto list com todos os endpoints da loja, do mais antigo para o mais novo, sem filtros. O secret não vem na lista.
Criar um endpoint
POST /v1/webhook_endpointsPede o escopo write. A resposta é 201 e traz o secret (whsec_…) uma única vez: guarde-o no seu servidor para verificar a assinatura. Depois, só trocar o segredo gera outro.
| Parâmetro | Tipo | Conteúdo |
|---|---|---|
url | texto | Obrigatório. Endereço https:// do seu servidor, até 2000 caracteres, num host público. Um host da rede interna (localhost, 10.x, 192.168.x, nomes .internal ou .local) recebe 400 private_host; um endereço sem https:// recebe 400 invalid_url. |
description | texto | Um lembrete para a equipe, até 200 caracteres. |
enabled_events | lista | Os tipos que o endpoint recebe, até 100: nomes do catálogo da versão, como invoice.paid, ou padrões com *, como customer.subscription.*. Omitida, vazia ou ["*"], o endpoint recebe todos os eventos do catálogo, inclusive os criados no futuro. Um nome que não existe na versão recebe 400 invalid_event_type. |
api_version | texto | O catálogo que o endpoint recebe: 2026-11-01 (padrão) ou 2026-09-01. Não muda depois de criado. |
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": "Faturamento do 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": "Faturamento do 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_EXEMPLO0000000000000000000000000000",
"livemode": true,
"created": 1790186400
}O endpoint nasce ativo e passa a receber os eventos criados a partir daí; eventos anteriores não chegam a ele, a não ser por um reenvio com webhook_endpoint.
Buscar um endpoint
GET /v1/webhook_endpoints/{id}curl https://api.vipter.com/v1/webhook_endpoints/0b7e2c4a-9d1f-4e3b-8a6c-5f2d1e0c9b8a \
-H "Authorization: Bearer vk_live_…"Devolve o objeto webhook_endpoint, sem secret. Um id que não existe na loja recebe 404 resource_missing.
Alterar um endpoint
POST /v1/webhook_endpoints/{id}Pede o escopo write. Só os campos enviados mudam.
| Parâmetro | Tipo | Conteúdo |
|---|---|---|
url | texto | O endereço novo, com as mesmas regras da criação. As entregas seguintes já vão para ele. |
description | texto ou null | null apaga a descrição. |
enabled_events | lista | Substitui a lista inteira. ["*"] volta a receber todos os eventos do catálogo. Pelo painel essa lista não pode ser alterada; pela API, pode. |
status | texto | enabled ou disabled. É a chave Ativo do painel: enabled zera consecutive_failures e apaga disabled_reason, o que religa um endpoint desativado sozinho. Enquanto está disabled, os eventos novos não são guardados para ele. |
Um api_version no corpo é ignorado: a versão não muda. Para passar ao outro catálogo, crie um endpoint novo e remova o antigo.
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" }'Devolve o objeto webhook_endpoint atualizado.
Remover um endpoint
DELETE /v1/webhook_endpoints/{id}Apaga o endpoint, o segredo e o histórico de entregas dele; as entregas pendentes param. Pede o escopo write.
{ "id": "0b7e2c4a-9d1f-4e3b-8a6c-5f2d1e0c9b8a", "object": "webhook_endpoint", "deleted": true }Trocar o segredo
POST /v1/webhook_endpoints/{id}/rotate_secretGera um segredo novo e devolve o objeto webhook_endpoint com ele em secret, uma única vez. O segredo anterior continua assinando por 24 horas: nesse período, cada entrega traz dois v1= no cabeçalho Vipter-Signature, e o seu servidor pode trocar a variável de ambiente sem perder eventos. Veja Trocar o segredo. Pede o escopo write.
curl -X POST https://api.vipter.com/v1/webhook_endpoints/0b7e2c4a-9d1f-4e3b-8a6c-5f2d1e0c9b8a/rotate_secret \
-H "Authorization: Bearer vk_live_…"Enviar um evento de teste
POST /v1/webhook_endpoints/{id}/testCria um evento customer.created de teste e entrega só a este endpoint, no catálogo dele: um objeto customer no formato da versão do endpoint, com "test": true a mais e livemode: false. É o mesmo que o botão Enviar evento de teste do card do endpoint no painel. O evento não passa por automações, e-mails, notas fiscais, áreas de membros nem pixels, não chega aos outros endpoints e não aparece em GET /v1/events. O formato está no catálogo. Pede o escopo 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"
}A entrega sai na hora. Acompanhe-a em GET /v1/webhook_endpoints/{id}/deliveries ou em Entregas recentes no painel. Se o seu servidor falhar, ela segue o calendário de tentativas de um evento real e conta para a desativação automática. Com o endpoint desativado, a entrega termina na hora com endpoint disabled.
Listar as entregas de um endpoint
GET /v1/webhook_endpoints/{id}/deliveries| Parâmetro | Tipo | Conteúdo |
|---|---|---|
status | texto | pending, delivering, succeeded, failed ou exhausted. |
limit, starting_after, ending_before | Paginação. O cursor é o id de uma 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
}
]
}Devolve um objeto list de webhook_delivery, da mais nova para a mais antiga: uma entrega por evento que o endpoint recebeu. O que cada status significa está em Tentativas, desativação e reenvio.
O objeto do endpoint
| Campo | Tipo | Conteúdo |
|---|---|---|
id | texto | UUID do endpoint. |
object | texto | "webhook_endpoint" |
url | texto | O endereço que recebe os eventos. |
description | texto ou null | A descrição. |
enabled_events | lista | Os tipos e padrões que o endpoint recebe. ["*"] quando recebe todos os eventos do catálogo. |
api_version | texto | 2026-11-01 ou 2026-09-01: o catálogo que o endpoint recebe. |
status | texto | enabled ou disabled. |
disabled_reason | texto ou null | O motivo, quando o Vipter desativou o endpoint sozinho: auto-disabled after 20 exhausted deliveries. null nos outros casos, inclusive quando a equipe desligou a chave. |
consecutive_failures | inteiro | Entregas seguidas que esgotaram as tentativas. Em 20, o endpoint é desativado; uma entrega succeeded zera. |
created_via | texto | dashboard ou api: onde o endpoint foi criado. |
secret | texto | O segredo de assinatura, whsec_…. Só na resposta de criar e de trocar o segredo. |
livemode | booleano | true em produção. |
created | inteiro | Quando o endpoint foi criado. |
O objeto da entrega
| Campo | Tipo | Conteúdo |
|---|---|---|
id | texto | UUID da entrega. |
object | texto | "webhook_delivery" |
event | texto | evt_… do evento entregue. Busque-o em GET /v1/events/{id}. |
webhook_endpoint | texto | O endpoint. |
status | texto | pending (na fila), delivering (sendo enviada), succeeded (o seu servidor respondeu 2xx), failed (a última tentativa falhou e há outra agendada) ou exhausted (as tentativas acabaram, ou o endpoint estava desativado). |
attempts | inteiro | Quantas tentativas já foram feitas, de até 8. Volta a 0 num reenvio. |
next_attempt_at | inteiro ou null | Quando a próxima tentativa está agendada. Só com status pending ou failed. |
last_attempt_at | inteiro ou null | Quando foi a última tentativa. |
delivered_at | inteiro ou null | Quando o seu servidor respondeu 2xx. |
last_response_status | inteiro ou null | O código HTTP da última resposta. null quando não houve resposta. |
last_error | texto ou null | O erro da última tentativa: HTTP 500, timeout, endpoint disabled ou a mensagem de rede. null quando deu certo. |
last_duration_ms | inteiro ou null | Quanto tempo a última tentativa levou, em milissegundos. |
created | inteiro | Quando a entrega foi criada. |
Em breve
O que ainda não existe na API: um filtro por client_reference_id em GET /v1/subscriptions e em GET /v1/customers. Para achar a assinatura de um usuário seu, use o campo subscription da sessão de checkout que a criou, ou guarde o sub_… que chega no webhook. O uso medido, antes listado aqui, está no ar: veja Uso medido.
O que fazer a seguir
- Siga o roteiro SaaS: do cadastro ao dashboard, com código para criar a sessão, receber o webhook e cobrar o uso no fim do mês.
- Leia as convenções da API antes de escrever o cliente: erros, paginação, limites.
- Para cobrar por consumo sem manter a conta do seu lado, siga Uso medido (Meters).
- Para saber de uma venda na hora, em vez de consultar a API, receba webhooks. Crie o endpoint por código em Endpoints de webhook e confira o que chegou em Eventos.
Convenções da API
O endereço base, o cabeçalho de versão, o formato de requisição e resposta, como dinheiro, datas e IDs são representados, a tabela de erros, idempotência, paginação, limites de requisições, o cabeçalho Request-Id e o que muda em relação à Stripe.
SaaS: do cadastro ao dashboard
Roteiro com código para cobrar um usuário do seu SaaS pelo Vipter: criar a sessão de checkout com o ID do usuário, redirecionar, confirmar o pagamento pela API ou pelo webhook checkout.session.completed, tratar PIX, idempotência, cobrar o uso do mês no cartão salvo, cancelar e trocar de plano, o que guardar e como conviver com a Stripe.