Migrar da Stripe para o Vipter
Guia para quem já cobra com a Stripe e vai passar a cobrar pelo Vipter, no todo ou só no Brasil: o que corresponde a quê (Price → oferta, Invoice → pedido), o que não migra (cartões salvos e assinaturas em curso), as mudanças no código de checkout, webhooks, cobranças avulsas e uso medido, com diffs em Node.js, e um plano de corte em cinco etapas que mantém os dois lado a lado durante a transição.
A API do Vipter foi desenhada para quem já integrou a Stripe: os mesmos nomes de objeto e de evento, o mesmo formato de erro, a mesma paginação, a mesma Idempotency-Key, o mesmo esquema de assinatura de webhook. O que muda é o que está por trás: no Vipter a loja vende pelos próprios adquirentes brasileiros (PIX, cartão parcelado, boleto) e o checkout é sempre o hospedado. Esta página lista as correspondências, o que não tem equivalente, o que trocar em cada trecho de código e em que ordem fazer o corte.
A migração é da camada de cobrança, não necessariamente do adquirente: a loja pode conectar a própria conta Stripe ao Vipter para continuar processando cartões por ela, e somar a Pagar.me, o Mercado Pago ou o Asaas para PIX e parcelamento. O código do seu sistema fala só com a API do Vipter, qualquer que seja o adquirente por trás.
Antes de começar
- Uma loja no Vipter com os provedores de pagamento conectados e, para testar, uma conexão de teste.
- Uma chave de API com o escopo
writee um endpoint de webhook na versão2026-11-01. - A lista dos
price_…da Stripe que o seu sistema usa hoje e dos eventos que ele trata.
O que corresponde a quê
| Stripe | Vipter | O que muda |
|---|---|---|
sk_live_… | vk_live_… | Mesmo uso (Authorization: Bearer). Não existe vk_test_: veja Testar sem modo de teste. |
Stripe-Version | Vipter-Version | Datas, como na Stripe. Atual: 2026-11-01. |
| Product | product (prd_) | Igual. |
| Price | offer (ofr_) | A oferta carrega o preço por moeda, o ciclo, o teste grátis, o limite de ciclos e o pacote. Não há price_data inline: o preço nasce no painel ou no catálogo, nunca na chamada. |
| Customer | customer (cust_) | Pede documento (CPF ou CNPJ) e telefone, exigidos pelos adquirentes brasileiros. |
| Subscription | subscription (sub_) | status igual (trialing, active, past_due, paused, canceled) mais expired. offer no lugar de items[].price. Uma oferta por assinatura. |
| Invoice e PaymentIntent | order (ord_) | Cada cobrança é um pedido, com billing_reason (purchase, renewal, manual, usage). Não há PaymentIntent nem Charge separados. |
| InvoiceItem + Invoice paga na hora | subscription_charge (sch_) | Uma chamada só: POST /v1/subscriptions/{id}/charges com o valor. Gera um pedido. |
| Checkout Session | checkout.session (cs_) | Mesmos campos principais (client_reference_id, customer_email, metadata, success_url com {CHECKOUT_SESSION_ID}, cancel_url). Um line_item só; quantity é o pacote. |
| Billing Portal Session | billing_portal.session | Igual: customer, return_url, url de uso único. |
| Coupon e Promotion Code | discounts[0].coupon com o código do cupom | O cupom é criado no painel. |
| Billing Meter e Meter Event | billing.meter, billing.meter_event | Mesmos campos. payload.customer_id; stripe_customer_id também é aceito como chave, para não mexer no código. |
Price com recurring.usage_type: metered | usage_item na oferta ou na assinatura | Preço por unidade, franquia, faixas graduadas, limiar de cobrança. |
Webhook Endpoint, whsec_…, Stripe-Signature | webhook_endpoint, whsec_…, Vipter-Signature | Mesmo esquema t=…,v1=…, HMAC SHA-256 de t.corpo. Veja Assinatura. |
Event (evt_) | event (evt_) | Mesmos nomes no catálogo 2026-11-01. O data.object é o objeto do Vipter. |
Sem equivalente
- PaymentIntent, PaymentMethod, SetupIntent, Charge. O pedido é a unidade; o cartão salvo é implícito na assinatura e só é cobrado por
POST /v1/subscriptions/{id}/chargesou pelas renovações. expand[], Search API, Tax Rates, Invoice em rascunho, Quotes, Payment Links por API. Os objetos já vêm com o que precisam (offereproductdentro da assinatura, por exemplo). Links de checkout saem do painel e aceitam parâmetros de URL.- Vários
line_itemsnuma sessão. Uma oferta por sessão. Para vender um conjunto, crie a oferta do conjunto. - Modo de teste. Os testes rodam na mesma loja, com uma conexão de teste do provedor; os pedidos saem com
livemode: false.
O que não migra
- Cartões salvos. O token de um cartão pertence ao adquirente e à integração que o criou. O Vipter não importa tokens, nem mesmo os da sua conta Stripe quando ela está conectada ao Vipter: os cartões salvos pela sua integração direta não são reaproveitados. Cada cliente precisa pagar uma vez pelo checkout do Vipter; a partir daí o cartão fica salvo na nova assinatura. O plano de corte abaixo é desenhado em volta disso.
- Assinaturas em curso. Não há endpoint de importação de assinatura. A assinatura nova nasce de uma sessão de checkout paga. Para não cobrar duas vezes o mesmo período, use uma oferta de migração com
trial_daysigual aos dias que faltam no ciclo da Stripe, ou cancele a assinatura da Stripe com reembolso proporcional no dia em que a sessão completar. - Histórico de faturas e eventos. Fica na Stripe. Exporte o que precisar antes de desligar a conta e guarde no seu banco o par
(provider, subscription_id)de cada cliente.
O que trocar no código
Os exemplos usam Node.js com fetch. O switch por tipo de evento, os IDs de usuário em client_reference_id e a estrutura de "um endpoint por provedor" vêm de Convivendo com a Stripe.
Criar o checkout
-const session = await stripe.checkout.sessions.create({
- mode: 'subscription',
- line_items: [{ price: 'price_1Pq…', quantity: 1 }],
- client_reference_id: user.id,
- customer_email: user.email,
- metadata: { user_id: user.id, plan: 'pro' },
- success_url: 'https://app.example.com/billing/success?session_id={CHECKOUT_SESSION_ID}',
- cancel_url: 'https://app.example.com/billing/plans',
-});
-redirect(session.url);
+const res = await fetch('https://api.vipter.com/v1/checkout/sessions', {
+ method: 'POST',
+ headers: {
+ Authorization: `Bearer ${process.env.VIPTER_API_KEY}`,
+ 'Content-Type': 'application/json',
+ 'Idempotency-Key': `${user.id}:pro:${Date.now()}`,
+ },
+ body: JSON.stringify({
+ offer: 'ofr_6e2b8d4f1a9c3e7b', // a oferta que substitui o price
+ client_reference_id: user.id,
+ customer_email: user.email,
+ metadata: { user_id: user.id, plan: 'pro' },
+ success_url: 'https://app.example.com/billing/success?session_id={CHECKOUT_SESSION_ID}',
+ cancel_url: 'https://app.example.com/billing/plans',
+ }),
+});
+const session = await res.json();
+redirect(session.url);O mode sai: o Vipter deduz da oferta. Guarde uma tabela price_… → ofr_… no seu código ou no banco durante a transição.
Confirmar o pagamento
-const session = await stripe.checkout.sessions.retrieve(sessionId, { expand: ['subscription'] });
-if (session.payment_status === 'paid') activate(session.client_reference_id, session.subscription.id);
+const session = await vipter(`/checkout/sessions/${sessionId}`);
+if (session.payment_status === 'paid') activate(session.client_reference_id, session.subscription);session.subscription já é o sub_…; para o objeto inteiro, GET /v1/subscriptions/{id}. Um PIX fica payment_status: pending até ser pago e então sai checkout.session.async_payment_succeeded, o mesmo nome que a Stripe usa para boleto.
Verificar o webhook
-const event = stripe.webhooks.constructEvent(rawBody, req.headers['stripe-signature'], process.env.STRIPE_WEBHOOK_SECRET);
+const event = verifyVipter(rawBody, req.headers['vipter-signature'], process.env.VIPTER_WEBHOOK_SECRET);O esquema é o mesmo (t=…,v1=…, HMAC SHA-256 sobre `${t}.${rawBody}`), então a função que você já tem serve com outro cabeçalho e outro segredo. O código completo da verificação está em Assinatura dos webhooks.
Tratar os eventos
| Evento | Na Stripe, data.object é | No Vipter, data.object é | O que ajustar |
|---|---|---|---|
checkout.session.completed | Checkout Session | checkout.session | subscription e customer já são IDs; order no lugar de payment_intent/invoice. |
invoice.paid | Invoice | order (object: "order") | amount_total no lugar de amount_paid; subscription e customer iguais; billing_reason diz se é renovação, compra ou cobrança avulsa. |
invoice.payment_failed | Invoice | order com status: failed | Igual ao anterior. |
customer.subscription.created / updated / deleted | Subscription | subscription | offer no lugar de items.data[0].price. updated traz data.previous_attributes como na Stripe. |
customer.subscription.paused / resumed | Subscription | subscription | Iguais. |
customer.created / updated | Customer | customer | document e address no formato do Vipter. |
Os eventos que só existem no Vipter, subscription_charge.succeeded|failed, order.refunded|partially_refunded|charged_back e billing.meter.error_report_triggered, estão no catálogo de eventos.
Cobrança avulsa
-await stripe.invoiceItems.create({ customer, amount: 1990, currency: 'brl', description: 'Excedente de uso' });
-const invoice = await stripe.invoices.create({ customer, auto_advance: true });
-await stripe.invoices.pay(invoice.id);
+const charge = await vipter(`/subscriptions/${subscriptionId}/charges`, {
+ method: 'POST',
+ idempotencyKey: `usage:${subscriptionId}:2026-09`,
+ body: { amount: 1990, description: 'Excedente de uso', metadata: { period: '2026-09' } },
+});
+// charge.status: succeeded | pending | failed; charge.order é o ord_…A recusa do cartão volta como 402 card_error, com a cobrança falhada em error.subscription_charge. A Idempotency-Key é obrigatória. Veja Cobrar o cartão salvo.
Uso medido
-await stripe.billing.meterEvents.create({
- event_name: 'api_calls',
- payload: { stripe_customer_id: customerId, value: '250' },
- identifier: requestId,
-});
+await vipter('/billing/meter_events', {
+ method: 'POST',
+ body: { event_name: 'api_calls', payload: { customer_id: customerId, value: '250' }, identifier: requestId },
+});payload.stripe_customer_id também é aceito, então até essa linha pode ficar como está. O preço do uso sai da oferta ou de POST /v1/subscriptions/{id}/usage_items, e a cobrança acontece no fim do ciclo ou ao atingir o limiar. Veja Uso medido.
Portal do cliente
-const portal = await stripe.billingPortal.sessions.create({ customer, return_url });
+const portal = await vipter('/billing_portal/sessions', { method: 'POST', body: { customer, return_url } });
redirect(portal.url);Plano de corte em cinco etapas
1. Inventário e catálogo
Liste os product_… e price_… em uso. Crie no Vipter os produtos e as ofertas equivalentes, com preço em reais, ciclo e teste grátis. Confira pela API com GET /v1/offers?type=recurring e monte a tabela price_… → ofr_….
2. Chave, endpoint e código
Crie a chave e um endpoint de webhook na versão 2026-11-01, apontando para uma rota nova (/webhooks/vipter). Aplique as trocas de código acima atrás de um sinalizador por usuário (billing_provider: 'stripe' | 'vipter'). Teste a sessão de checkout com a conexão de teste do provedor.
3. Novos clientes no Vipter
Ligue o sinalizador para quem se cadastra a partir de agora. A Stripe continua cobrando os clientes antigos. Os dois webhooks chegam em rotas separadas, com segredos separados, e o client_reference_id identifica o usuário nos dois.
4. Migrar os clientes antigos na renovação
Para cada assinatura da Stripe, perto da renovação, mande ao cliente um link de uma sessão de checkout do Vipter com client_reference_id igual ao ID dele e metadata.stripe_subscription com o sub_… antigo. No checkout.session.completed, leia esse metadata, cancele a assinatura da Stripe (cancel_at_period_end: true ou del com reembolso proporcional) e troque o sinalizador do usuário. Quem não pagar segue na Stripe até você decidir.
5. Desligar a Stripe
Quando a lista de assinaturas ativas na Stripe chegar a zero, desative o endpoint da Stripe, revogue as chaves sk_live_… e exporte faturas e clientes para o seu arquivo. Remova o sinalizador do código.
Diferenças que costumam surpreender
- O valor vem da oferta, não da chamada. Não existe
unit_amountnuma sessão de checkout. Para um preço novo, crie uma oferta. invoice.paidtraz um pedido, não uma fatura. O campo éobject: "order", comamount_total,linesebilling_reason.- Um
customer.subscription.updatedpara muita coisa: renovação, troca de plano, recuperação de inadimplência e troca de cartão. Diferencie pordata.previous_attributes, como na Stripe. - Documento e telefone são exigidos na criação de clientes pela API, porque os adquirentes brasileiros exigem.
- Parcelamento é decisão do comprador no checkout; o pedido informa o número de parcelas, e a assinatura cobra o valor cheio em cada ciclo.
- PIX e boleto são assíncronos: a sessão completa com
payment_status: pendinge o pagamento confirma depois porcheckout.session.async_payment_succeeded. - Sem
expand[]: a assinatura já vem comoffereproductresumidos; o resto é uma segunda chamada. - Limites: 100 requisições a cada 2 segundos por chave, com
X-RateLimit-*e429. Veja convenções.
Lista de verificação
- Tabela
price_… → ofr_…completa e ofertas ativas (GET /v1/offers). - Chave
vk_live_…comwriteem variável de ambiente;GET /v1/accountresponde 200. - Endpoint
2026-11-01criado; teste comPOST /v1/webhook_endpoints/{id}/testaceito pelo seu servidor. - Checkout de teste pago com a conexão de teste;
checkout.session.completedrecebido;client_reference_idconferido na assinatura. - Cobrança avulsa de teste com
Idempotency-Key; repetição devolve a mesma cobrança. - Deduplicação de eventos por
(provider, id). - Sinalizador por usuário e fluxo de migração na renovação.
- Exportação da Stripe guardada antes de desligar.
O que fazer a seguir
- Siga o roteiro completo com código em SaaS: do cadastro ao dashboard.
- Veja cada campo em Referência da API e cada evento no catálogo.
- Deixe um agente de IA fazer a troca de código com a skill da API.
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.
Integrar com agentes de IA
O que o Vipter publica para que um agente de IA (Claude Code, Cursor, Codex, ChatGPT e similares) integre a API sem ajuda humana: a skill vipter-api no formato Agent Skills, o llms.txt com instruções, toda a documentação em Markdown, o OpenAPI 3.1 e a coleção Postman; como apontar o agente para cada um, o que pedir a ele e o que nunca entregar a ele.