Vitrin Digital — Guia de Integração
Integre pagamentos na sua aplicação em minutos. Checkout transparente, Pix, Pix Automático (recorrência via SPI/BC), boleto, cartão de crédito, assinaturas e produtos avulsos — tudo via API REST.
Recursos:
- 📖 Swagger UI público — explore interativamente
- 📦 Schema OpenAPI — importe direto no Postman ou Insomnia
- 📜 Este guia — referência completa, passo a passo
⚡ Integre em 15 minutos
Os 5 passos abaixo cobrem o caso mais comum: receber um pagamento Pix e ser notificado quando ele for confirmado.
1. Cadastre-se e copie sua API key (humano, 2 min)
Cadastro, verificação documental e configuração do PIN são feitos no dashboard — não há cURL pra isso. Acesse:
vitrin.digital → Criar conta → preencha CNPJ, endereço, dados do sócio responsável, conta bancária para repasse e PIN → conclua o onboarding documental.
💡 Conta bancária no signup: o cadastro coleta sua conta de repasse já no formulário inicial (banco, agência, conta, dígito, tipo). Isso evita que seu primeiro recebível trave esperando essa info depois da aprovação. Pode editá-la depois em Configurações → Conta bancária (com PIN).
Como você recebe as chaves:
vd_test_*(sandbox) — gerada imediatamente no signup e exibida na tela final. Funciona já: pode criar cobranças sandbox, clientes, simular webhooks — tudo sem mover dinheiro real, sem aguardar nada.vd_live_*(produção) — gerada apenas após aprovação do cadastro pela equipe Vitrin (1-2 dias úteis). Quando aprovada, a chave plaintext é enviada uma única vez no email de aprovação. Guarde em local seguro — depois disso, só dá pra regenerar (e a anterior expira).
Use a vd_test_* nos exemplos abaixo pra testar enquanto aguarda aprovação. Troque pra vd_live_* quando for pro ar.
2. Assine os 4 contratos comerciais (humano, 3 min)
A primeira chamada de cobrança retorna HTTP 403 enquanto os contratos não forem assinados. A assinatura é feita pelo dono da org no dashboard com PIN — não há endpoint REST pra isso (é exigido pela natureza da assinatura eletrônica).
Acesse /dashboard/contract, revise os 4 documentos e confirme com seu PIN.
Sua aplicação pode checar programaticamente se a org está liberada via
GET /contracts/status/antes de tentar cobrar — útil pra mostrar mensagem amigável no seu UI.
3. Crie um cliente (1 min)
curl -X POST https://api.vitrin.digital/api/v1/customers/ \
-H "Authorization: Bearer vd_live_..." \
-H "Content-Type: application/json" \
-d '{
"name": "Maria da Silva",
"email": "maria@exemplo.com",
"cpf_cnpj": "12345678909",
"phone": "11988776655"
}'
Retorna { "id": "<customer_id>", ... }.
4. Crie uma cobrança Pix (2 min)
curl -X POST https://api.vitrin.digital/api/v1/checkout/pix/ \
-H "Authorization: Bearer vd_live_..." \
-H "Content-Type: application/json" \
-d '{
"customer_id": "<customer_id>",
"amount": "99.90",
"description": "Pedido #1234"
}'
Retorna o payment_id, o QR Code base64 (qr_code) e o copia-cola (copy_paste). Renderize isso na sua UI pro cliente final.
5. Configure o webhook e valide a assinatura (5 min)
Configure a URL HTTPS de webhook em /dashboard/webhooks (requer PIN). Quando o cliente pagar, a Vitrin envia:
{
"event": "PAYMENT_RECEIVED",
"payment": { "id": "pay_xxx", "status": "received", "amount": "99.90" }
}
Valide a assinatura HMAC no header X-Vitrin-Signature (SHA256 do body com seu webhook secret) — código de exemplo na seção 5. É só. Você está integrado.
Próximos passos
Depois desses 5 passos, você provavelmente vai querer:
- Boleto ou cartão de crédito → seção 3.2 e 3.3
- Cobrança recorrente → Pix Automático ou Assinaturas
- Antecipar recebíveis → endpoints
/anticipations/ - Cronograma de recebíveis →
GET /balance/scheduled/(Pix D+1, Boleto D+2, Cartão Nx D+30·n) - Defender chargebacks →
POST /payments/{id}/evidences/pra anexar comprovantes
SDKs oficiais
Em vez de chamar a API com fetch/requests cru, instale o pacote da sua linguagem. Os dois SDKs cobrem charges, customers, plans, subscriptions, products, balance + verificação de webhook HMAC + retry automático em 429/5xx + erros tipados (VitrinAuthError, VitrinValidationError com fieldErrors, VitrinRateLimitError, etc).
Node.js
Pacote: @vitrindigital/node — Node 18+, zero deps de runtime.
npm install @vitrindigital/node
# ou: pnpm add @vitrindigital/node yarn add @vitrindigital/node
import { Vitrin } from '@vitrindigital/node';
const vitrin = new Vitrin({ apiKey: process.env.VITRIN_API_KEY! });
const charge = await vitrin.charges.create({
customer_id: 'cus_xxx',
amount: 99.90,
billing_type: 'PIX',
idempotencyKey: `pedido-${orderId}`, // evita duplo-débito em retry
});
Validação de webhook (Express):
import { Webhooks } from '@vitrindigital/node';
app.post('/webhooks/vitrin', express.raw({ type: 'application/json' }), (req, res) => {
const event = Webhooks.constructEvent({
payload: req.body,
signature: req.header('x-vitrin-signature'),
timestamp: req.header('x-vitrin-timestamp'),
secret: process.env.VITRIN_WEBHOOK_SECRET!,
});
// process event.data ...
res.sendStatus(200);
});
Detalhes completos em sdk/node/README.md.
Python
Pacote: vitrin — Python 3.10+, única dep requests.
pip install vitrin
import os
from vitrin import Vitrin
vitrin = Vitrin(api_key=os.environ["VITRIN_API_KEY"])
charge = vitrin.charges.create(
customer_id="cus_xxx",
amount=99.90,
billing_type="PIX",
idempotency_key=f"pedido-{order_id}",
)
Validação de webhook (Flask):
from flask import Flask, request
from vitrin import webhooks, VitrinError
import os
app = Flask(__name__)
@app.post("/webhooks/vitrin")
def handle():
try:
event = webhooks.construct_event(
payload=request.get_data(),
signature=request.headers.get("X-Vitrin-Signature"),
timestamp=request.headers.get("X-Vitrin-Timestamp"),
secret=os.environ["VITRIN_WEBHOOK_SECRET"],
)
except VitrinError as e:
return str(e), 400
# process event.data ...
return "", 200
Detalhes completos em sdk/python/README.md.
Outras linguagens
Sem SDK oficial ainda. Use a API REST direto — exemplos em cURL ao longo deste guia.
1. Primeiros Passos
Cadastro e API key (humano, no dashboard)
Cadastro, verificação documental, definição do PIN e geração da primeira API key são feitos no dashboard web — não há endpoint REST pra essas ações. Isso é proposital: o cadastro precisa de upload de documentos físicos, validação CNPJ junto ao processador, e definição do PIN que protege ações sensíveis. Tudo isso é interação humana.
Como obter suas chaves:
- Acesse vitrin.digital → Criar conta
- Preencha CNPJ, razão social, endereço, dados do sócio responsável e defina seu PIN de 6 dígitos
- Anote a
vd_test_*que aparece na tela final — você já pode integrar e testar imediatamente - Aguarde aprovação (1-2 dias úteis). Quando aprovado, a
vd_live_*chega no seu email registrado — plaintext exibida uma única vez. Guarde em local seguro - Se perder a
vd_live_*, regenere pelo dashboard ou viaPOST /auth/api-key/regenerate/(com PIN)
Autenticação
Cada organização recebe 2 chaves com momentos de geração diferentes:
| Chave | Prefixo | Quando é gerada | Quando funciona |
|---|---|---|---|
| Teste (sandbox) | vd_test_* | No signup — exibida na tela | Imediatamente após cadastro |
| Produção | vd_live_* | No momento da aprovação — enviada por email | Após aprovação + contratos assinados |
A vd_test_* permite cobranças sandbox, gestão de clientes, e teste de webhooks antes da aprovação. Cobranças sandbox são simulação (sem dinheiro real). Quando estiver pronto pro ar, troque pra vd_live_* em produção.
Todas as requisições usam a API key no header:
Authorization: Bearer vd_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Ou pra sandbox:
Authorization: Bearer vd_test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Ambas as chaves autenticam nos mesmos endpoints. A diferença é qual ambiente do provedor conecta (produção vs sandbox) e quais status de org são aceitos (
vd_test_*aceita inclusivepending_review).
Rotacionar/recuperar a API key
A vd_live_* é exibida apenas uma vez no email de aprovação. Se perder ou quiser rotacionar (boas práticas, CI/CD, key comprometida), regenere via API com PIN do dono:
POST /api/v1/auth/api-key/regenerate/
{ "pin": "123456", "key_type": "live" }
A resposta inclui a nova api_key plaintext (também só uma vez). Armazene imediatamente — a antiga é invalidada na hora e qualquer chamada subsequente retorna 401.
Pra rotacionar a chave de teste, use "key_type": "test". Default é live.
Por que o PIN está aqui? Rotação invalida a chave anterior. O PIN garante que o dono autorizou a operação, mesmo que alguém com acesso temporário à API key faça a chamada.
1.5. Status do onboarding (read-only)
Após o cadastro a org passa por verificação documental antes de poder cobrar e sacar. O upload dos documentos é feito no dashboard em /dashboard/onboarding — sem cURL, sem API. Sua aplicação pode, no entanto, consultar o status do onboarding pra mostrar mensagens contextuais ou habilitar funcionalidades só quando a org estiver liberada:
GET /api/v1/onboarding/status/
Resposta:
{
"account_ready": true,
"org_status": "onboarding",
"document_status": "PENDING",
"account_status": {
"general": "PENDING",
"documentation": "AWAITING_APPROVAL",
"commercial_info": "APPROVED",
"bank_account_info": "APPROVED"
}
}
Quando account_status.general == "APPROVED", a org passa automaticamente para org_status='active' e libera saques. O dashboard reflete isso em tempo real.
1.6. Contratos eletrônicos
A Vitrin opera sob 4 contratos comerciais (Termos de Uso, Anexo Comercial, Política de Chargebacks e Política de Privacidade) que precisam estar assinados antes que sua org consiga gerar cobranças. Tentativas de cobrar antes da assinatura retornam HTTP 403:
{ "detail": "Voce precisa assinar todos os contratos comerciais antes de operar. Acesse /dashboard/contract para revisar e assinar." }
Como assinar (humano, no dashboard)
A assinatura é feita pelo dono da org em /dashboard/contract. Lá ele revisa cada contrato (com as taxas reais preenchidas no Anexo Comercial), faz scroll até o fim e confirma com PIN. Não há endpoint REST pra assinar — assinatura eletrônica avançada exige interação humana inequívoca.
Consultar status (programático)
Antes de cada chamada de cobrança, sua aplicação pode (opcionalmente) checar se está tudo em ordem:
GET /api/v1/contracts/status/
Resposta:
{
"all_signed": true,
"pending": [],
"active": [
{
"id": "uuid",
"contract_slug": "terms-of-use",
"contract_title": "Termos de Uso",
"contract_version": 1,
"signed_at": "2026-04-10T12:34:56Z",
"valid_until": null,
"status": "active"
}
]
}
Se all_signed == false, redirecione o operador para /dashboard/contract.
Re-assinatura quando o contrato muda
Se a Vitrin publicar uma nova versão de qualquer contrato (ex: atualização da Política de Chargebacks), o pending volta a ter itens com reason: "new_version" e a operação fica bloqueada até nova assinatura. Você é avisado por e-mail com 30 dias de antecedência.
2. Clientes
Antes de cobrar, cadastre o pagador.
Criar
POST /api/v1/customers/
{
"name": "Maria Silva",
"email": "maria@email.com",
"cpf_cnpj": "12345678909",
"phone": "11999887766"
}
Resposta: retorna id do cliente (UUID) para usar nas cobranças.
Campos opcionais — pessoais:
{
"birth_date": "1990-03-15", // data de nascimento (PF)
"company_name": "Empresa LTDA", // razão social (PJ)
"notes": "Cliente VIP — desconto", // observação interna, não visível ao cliente
"custom_data": { // respostas dos campos customizados do checkout
"como_conheceu": "Instagram",
"tamanho_camiseta": "M"
}
}
Campos opcionais — endereço (recomendado para produtos físicos e defesa de chargeback):
{
"postal_code": "01001000",
"address": "Rua das Flores",
"address_number": "42",
"complement": "Apto 12",
"province": "Centro",
"city": "São Paulo",
"state": "SP"
}
O dashboard auto-preenche endereço via ViaCEP quando o pagador digita o CEP. Em integrações server-side você pode replicar essa lógica chamando
https://viacep.com.br/ws/{cep}/json/.
Listar
GET /api/v1/customers/?page=1
Resposta paginada:
{
"count": 142,
"next": "https://api.vitrin.digital/api/v1/customers/?page=2",
"previous": null,
"results": [ { "id": "uuid", "name": "Maria Silva", ... } ]
}
Filtros aceitos via query string: email, cpf_cnpj, name__icontains.
Detalhes
GET /api/v1/customers/{id}/
Atualizar
PUT /api/v1/customers/{id}/ # substitui todos os campos
PATCH /api/v1/customers/{id}/ # atualiza só os enviados
Excluir
DELETE /api/v1/customers/{id}/
Excluir um cliente que tem transações associadas é bloqueado — você recebe HTTP 409. Use para limpar cadastros que nunca pagaram.
3. Cobranças
3.1 Pix
POST /api/v1/checkout/pix/
{
"customer_id": "uuid-do-cliente",
"amount": 99.90,
"description": "Plano Mensal"
}
Resposta:
{
"payment_id": "uuid-da-transacao",
"qr_code": "base64-do-qr-code",
"copy_paste": "00020101021226880014br.gov.bcb...",
"amount": 99.90,
"status": "pending"
}
O frontend da sua app exibe o QR code (qr_code em base64) ou o código copia-e-cola (copy_paste).
3.2 Boleto
POST /api/v1/checkout/boleto/
{
"customer_id": "uuid-do-cliente",
"amount": 99.90,
"description": "Plano Mensal"
}
Resposta:
{
"payment_id": "uuid-da-transacao",
"barcode": "23793.38128 60000.000003 00000.000406 1 84340000009990",
"url": "https://vitrin.digital/b/pdf/xxxx",
"amount": 99.90,
"status": "pending"
}
3.3 Cartão de Crédito
Passo 1: Tokenizar o cartão
POST /api/v1/checkout/tokenize/
{
"customer_id": "uuid-do-cliente",
"credit_card": {
"holderName": "MARIA SILVA",
"number": "5162306219378829",
"expiryMonth": "01",
"expiryYear": "2028",
"ccv": "318"
},
"credit_card_holder_info": {
"name": "Maria Silva",
"email": "maria@email.com",
"cpfCnpj": "12345678909",
"postalCode": "01001000",
"addressNumber": "42",
"phone": "11999887766"
}
}
Resposta:
{
"credit_card_token": "tok_xxxxxxxxxxxxx",
"credit_card_number": "5162 **** **** 8829",
"credit_card_brand": "MASTERCARD"
}
Passo 2: Cobrar com o token
POST /api/v1/checkout/pay/
{
"customer_id": "uuid-do-cliente",
"amount": 299.90,
"billing_type": "CREDIT_CARD",
"installments": 3,
"credit_card_token": "tok_xxxxxxxxxxxxx",
"description": "Pacote Premium"
}
Dados do cartão nunca passam pelo seu servidor. A tokenização é feita via API e o token é usado para cobranças futuras.
Passo 3: 3D Secure (obrigatório por padrão — pré-requisito para antecipação 100%)
Padrão Vitrin: 3DS é obrigatório em toda cobrança de cartão. Cobranças sem
authenticationretornam HTTP 422 comerror: "Cobrança recusada — 3DS obrigatório"ereturn_code: "VITRIN_3DS_MISSING". A tentativa é persistida como Transaction comstatus='failed'— você consegue ver emGET /api/v1/transactions/?status=failede no painel. Ocustomer_messageé genérico ("Não foi possível processar este cartão. Use outro cartão ou tente novamente.") — não vazamos a política pro pagador. Isso garante liability shift e mantém todas as orgs elegíveis para antecipação 100%. Cenários excepcionais (cartões corporativos sem 3DS, bandeiras não-suportadas com volume relevante) podem ser baixados praoptionalcaso a caso pela equipe Vitrin via Anexo Comercial.
Cobranças de cartão autenticadas via 3DS ficam elegíveis para antecipação 100% em D+1, em vez do D+31 padrão.
Importante: a antecipação é um benefício concedido caso a caso pela Vitrin após análise (perfil de risco, TPV, histórico, vertical). Mesmo com 3DS implementado, a liberação da antecipação 100% e a taxa aplicada dependem de aprovação Vitrin e/ou negociação comercial específica para sua organização. Confirme as condições no Anexo Comercial assinado.
3DS dispensa antifraude. Quando a cobrança chega com
authentication, a análise antifraude (R$ 0,50/tx, +60s de latência) é automaticamente desligada — o liability shift do 3DS já protege contra chargeback de "transação não reconhecida". Só faz sentido manter o antifraude ligado para fluxos onde 3DS não é viável (cartões não-enrolados, bandeiras sem suporte, integração server-only). Veja Antifraude abaixo.
A Vitrin oferece o SDK vitrin.js, que abstrai todo o fluxo browser-side em três chamadas (tokenizar → autenticar → cobrar). Este é o caminho recomendado.
Modo recomendado — via vitrin.js (SDK oficial):
<script src="https://vitrin.digital/vitrin.js"></script>
<script>
Vitrin.init({ apiKey: 'vd_test_xxxxx', sandbox: true });
async function pagar() {
// 1. Tokeniza o cartão (PCI compliant)
const { token } = await Vitrin.tokenize({
customerId: 'cus_xxx',
holderName: 'MARIA SILVA',
number: '5162306219378829',
expiryMonth: '01',
expiryYear: '2028',
cvv: '318',
});
// 2. Autentica via 3DS (carrega script lazy + roda challenge)
const auth = await Vitrin.authenticate3DS({
cardToken: token,
amount: 299.90,
});
// 3. Cobra. `authentication` é opcional — se cartão não suporta 3DS,
// `auth` será null e a cobrança segue (sem antecipação 100%).
const result = await Vitrin.charge({
customerId: 'cus_xxx',
amount: 299.90,
installments: 3,
cardToken: token,
description: 'Pacote Premium',
authentication: auth, // pode ser null
});
}
</script>
O SDK trata automaticamente:
- Carregamento lazy do MPI 3DS (script só baixa quando precisa)
- Detecção de bandeira não-enrolada (
onUnenrolled→ resolve comnull) - Detecção de bandeira não-suportada (JCB/Diners → resolve com
null) - Mapeamento dos campos retornados (Eci/Cavv/Xid/dsTransID/Version)
- Tratamento de erros via
Vitrin.Error
Modo manual (server-to-server):
Se preferir não usar o SDK (ex: app mobile), monte o payload authentication direto:
POST /api/v1/checkout/pay/
{
"customer_id": "uuid-do-cliente",
"amount": 299.90,
"billing_type": "CREDIT_CARD",
"installments": 3,
"credit_card_token": "tok_xxxxxxxxxxxxx",
"description": "Pacote Premium",
"authentication": {
"eci": "05",
"cavv": "AAABBBCCCDDDEEEFFFGGG=",
"ds_trans_id": "ds-trans-uuid-2x",
"protocol_version": "2.2.0",
"reference_id": "ref_mpi_001",
"status": "AUTHENTICATED"
}
}
Bandeiras com 3DS: Visa e Mastercard (3DS 2.2), Elo e Amex (3DS 2.1). JCB, Aura, Diners, Discover não suportam 3DS — siga sem authentication (cobrança permanece elegível, mas sem o liability shift / antecipação 100%).
Tabela ECI (resumida):
| Master | Visa/Elo/Amex | Resultado | Liability shift | Antecipação 100% |
|---|---|---|---|---|
| 02 | 05 | Autenticada pelo emissor | ✅ Sim | ✅ Elegível |
| 01 | 06 | Autenticada pela bandeira | ✅ Sim | ✅ Elegível |
| 04 | 07 | Data Only | ❌ Não | ❌ Não |
"Elegível" significa que a transação atende ao requisito técnico para antecipação 100%. A liberação efetiva depende de aprovação Vitrin e das condições negociadas no Anexo Comercial da sua organização (taxa de antecipação, prazos, modalidade automática vs sob demanda).
Se o cartão estiver não-enrolado ou for de bandeira sem 3DS, você ainda consegue cobrar — só envie a request sem
authentication(cobrança aceita normalmente; perde-se o benefício da antecipação acelerada).
Recusas: como tratar
Quando o emissor (banco do cliente) recusa a cobrança de cartão, a Vitrin devolve HTTP 422 com um payload pronto pra você decidir o próximo passo:
{
"error": "Pagamento não autorizado.",
"description": "Transação não permitida",
"return_code": "GD",
"return_code_description": "Transação não permitida",
"customer_message": "Pagamento não autorizado pelo emissor. Use outro cartão para finalizar a compra.",
"retryable": false
}
Os campos chave são:
customer_message: texto pronto para exibir ao cliente final no checkout. Já vem em PT-BR e adaptado ao motivo da recusa (saldo insuficiente vs cartão bloqueado vs erro transitório).retryable:true→ emissor admite nova tentativa com o mesmo cartão (ex.: timeout, sistema indisponível, parcelamento inválido). Você pode oferecer "Tentar novamente" ao cliente.false→ recusa definitiva para esse cartão. Não tente de novo com o mesmo cartão — a próxima tentativa é recusada de imediato pelo nosso processador (sem chegar ao emissor) e fica registrada no histórico do cliente. Ofereça troca de cartão.
return_code/return_code_description: código ABECS oficial e descrição interna. Útil pra logs e suporte; não recomendamos exibir ao cliente final (usecustomer_message).
Exemplo de UX recomendado:
const resp = await vitrin.charges.create({ /* ... */ });
if (resp.status === 422) {
const body = await resp.json();
// Mostre a customer_message ao usuário
showError(body.customer_message);
// Se retryable, ofereça botão "Tentar de novo"
if (body.retryable) {
showRetryButton();
} else {
// Recusa definitiva: ofereça outro cartão
showCardSwitcher();
}
}
Importante: mesmo com 3DS autenticado (
Is3DSAuthenticated: true), o emissor pode recusar — o 3DS dá liability shift, mas não garante autorização. Recusas após 3DS geralmente vêm de checagens de risco do banco emissor (fraude, saldo, divergência de dados do titular).
Antifraude (fallback quando 3DS não vem)
A Vitrin oferece análise antifraude opcional para cobranças de cartão. Custo: R$ 0,50/tx. Você ativa pela tela /dashboard/settings (toggle "Antifraude").
Decisão automática por cobrança (você não controla por chamada — depende do que chega no POST /checkout/pay/):
| 3DS na cobrança | Antifraude na conta | Resultado |
|---|---|---|
✅ presente (authentication) | qualquer | Antifraude OFF — 3DS dispensa (liability shift + economia de R$ 0,50/tx + ~60s) |
| ❌ ausente | ON | Antifraude ON — fallback de proteção |
| ❌ ausente | OFF | Sem proteção — você assume o risco de chargeback |
Quando ligar o antifraude da conta: se você integra server-only (sem checkout web), aceita bandeiras sem suporte 3DS (JCB, Aura, Diners, Discover) ou tem cartões não-enrolados chegando com frequência. Para checkout web com vitrin.js, o caminho recomendado é deixar antifraude OFF e enviar 3DS em todas as cobranças (mais barato, mais rápido, melhor proteção).
Os parâmetros do antifraude (regras, score mínimo) são definidos pela Vitrin globalmente — você não controla via API. O toggle só liga ou desliga.
3.4 Consultar Parcelamento
Antes de cobrar, consulte as opções de parcelamento que o cliente final vê:
GET /api/v1/checkout/installments/?amount=299.90
Resposta:
{
"amount": 299.90,
"installments": [
{ "installment_count": 1, "installment_value": 299.90, "total_value": 299.90 },
{ "installment_count": 2, "installment_value": 154.46, "total_value": 308.92 },
{ "installment_count": 3, "installment_value": 103.99, "total_value": 311.97 }
]
}
A resposta é desenhada pra alimentar diretamente o frontend de checkout do cliente final: o que ele paga por parcela (installment_value) e o total (total_value). Os juros embutidos no parcelamento ficam transparentes pro pagador (consolidados no total_value) e não são expostos como campo separado.
Para conhecer o custo aplicado a cada cobrança da sua conta, consulte os termos comerciais ativos em
GET /api/v1/commercial-terms/active/ou a aba "Comissões" do dashboard. Os valores líquidos por transação aparecem emTransaction.net_amountquando a cobrança é criada.
3.5 Guest Checkout (sem cadastro prévio)
Cria o cliente e cobra em um único passo:
POST /api/v1/checkout/guest/
{
"customer_name": "João Santos",
"customer_email": "joao@email.com",
"customer_cpf_cnpj": "98765432100",
"customer_phone": "11988776655",
"amount": 49.90,
"billing_type": "PIX",
"description": "Ingresso Evento"
}
3.6 Pix Automático (recorrência via SPI/BC)
Para cobranças recorrentes sem cartão. O pagador escaneia um QR Code uma única vez, autoriza a primeira cobrança + futuras recorrências via app bancário, e a partir daí você gera as cobranças periódicas.
Requisitos: o método precisa estar habilitado pelo admin (
PIX_AUTOMATICemallowed_payment_methods) e pela própria org emenabled_payment_methods. A conta de pagamentos no provedor precisa estar aprovada (CNPJ ativo há 6+ meses).
Passo 1: Criar a autorização (gera QR Code de aceite)
POST /api/v1/pix-automatic/authorizations/
{
"customer_id": "uuid-do-cliente",
"contract_id": "ASSINATURA-001",
"frequency": "MONTHLY",
"start_date": "2026-05-01",
"value": 99.90,
"description": "Mensalidade Plus",
"first_charge_value": 99.90,
"first_charge_description": "Primeira mensalidade"
}
Campos:
frequency:WEEKLY,MONTHLY,QUARTERLY,SEMIANNUALLY,ANNUALLYvalue: valor fixo cobrado a cada ciclo. Obrigatório — semvalue(oumin_limit_value) o BACEN força o pagador a definir um teto no app do banco, comportamento que normalmente não é o desejado para assinatura de valor fixo.valueemin_limit_valuesão mutuamente exclusivos.min_limit_value(alternativa avalue): piso de valor variável. O pagador define um teto no app que precisa ser ≥ esse piso, e a org pode cobrar qualquer valor ≤ teto a cada ciclo.finish_date(opcional): se omitido, prazo indeterminado.first_charge_value: valor da primeira cobrança (vinculada ao QR Code de aceite — obrigatório). Renderiza no QR Code que o cliente escaneia ao aceitar a autorização.
Endereço e telefone do Customer: o BACEN exige que o pagador veja todos os dados no app do banco antes de aceitar. Antes de criar a autorização, garanta que o
Customertemphone,postal_code,address,address_number,province,cityestatepreenchidos — se faltar algum, a API retorna 400 commissing_fields.
Resposta:
{
"id": "uuid-da-autorizacao",
"provider_authorization_id": "pix_auto_xxx",
"status": "CREATED",
"immediate_qr_code": "<base64 PNG do QR Code>",
"immediate_payload": "00020101021226880014br.gov.bcb...",
"frequency": "MONTHLY",
"value": "99.90"
}
Renderize o QR Code para o cliente:
<img src="data:image/png;base64,<immediate_qr_code>" alt="QR Code Pix Automatico" />
Ou ofereça o copia-e-cola via immediate_payload.
Passo 2: Aguardar o aceite + 1ª cobrança (via webhook)
Quando o cliente escaneia, aceita no app bancário e paga a primeira cobrança, dois sinais chegam:
- A autorização vai de
CREATEDparaACTIVE. Você recebe o evento:PIX_AUTOMATIC_RECURRING_AUTHORIZATION_ACTIVATED - A
Transactionda primeira cobrança (criada automaticamente no momento da autorização, comstatus='pending') vai parareceivedquando o cliente paga, via webhook PIX padrão. Ela aparece emGET /api/v1/transactions/combilling_type='PIX_AUTOMATIC'eprovider_payment_idigual ao identificador da transação no processador.
Passo 3: Gerar cobranças recorrentes
Com a autorização ACTIVE, gere cada cobrança periódica:
POST /api/v1/pix-automatic/authorizations/{id}/charges/
{
"value": 99.90,
"due_date": "2026-06-01",
"description": "Mensalidade junho"
}
Janela do BC: o
due_datedeve estar entre 2 e 10 dias úteis no futuro. Tentativas fora dessa janela retornam HTTP 400.Se a autorização tem
valuefixo, você precisa enviar exatamente esse valor.
Resposta: retorna a Transaction criada (mesmo formato do /checkout/pix/).
Outras operações
GET /api/v1/pix-automatic/authorizations/ # Listar autorizações
GET /api/v1/pix-automatic/authorizations/{id}/ # Detalhes
PATCH /api/v1/pix-automatic/authorizations/{id}/ # Alterar Customer/start_date (limitado pelo processador)
GET /api/v1/pix-automatic/authorizations/{id}/instructions/ # Listar cobranças geradas
POST /api/v1/pix-automatic/authorizations/{id}/sync/ # Forçar sync com o provedor
DELETE /api/v1/pix-automatic/authorizations/{id}/ # Cancelar (PIN obrigatório)
GET /api/v1/pix-automatic/instructions/ # Todas as instruções da org
GET /api/v1/pix-automatic/instructions/{id}/ # Detalhe da instrução
POST /api/v1/pix-automatic/instructions/{id}/sync/ # Forçar sync da instrução
DELETE /api/v1/pix-automatic/instructions/{id}/ # Cancelar agendamento (PIN obrigatório)
PATCHé limitado pelo processador a alterar dados doCustomerestart_date. Mudanças de valor (value/min_limit_value),finish_date,frequency,customeroucontract_idexigem criar nova autorização (cancelar a atual e emitir um novo QR de aceite). Status terminal (CANCELLED/REFUSED/EXPIRED) bloqueia qualquer alteração.
DELETE /instructions/{id}/cancela um agendamento ainda não pago. Status terminal (DONE/CANCELLED/REFUSED) bloqueia o cancelamento. ATransactionpendente associada é marcada comocanceledautomaticamente.
Status possíveis
| Autorização | Significado |
|---|---|
CREATED | Aguardando aceite do pagador |
ACTIVE | Aceita — cobranças podem ser geradas |
CANCELLED | Cancelada (pela org ou pelo pagador) |
REFUSED | Recusada pelo banco do pagador |
EXPIRED | Expirada sem aceite |
| Instrução (cobrança) | Significado |
|---|---|
AWAITING_REQUEST | Criada localmente |
SCHEDULED | Agendada no SPI |
DONE | Pagamento confirmado |
CANCELLED | Cancelada antes do débito |
REFUSED | Recusada (saldo insuficiente, divergência etc.) |
3.7 Cartões Salvos e One-Click Buy
Permite cobrar um cliente recorrente sem pedir os dados do cartão de novo. O fluxo é:
- Cliente preenche o cartão uma vez com consentimento explícito
- Você guarda o
saved_card_token(a Vitrin persiste a referência; o número do cartão fica no cofre PCI do processador) - Cobranças futuras chamam
/charges/charge-saved/informando só ocustomer_ide o valor
Um cartão preferido por cliente. Salvar um novo cartão substitui o anterior. Cliente final pode remover via link assinado que você gera pelo painel ou pela API.
Salvar cartão (consentimento obrigatório — LGPD)
POST /api/v1/customers/{customer_id}/saved-card/
{
"credit_card": {
"holder_name": "JOAO DA SILVA",
"number": "4000000000001000",
"expiry_month": "12",
"expiry_year": "2030",
"cvv": "123"
},
"consent": {
"accepted_at": "2026-05-16T12:00:00Z",
"ip": "189.10.20.30"
}
}
Resposta 201 Created:
{
"brand": "visa",
"last4": "1000",
"exp_month": 12,
"exp_year": 2030,
"holder_name": "JOAO DA SILVA",
"consent_at": "2026-05-16T12:00:00Z"
}
O bloco consent é obrigatório — sem accepted_at e ip o cartão não é persistido (400). Use o timestamp em que o cliente final aceitou o checkbox "Salvar cartão para próximas compras" e o IP capturado no mesmo request.
Cobrar com o cartão salvo (one-click)
POST /api/v1/charges/charge-saved/
{
"customer_id": "5e3a0a1b-9b1f-4f87-9b6b-1a2b3c4d5e6f",
"amount": 99.90,
"description": "Recompra plano Pro",
"installments": 1,
"external_reference": "reorder-2026-05-16"
}
Retorna o mesmo Transaction que /checkout/pay/ retorna. O cliente paga sem digitar nada — você cobra com 1 request.
| Erro | Quando |
|---|---|
404 customer_not_found | customer_id não existe ou pertence a outra org |
422 no_saved_card | Cliente sem cartão salvo |
422 card_expired | Mês/ano de expiração já passou — peça novo cartão |
422 <ABECS code> | Recusa do emissor (vide tabela em 3.3) |
Consultar / remover (lado da org)
GET /api/v1/customers/{customer_id}/saved-card/ # 404 se não houver
DELETE /api/v1/customers/{customer_id}/saved-card/ # 204
A resposta do GET nunca inclui o token — só brand, last4, expiração, nome e timestamp do consentimento.
Link assinado para o cliente final gerenciar
Você pode gerar uma URL que o cliente final abre para ver/remover o próprio cartão, sem precisar do seu painel (ex: enviar por email após assinatura de plano, página de minha-conta no site da loja):
POST /api/v1/customers/{customer_id}/saved-card/management-link/
Resposta:
{
"url": "https://vitrin.digital/customer/cards/eyJhbGciOi...",
"token": "eyJhbGciOi...",
"expires_at": "2026-05-17T12:00:00Z"
}
O token é um JWT de 24h. A página servida em url permite ao cliente final:
GET /api/v1/public/saved-card/?token=<jwt>— ver bandeira + últimos 4 + nome da lojaDELETE /api/v1/public/saved-card/?token=<jwt>— remover o próprio cartão
LGPD: o link assinado é o mecanismo recomendado para atender o direito de revogação do consentimento. Inclua-o em emails transacionais relevantes e na sua página de minha-conta.
Link de recompra one-click (cliente final clica e paga)
Enquanto charge-saved é servidor pra servidor (você cobra direto via API), o link de recompra entrega o controle pro cliente final: você gera uma URL assinada, manda por email/WhatsApp, e o cliente confirma a compra com 1 clique numa página hospedada pela Vitrin.
POST /api/v1/customers/{customer_id}/one-click-link/
{
"product_id": "5e3a0a1b-9b1f-4f87-9b6b-1a2b3c4d5e6f",
"amount": "99.90", // opcional — só pra produto customer_defined ou plano com valor flexível
"installments": 3, // opcional, default 1
"coupon_code": "OFF10", // opcional — só pra produto; valida targeting na emissão
"bump_ids": ["..."], // opcional — só pra produto; bumps pré-aceitos vinculados ao parent
"expires_in": 900 // opcional (segundos), default 900 = 15min, máx 3600
}
Use plan_id no lugar de product_id pra gerar link de assinatura. Exatamente um dos dois. Cupom e bumps só funcionam pra produto — planos rejeitam (400).
Resposta:
{
"url": "https://vitrin.digital/c/loja/produto-x/oneclick?t=eyJhbGciOi...",
"token": "eyJhbGciOi...",
"expires_at": "2026-05-16T15:15:00Z"
}
O cliente abre a url, vê o card do cartão salvo (bandeira + ••••1234), valor e parcelas, e clica "Pagar — 1 clique". A Vitrin valida o JWT, cobra usando o token salvo (mesma engine do checkout normal — cria Order/Subscription, calcula fees, executa split de comissão, dispara webhook) e redireciona pro thank_you_url configurado no item.
Segurança:
- JWT HS256 com TTL curto (15min default, 1h máx) — limita janela de fraude
- Uso único: depois do primeiro clique, o token é consumido (nonce Redis) — segundo POST retorna
401 token_consumed - Nonce só é consumido após validações reversíveis (cartão removido / expirado / item inativo / subscription duplicada) — falhas reversíveis preservam o link; cliente pode retentar
- Cartão precisa estar salvo e válido no momento do clique. Se foi removido entre emissão e uso →
422 card_removed - Item precisa estar
is_active=Truena sua org — mudanças refletem em tempo real - Rate limit: 10 req/min por IP no endpoint público (
/public/oneclick/), abaixo do checkout normal por cobrar sem CVV
| Erro | Quando |
|---|---|
400 coupon_not_found / coupon_invalid / coupon_not_applicable | Cupom inexistente, inválido (expirado/inativo) ou não aplicável ao produto (na emissão) |
400 bumps_not_found | Algum bump_id não pertence ao parent product (na emissão) |
422 no_saved_card | Customer sem cartão salvo (na emissão) |
422 card_expired | Cartão com mês/ano expirado (na emissão ou no clique) |
422 card_removed | Cartão foi deletado entre emissão e clique |
422 provider_refused | Cartão recusado pelo processador (customer_message pronto pra exibir) |
502 provider_error | Processador instável; retryable: true |
409 subscription_already_active | Cliente já tem subscription vigente desse plano (preserva nonce) |
404 item_not_found | Produto/plano não existe ou ficou inativo |
401 token_expired | TTL estourou |
401 token_consumed | Cliente já usou o link |
401 token_invalid | Assinatura inválida / scope errado / tampering |
Histórico de links emitidos
GET /api/v1/customers/{customer_id}/one-click-history/?limit=20
Retorna items[] com status (paid / pending / expired / consumed_failed), jti, issued_at, paid_at + paid_resource_id quando aplicável. Útil pra reconciliar campanhas de email e ver quem clicou.
Conformidade
- O número do cartão nunca toca o servidor da Vitrin — só o token retornado pelo cofre PCI do processador
- Cada token é exclusivo da sua organização e não pode ser usado por outra org
- O texto de consentimento sugerido está em
docs/MANUAL-ORG.md— cole na sua política de privacidade - Cobranças com cartão salvo não pedem CVV. Isso é parte do one-click. Para fluxos sensíveis (valor alto, antifraude), prefira
/checkout/pay/com cartão fresco + 3DS
SDKs
// Node
await vitrin.customers.savedCard.create(customer.id, {
credit_card: { holder_name, number, expiry_month, expiry_year, cvv },
consent: { accepted_at, ip },
})
await vitrin.charges.createWithSavedCard({
customer_id: customer.id,
amount: 99.90,
description: 'Recompra plano Pro',
})
await vitrin.customers.savedCard.createManagementLink(customer.id)
# Python
vitrin.customers.save_card(
customer.id,
credit_card={"holder_name": ..., "number": ..., "expiry_month": ..., "expiry_year": ..., "cvv": ...},
consent={"accepted_at": ..., "ip": ...},
)
vitrin.charges.create_with_saved_card(
customer_id=customer.id, amount="99.90", description="Recompra plano Pro",
)
vitrin.customers.saved_card_management_link(customer.id)
# Link de recompra one-click (cliente clica e paga)
vitrin.customers.one_click_link(
customer.id, product_id=product.id, amount="99.90", installments=3,
)
// Node — link de recompra
await vitrin.customers.oneClickLink(customer.id, {
product_id: product.id,
amount: "99.90",
installments: 3,
})
3.8 Cobrança das taxas
As taxas da plataforma são descontadas automaticamente do valor líquido recebido. O valor que você passa em amount é o valor que o cliente paga; você recebe amount − taxa.
Exemplo: numa cobrança de R$ 100 com taxa de R$ 5,49, o cliente paga R$ 100 e você recebe R$ 94,51 líquido na sua conta. A taxa sai do seu repasse, não é somada à fatura do pagador.
Esse comportamento é uniforme para Pix, Boleto e Cartão. Confira a taxa exata aplicada à sua org no simulador ou em GET /org/settings/.
4. Polling de Status (Pix)
Para Pix, faça polling até o pagamento ser confirmado:
GET /api/v1/payments/{payment_id}/status/
Resposta:
{
"payment_id": "uuid",
"status": "pending",
"confirmed": false,
"pix_copy_paste": "00020101..."
}
Faça polling a cada 3-5 segundos. Quando confirmed: true, o pagamento foi recebido.
5. Webhooks
Configurar
Configure a URL onde sua aplicação recebe os eventos. Pode ser via dashboard ou API:
Via dashboard: acesse /dashboard/webhooks (requer PIN), informe a URL HTTPS, salve. O secret HMAC é exibido na tela após salvar — guarde-o.
Via API:
PUT /api/v1/webhooks/config/
{
"webhook_url": "https://suaapp.com/webhooks/vitrin",
"pin": "123456"
}
O secret HMAC é gerado automaticamente e retornado no response. Você pode regenerá-lo a qualquer momento (e qualquer requisição feita à URL com signature antiga ficará inválida).
Sem URL configurada, a Vitrin processa o evento internamente (atualizando suas Transactions) mas não repassa ao seu backend. Configure a URL antes de ir pro ar.
Receber eventos
A Vitrin envia um POST para a sua URL quando eventos ocorrem:
POST https://suaapp.com/webhooks/vitrin
Headers:
X-Vitrin-Signature: sha256=abc123...
X-Vitrin-Event: PAYMENT_RECEIVED
X-Vitrin-Event-Id: PAYMENT_RECEIVED:pay_xxx:2026-04-09
Content-Type: application/json
Body:
{
"event": "PAYMENT_RECEIVED",
"payment": {
"id": "<uuid-da-transacao>",
"status": "received",
"billing_type": "PIX",
"amount": 99.90,
"net_amount": 96.41,
"due_date": "2026-05-07",
"paid_at": "2026-05-07T12:34:56Z",
"customer_id": "<uuid-do-cliente>",
"subscription_id": null,
"description": "Mensalidade",
"installments": 1
}
}
Cobranças de cartão trazem também o snapshot da bandeira/últimos 4 (em qualquer status) e, no caso de recusa, um bloco refusal com o motivo:
{
"event": "PAYMENT_REFUSED",
"payment": {
"id": "<uuid-da-transacao>",
"status": "failed",
"billing_type": "CREDIT_CARD",
"amount": 133.32,
"installments": 12,
"customer_id": "<uuid-do-cliente>",
"card": { "brand": "Mastercard", "last4": "0523" },
"refusal": {
"code": "51",
"description": "Saldo/limite insuficiente",
"message": "Autorizacao negada",
"customer_message": "Cartão sem saldo/limite. Use outro cartão ou tente menos parcelas.",
"retryable": false
}
}
}
cardaparece em qualquer cobrança CC (autorizada ou recusada).refusalsó vem comstatus='failed'e contém:code: ABECS oficial (ex:"51","GD")description: descrição interna em portuguêsmessage: mensagem original do emissorcustomer_message: mensagem pronta pra mostrar ao cliente final no checkoutretryable:falsesignifica que tentar de novo no mesmo cartão vai falhar — peça outro cartão ou outra forma de pagamento.
Pagamentos via checkout público (ou via API de Orders) trazem também o bloco order enriquecido — use para provisionar acesso/entrega sem chamada GET extra:
{
"event": "PAYMENT_RECEIVED",
"payment": {
"id": "<uuid-tx>",
"status": "received",
"amount": 75.00,
"order": {
"id": "<uuid-order>",
"status": "pending",
"unit_price": 75.00,
"total_amount": 75.00,
"quantity": 1,
"product": {
"id": "<uuid-product>",
"slug": "doacao-vitrin",
"name": "Apoie o canal",
"type": "contribution",
"thank_you_url": "https://..."
},
"coupon": { "id": "<uuid>", "code": "PROMO10", "discount_amount": 10.00 },
"bump": { "id": "<uuid>", "product_id": "<uuid>", "product_name": "E-book bônus", "amount": 19.00 },
"customer_chosen_amount": 75.00
}
}
}
ordersó aparece quando há um Order vinculado (compra via checkout público ouPOST /orders/create/).coupon/bump/customer_chosen_amountsó aparecem quando aplicáveis.
Cobranças com atribuição de afiliado trazem o bloco affiliate com o slug, valor da comissão e status de liquidação. Use para provisionar comissão no seu sistema interno (ex: gamificação, ranking) ou para notificar o afiliado fora da Vitrin:
{
"event": "PAYMENT_RECEIVED",
"payment": {
"id": "<uuid-tx>",
"status": "received",
"amount": 100.00,
"affiliate": {
"slug": "joao-silva",
"commission_amount": 30.00,
"status": "settled",
"is_recurring": false,
"settled_at": "2026-05-19T22:34:56Z"
}
}
}
affiliatesó aparece quando a venda foi atribuída a um afiliado (cliente entrou no checkout pelo link dele dentro da janela de atribuição configurada na sua loja).statusreflete o estado da comissão na carteira do afiliado:pending: comissão registrada mas ainda não liquidada (cobrança ainda não confirmou).settled: comissão liquidada na carteira do afiliado — vai pra ele no próximo repasse.reversed: comissão estornada (chargeback ou refund).
is_recurring:trueem renovações de assinatura/Pix Automático;falsena primeira venda ou produto avulso.settled_at/reversed_ataparecem conforme o estado.
Quando há coprodutores vinculados ao produto, vem também o bloco coproducers:
{
"payment": {
"coproducers": [
{ "name": "Editora Parceira", "amount": 20.00, "mode": "pct" }
]
}
}
modeépctoufixed; oamountem reais é o valor que foi destacado pra cada coprodutor.
Importante: a fatia da Vitrin não aparece no webhook canônico —
amountjá é o que sua loja vende;net_amountjá está líquido de taxas. Comissão de afiliado e splits de coprodutor são debitados desse líquido (não da Vitrin).
Eventos sintéticos da Vitrin (dunning de assinaturas):
| Evento | Quando |
|---|---|
subscription.dunning_warning | Assinatura overdue há ≥ 7 dias |
subscription.canceled_for_dunning | Assinatura overdue há ≥ 30 dias — Vitrin cancelou no provedor |
Ambos trazem subscription_id, plan_id, customer_id, overdue_since no payload.
Nota: o payment.id é o UUID da Transaction na Vitrin — use ele
para reconciliar contra GET /payments/{id}/. Em raros casos onde o
webhook chega antes da Transaction ser persistida internamente
(condição de corrida), o body vem com payment.provider_payment_id_legacy
no lugar de payment.id — você pode reconciliar via lookup posterior.
Validar assinatura
import hmac, hashlib
def verify_webhook(body: bytes, signature: str, secret: str) -> bool:
expected = hmac.new(
secret.encode(), body, hashlib.sha256
).hexdigest()
return hmac.compare_digest(f"sha256={expected}", signature)
Eventos suportados
| Evento | Quando |
|---|---|
PAYMENT_RECEIVED | Pagamento confirmado |
PAYMENT_CONFIRMED | Pagamento confirmado (cartão) |
PAYMENT_OVERDUE | Pagamento vencido |
PAYMENT_REFUSED | Pagamento recusado (cartão) — body inclui refusal |
PAYMENT_REFUNDED | Reembolso processado |
PAYMENT_CHARGEBACK_REQUESTED | Chargeback aberto |
PAYMENT_DELETED | Pagamento cancelado |
SUBSCRIPTION_CREATED | Assinatura criada |
SUBSCRIPTION_DELETED | Assinatura cancelada |
PIX_AUTOMATIC_RECURRING_AUTHORIZATION_CREATED | Autorização Pix Automático criada |
PIX_AUTOMATIC_RECURRING_AUTHORIZATION_ACTIVATED | Pagador aceitou — autorização ativa |
PIX_AUTOMATIC_RECURRING_AUTHORIZATION_REFUSED | Autorização recusada pelo banco |
PIX_AUTOMATIC_RECURRING_AUTHORIZATION_CANCELLED | Autorização cancelada |
PIX_AUTOMATIC_RECURRING_AUTHORIZATION_EXPIRED | Autorização expirou sem aceite |
PIX_AUTOMATIC_RECURRING_PAYMENT_INSTRUCTION_CREATED | Instrução de cobrança criada |
PIX_AUTOMATIC_RECURRING_PAYMENT_INSTRUCTION_SCHEDULED | Instrução agendada no SPI |
PIX_AUTOMATIC_RECURRING_PAYMENT_INSTRUCTION_REFUSED | Instrução recusada (ver refusalReason) |
PIX_AUTOMATIC_RECURRING_PAYMENT_INSTRUCTION_CANCELLED | Instrução cancelada |
6. Assinaturas
Recorrência via cartão de crédito ou boleto. Cada plano define a periodicidade e o valor; cada assinatura vincula um cliente a um plano.
Planos
Criar:
POST /api/v1/plans/
{
"name": "Plano Pro",
"price": 179.90,
"billing_cycle": "monthly",
"description": "Acesso completo",
"trial_days": 7,
"trial_price": "0.00",
"max_cycles": 0,
"setup_fee": "0.00",
"cover_image_url": "https://..."
}
Ciclos: weekly, biweekly, monthly, bimonthly, quarterly, semiannually, yearly.
Campos opcionais (catálogo expandido):
trial_days(int, default 0) — dias de teste antes da primeira cobrança real.0desativa.trial_price(decimal, default 0) — valor cobrado no trial. Hoje só0é suportado (trial gratuito); trial pago é roadmap.max_cycles(int, default 0) — encerra a assinatura após N cobranças.0= indeterminado.setup_fee(decimal, default 0) — taxa de adesão única na primeira mensalidade.cover_image_url— capa exibida no checkout público.
Listar / Detalhes / Atualizar / Excluir:
GET /api/v1/plans/
GET /api/v1/plans/{id}/
PUT /api/v1/plans/{id}/ # substitui
PATCH /api/v1/plans/{id}/ # parcial
DELETE /api/v1/plans/{id}/ # bloqueado se houver assinaturas ativas
Assinaturas
Criar:
POST /api/v1/subscriptions/
{
"customer_id": "uuid-do-cliente",
"plan_id": "uuid-do-plano",
"billing_type": "CREDIT_CARD",
"credit_card_token": "tok_xxx"
}
Para boleto, omita o token e use "billing_type": "BOLETO".
Listar:
GET /api/v1/subscriptions/?page=1
Resposta paginada. Filtros aceitos: status, customer_id, plan_id.
Detalhes (com dados ao vivo do processador):
GET /api/v1/subscriptions/{id}/detail/
Retorna o histórico de cobranças geradas, próxima fatura, e status atual sincronizado com o provedor.
Cancelar:
POST /api/v1/subscriptions/{id}/cancel/
7. Produtos e Pedidos
Para vendas avulsas (eventos, pacotes, créditos, ingressos, infoprodutos).
Produtos
Criar:
POST /api/v1/products/
{
"name": "Evento Premium",
"product_type": "one_time",
"price": 347.00,
"description": "Acesso a 1 evento com recursos Pro",
"long_description": "Texto rico em markdown para a página pública",
"cover_image_url": "https://...",
"video_url": "https://www.youtube.com/embed/...",
"thank_you_url": "https://...",
"allow_installments": true,
"max_installments": 12
}
Tipos (product_type):
one_time— pagamento único (ingresso, infoproduto, serviço)pack— pacote com N unidades (curso com aulas, conjunto de itens)credits— créditos (saldo pré-pago para uso futuro)contribution— doação, gorjeta, apoio voluntário; pagador define o valorbundle— combo agrupando outros produtos (ver seção "Bundle")
Modo de preço (price_mode):
| Valor | Comportamento |
|---|---|
fixed (default) | Cliente paga exatamente price |
customer_defined | Cliente digita o valor no checkout. price vira sugestão pré-preenchida. |
Para customer_defined, configure piso/teto:
min_price(decimal, opcional) — valor mínimo aceitomax_price(decimal, opcional) — teto opcional
O tipo
contributionautomaticamente forçaprice_mode=customer_defined. Para um produto regular ("Pague quanto quiser pelo curso"), useone_timecomprice_mode=customer_defined.
Listar / Detalhes / Atualizar / Excluir:
GET /api/v1/products/
GET /api/v1/products/{id}/
PUT /api/v1/products/{id}/
PATCH /api/v1/products/{id}/
DELETE /api/v1/products/{id}/
Filtros: active, product_type, name__icontains.
Bundle (combo de produtos)
Agrupa N produtos em uma única venda com preço promocional.
GET /api/v1/bundle-items/?bundle=<uuid>
POST /api/v1/bundle-items/
PATCH /api/v1/bundle-items/{id}/
DELETE /api/v1/bundle-items/{id}/
POST /api/v1/bundle-items/
{
"bundle": "uuid-do-produto-tipo-bundle",
"item": "uuid-do-produto-incluido",
"quantity": 1,
"position": 0
}
Restrições:
- O produto pai precisa ter
product_type='bundle'. - Bundle aninhado (bundle dentro de bundle) é rejeitado.
- Item precisa pertencer à mesma organização.
Comportamento no checkout: o pagador paga bundle.price e o backend cria 1 Order parent (com a receita) + N child Orders com unit_price=0 (marcadas em metadata.bundle_parent_id). Cada child Order tem o produto incluído + quantidade — use isso pra provisionar a entrega item por item via webhook.
O webhook canônico inclui payment.order.bundle_items[] com {order_id, product_id, product_name, quantity} para todos os itens incluídos.
Cupons
Códigos de desconto aplicáveis no checkout público.
GET /api/v1/coupons/
POST /api/v1/coupons/
GET /api/v1/coupons/{id}/
PATCH /api/v1/coupons/{id}/
DELETE /api/v1/coupons/{id}/
POST /api/v1/coupons/
{
"code": "BLACKFRIDAY",
"discount_type": "percent", // "percent" | "fixed"
"discount_value": "20.00", // 20% ou R$ 20,00 conforme o tipo
"valid_until": "2026-12-31T23:59:59Z",
"usage_limit": 100, // 0 = ilimitado
"min_amount": "50.00",
"is_active": true,
"plans": [], // lista de UUIDs (vazio = todos os planos)
"products": [] // lista de UUIDs (vazio = todos os produtos)
}
Order Bumps
Ofertas adicionais exibidas no checkout do produto principal.
GET /api/v1/order-bumps/?parent_product=<uuid>
POST /api/v1/order-bumps/
PATCH /api/v1/order-bumps/{id}/
DELETE /api/v1/order-bumps/{id}/
POST /api/v1/order-bumps/
{
"parent_product": "uuid-produto-principal",
"bump_product": "uuid-produto-adicional",
"title": "Adicione o e-book complementar",
"description": "Material extra para aprofundar",
"discount_percent": "30.00" // 30% off sobre o preço do bump_product
}
Quando o cliente aceita um ou mais bumps no checkout, é criado 1 Order parent (com o total cobrado) + N child Orders (metadata.via='order_bump', metadata.parent_order_id=<parent.id>). O webhook PAYMENT_RECEIVED inclui:
payment.order.bump— primeiro bump (compat).payment.order.bumps[]— todos os bumps aceitos com{order_id, product_id, product_name, amount}.
Checkout público (sem auth)
Endpoints sem API key — alimentam a página https://vitrin.digital/c/<org-slug>/<item-slug>.
GET /api/v1/public/checkout/<org_slug>/<item_slug>/
POST /api/v1/public/checkout/<org_slug>/<item_slug>/pay/
POST /api/v1/public/checkout/<org_slug>/coupon/validate/
GET /api/v1/public/checkout/payments/<tx_id>/status/
GET /<item_slug>/ — retorna produto/plano + branding da org + bumps disponíveis (order_bumps[]). Slug pode apontar tanto pra Plan quanto pra Product (resolução 1:1; em conflito Product ganha).
POST .../pay/ — cria customer guest e cobra:
{
"customer_name": "Maria Silva",
"customer_cpf_cnpj": "12345678900",
"customer_email": "maria@exemplo.com",
"customer_phone": "11999998888",
"customer_birth_date": "1990-03-15",
"customer_company_name": "Empresa LTDA",
"customer_postal_code": "01310100",
"customer_address": "Av Paulista",
"customer_address_number": "1000",
"customer_complement": "Apto 12",
"customer_province": "Bela Vista",
"customer_city": "São Paulo",
"customer_state": "SP",
"custom_data": { "como_conheceu": "Instagram" },
"billing_type": "CREDIT_CARD",
"installments": 3,
"credit_card_token": "tok_...",
"custom_amount": "75.00",
"coupon_code": "PROMO10",
"bump_ids": ["uuid-bump-1", "uuid-bump-2"]
}
- Sistema (sempre obrigatórios):
customer_name,customer_cpf_cnpj. - Toggleáveis: os demais
customer_*são exigidos conformefield_configda org (retornado embranding.field_configno GET acima). Se um campoenabled=true, required=truevem vazio, a resposta é400 {error, missing: [...]}. custom_dataaceita as chaves definidas embranding.field_config.custom_fields— campos required vazios também caem na mesma validação.custom_amountobrigatório quandoproduct.price_mode=customer_defined.coupon_codevalida e aplica desconto se válido.bump_ids[]cria N child Orders adicionais vinculados viametadata.parent_order_id(até 10 por checkout).bump_idsingular continua aceito por compat.
Resposta inclui transaction_id, order_id/subscription_id, pix_qr_code/boleto_url quando aplicável, e thank_you_url para redirecionamento pós-pagamento.
POST .../coupon/validate/ — útil para validação em tempo real no front:
{ "code": "PROMO10", "item_slug": "curso-pro", "amount": "297.00" }
Configurações do Checkout (org)
Branding e campos do formulário usados pelas páginas públicas:
GET /api/v1/checkout/settings/
PUT /api/v1/checkout/settings/
{
"logo_url": "https://...",
"primary_color": "#00BCB5",
"accent_color": "#390043",
"show_org_name": true,
"support_email": "ajuda@minha-org.com",
"support_whatsapp": "+5511...",
"default_thank_you_url": "https://...",
"terms_text": "Ao comprar, você concorda com...",
"field_config": {
"fields": {
"email": {"enabled": true, "required": true},
"phone": {"enabled": true, "required": false},
"birth_date": {"enabled": false, "required": false},
"address": {"enabled": true, "required": true},
"address_number": {"enabled": true, "required": true},
"complement": {"enabled": true, "required": false},
"postal_code": {"enabled": true, "required": true},
"city": {"enabled": true, "required": true},
"state": {"enabled": true, "required": true}
},
"custom_fields": [
{
"key": "como_conheceu",
"label": "Como nos conheceu?",
"type": "select",
"required": true,
"options": ["Instagram", "Indicação", "Google"]
}
]
}
}
Sobre field_config:
- Campos sistema (
name,cpf_cnpj) sempre aparecem e são obrigatórios — não toggleáveis. - Toggleáveis:
email,phone,birth_date,address,address_number,complement,postal_code,city,state. Cada um temenablederequired. custom_fields[]: tipos suportados —text,email,tel,select,textarea. Paraselect, populeoptions[].- O backend sempre devolve
field_configmesclado com defaults — campos não enviados em PUT continuam com o valor anterior; chaves desconhecidas são ignoradas. - O endpoint público
GET /public/checkout/<org>/<slug>/devolvebranding.field_configjá consolidado com os requisitos dos métodos habilitados (a UI recebe o conjunto efetivo, não precisa fazer merge).
Requisitos automáticos por método de pagamento. Independente do field_config configurado, cada método tem campos mínimos exigidos pelo provedor — e a Vitrin força esses campos como enabled=true, required=true quando o método correspondente é o escolhido na cobrança:
Método (billing_type) | Campos exigidos automaticamente |
|---|---|
PIX / PIX_AUTOMATIC | email |
CREDIT_CARD | email, phone |
BOLETO | email, postal_code, address, address_number, city, state |
O endpoint público também devolve branding.method_requirements — um mapa {billing_type: [field_keys]} — para o frontend ajustar dinamicamente o que mostrar como obrigatório quando o usuário troca de método. A validação no POST .../pay/ aplica os requisitos do método selecionado (não a união de todos): pagador pode pagar via Pix sem informar endereço, e via Boleto será obrigado.
Métodos disponíveis (enabled_payment_methods). A organização escolhe quais métodos expor no checkout entre os que o admin Vitrin liberou (allowed_payment_methods). O endpoint GET /public/checkout/<org>/<slug>/ devolve apenas os métodos enabled_payment_methods. Tentar pagar com método não habilitado retorna 400.
Pedidos
Criar:
POST /api/v1/orders/create/
{
"customer_id": "uuid-do-cliente",
"product_id": "uuid-do-produto",
"billing_type": "PIX",
"quantity": 1
}
O pedido cria a cobrança automaticamente. Quando o webhook PAYMENT_RECEIVED chega, o pedido muda para paid.
Listar:
GET /api/v1/orders/?status=paid&page=1
Status possíveis: pending, paid, fulfilled, cancelled. Filtros: status, product_id, customer_id.
Detalhes:
GET /api/v1/orders/{id}/
Inclui o cliente, produto, transação vinculada e timestamps de cada transição de status.
Marcar como entregue (fulfill):
POST /api/v1/orders/{id}/fulfill/
Use depois que você entregou o produto (mandou ingresso por e-mail, liberou acesso ao curso, despachou o item físico). Marca fulfilled_at e expõe ao cliente final via dashboard.
Pedidos são read-only via REST (não há PUT/PATCH/DELETE direto). Para modificar, cancele e crie um novo.
8. Reembolso e Cancelamento
Reembolso (requer PIN)
POST /api/v1/payments/{id}/refund/
{
"pin": "123456",
"amount": 50.00
}
Omita amount para reembolso total.
Cancelar cobrança pendente
POST /api/v1/payments/{id}/cancel/
Só funciona para cobranças com status pending ou confirmed.
9. Saldo e Transferências
Consultar saldo
GET /api/v1/balance/
Resposta:
{
"available": 1500.00,
"total": 2300.00,
"pending": 800.00,
"withdrawal_fees": { "pix": 3.49, "ted": 9.49 }
}
- available: pronto para saque
- pending: aguardando compensação (D+1 Pix, D+2 Boleto, D+30 Crédito)
- total: available + pending
Cronograma de recebíveis
GET /api/v1/balance/scheduled/?days_ahead=90
Lista quando cada cobrança paga vai cair na conta. Útil pra projeção de fluxo de caixa.
Resposta:
{
"horizon_days": 90,
"total_scheduled": 12500.0,
"count": 18,
"by_day": [
{ "date": "2026-04-29", "total": 350.0, "count": 2 },
{ "date": "2026-04-30", "total": 199.9, "count": 1 }
],
"items": [
{
"release_date": "2026-04-29",
"amount": 99.9,
"method": "PIX",
"installment": null,
"transaction_id": "tx_xxx",
"anticipated": false
},
{
"release_date": "2026-05-15",
"amount": 100.0,
"method": "CREDIT_CARD",
"installment": "1/3",
"transaction_id": "tx_yyy",
"anticipated": false
}
]
}
Regras de release: PIX = paid_at + 1d; Boleto = paid_at + 2d; Cartão Nx = uma linha por parcela em paid_at + 30·i. Antecipação consolida tudo em paid_at + 1d.
Limitação importante:
/balance/scheduled/é uma projeção heurística baseada empaid_at+ janela do método. Ela não considera cut-off de horário do processador, retenção de saldo mínimo, falhas de repasse, fins de semana ou feriados. Para previsão fiel do que vai cair na sua conta bancária hoje (ou nos próximos dias úteis), use/treasury/calendar/— esse sim consulta o processador em tempo real e devolve o estado oficial (Previsto, Depositado, Rejeitado).
Calendário de saques (fonte de verdade)
GET /api/v1/treasury/calendar/?start=YYYY-MM-DD&end=YYYY-MM-DD
Reflete exatamente o calendário oficial do processador — esse é o endpoint que sua app deve consultar para mostrar "Caindo hoje", "Próximos saques", "Já depositado", "Recusado". Cada item corresponde a um repasse concreto que vai sair (ou já saiu) para a conta bancária cadastrada da sua loja, com a janela de cut-off do processador já aplicada.
Resposta:
{
"range": { "start": "2026-05-18", "end": "2026-05-22" },
"totals": {
"previsto": 3890.29,
"depositado": 3249.30,
"disponivel": 0.00,
"rejeitado": 0.00,
"indefinido": 0.00
},
"count": 5,
"items": [
{
"status": "Depositado",
"amount": 3249.30,
"release_date": "2026-05-20T00:00:00",
"is_today": true,
"is_transferred": false,
"message": "Pagamento Efetivado."
},
{
"status": "Previsto",
"amount": 3890.29,
"release_date": "2026-05-21T00:00:00",
"is_today": false,
"is_transferred": false,
"message": ""
}
]
}
Status possíveis em items[].status:
| Status | Significado |
|---|---|
Previsto | Repasse agendado pra essa data (ainda não saiu da conta do processador). |
Depositado | Repasse efetivado — o valor já caiu (ou está caindo) na sua conta bancária. |
Disponível | Saldo acumulado disponível pra saque (orgs com auto-repasse desligado). |
Rejeitado | Repasse falhou — message traz o motivo (dados bancários ausentes, conta inativa, etc.). |
Indefinido | Placeholder do processador antes da consolidação do dia. Ignore no agregado. |
Como usar nas suas telas:
- "Caindo hoje" =
itemsonderelease_date == today AND status == 'Depositado'→ someamount. Esse é o número que vai bater exato com o extrato bancário da sua loja no dia. - "Próximos saques" =
itemsondestatus == 'Previsto', agrupado porrelease_date. - "Saldo disponível pra saque" =
totals.disponivel(só pra orgs com auto-repasse pausado). - "Repasses falhos" =
itemsondestatus == 'Rejeitado'— mostre omessageao operador da loja.
Não some
Transaction.net_amountlocalmente para estimar saque do dia. Esse cálculo ignora:
- Cut-off horário do processador (cobranças pagas após ~18h BRT entram no batch do dia seguinte).
- Reservas / retenções de saldo mínimo aplicadas pelo processador.
- Fins de semana e feriados (repasse roda em dia útil).
- Recusas de TED/Pix (conta destino inativa, divergência de titular, etc.).
A diferença entre soma local e valor real chega a 5–10% facilmente. Use
/treasury/calendar/sempre que precisar do número que vai bater com o extrato bancário.
Descritor do Pix de repasse (limitação de white-label)
Quando o processador transfere o repasse para a conta bancária da sua loja, o descritor da transação no extrato bancário do destinatário (ex.: Cora, Nubank, Itaú) carrega o nome legal da instituição de pagamento que executou o Pix — não o nome da Vitrin nem o da sua loja. Essa é uma exigência regulatória do SPI/BACEN: o ordenador da transferência precisa identificar quem está mandando o dinheiro.
Pra você como operador da loja, isso aparece no seu próprio extrato (R$ X recebido de "<nome da instituição de pagamento>") e não há configuração para mudar — vale tanto pra Vitrin quanto pra qualquer outro intermediador no Brasil.
Esse vazamento de marca é restrito ao extrato bancário do repasse. Em toda outra superfície (checkout, e-mails transacionais, webhooks, dashboard) a Vitrin se mantém white-label e a sua loja aparece como a marca-fim.
Transferir via Pix (requer PIN)
POST /api/v1/transfers/pix/
{
"value": 500.00,
"pix_key": "12345678909",
"pix_key_type": "CPF",
"pin": "123456"
}
Tipos de chave: CPF, CNPJ, EMAIL, PHONE, EVP (aleatória).
Taxa de saque retida do valor: pediu R$500, R$500 são debitados da sua conta de pagamentos, R$500 − fee vai pro destino. Resposta carrega
requested,withdrawal_feeenet_to_destinationpra contabilização. Saques ≤ taxa retornam HTTP 400.
Transferir via dados bancários (requer PIN)
POST /api/v1/transfers/bank/
{
"value": 500.00,
"bank_code": "237",
"agency": "0001",
"account": "12345",
"account_digit": "6",
"account_type": "CONTA_CORRENTE",
"owner_name": "Maria Silva",
"cpf_cnpj": "12345678909",
"pin": "123456"
}
Histórico de transferências
GET /api/v1/transfers/
Combina transferências locais (já solicitadas pela sua org) + dados ao vivo do processador (status real, falhas, datas de efetivação).
9.5. Programa de Afiliados
A Vitrin oferece um programa de afiliação no padrão Hotmart/Kiwify: terceiros se cadastram como afiliados na plataforma, sua loja libera produtos e planos para afiliação com uma comissão definida, eles compartilham um link rastreado e cada venda atribuída paga uma comissão automática no afiliado. Toda a infraestrutura de atribuição, ledger, repasse e estorno é da Vitrin — sua API só precisa ligar o programa, configurar comissões e (opcional) receber os blocos affiliate / coproducers no webhook.
9.5.1. Como funciona, em uma frase
Quando o cliente chega no seu checkout via ?aff=<slug-do-afiliado>, a Vitrin grava um cookie de 1ª parte com validade attribution_window_days (padrão 30 dias, configurável de 7 a 90). Se o pagamento acontecer dentro dessa janela, a comissão configurada no produto/plano (ou o default do programa) é debitada do líquido da sua venda e creditada na carteira do afiliado. Sua taxa Vitrin continua a mesma — afiliado não sai da fatia da plataforma.
9.5.2. Ligar o programa
PATCH /api/v1/organization/affiliate-program/
{
"enabled": true,
"attribution_window_days": 30,
"requires_approval": true,
"default_commission_pct": "20.00",
"default_commission_recurring_pct": "10.00",
"public_marketplace": false
}
requires_approval=true(recomendado): cada afiliado que pede para promover seus produtos fica empendingaté você aprovar.falselibera afiliação automática — use só em programas abertos.default_commission_pcté a comissão default aplicada quando o produto/plano não tem comissão própria.default_commission_recurring_pcté a comissão default em renovações (assinatura/Pix Automático). Em branco usa a mesma da primeira venda.public_marketplace=truelista seus produtos no marketplace público da Vitrin (qualquer afiliado descobre).falsemantém privado — só quem você convidar e for aprovado consegue ver e promover.
Para ler o estado atual:
GET /api/v1/organization/affiliate-program/
9.5.3. Comissão por produto / plano
Cada produto e plano pode sobrescrever o default do programa.
PUT /api/v1/products/{product_id}/affiliate-config/
{
"enabled": true,
"commission_pct": "30.00",
"auto_approve": false,
"max_affiliates": null
}
PUT /api/v1/plans/{plan_id}/affiliate-config/
{
"enabled": true,
"commission_pct": "30.00",
"commission_recurring_pct": "15.00",
"affiliate_recurring_months": null,
"auto_approve": false,
"max_affiliates": null
}
enabled=falseretira o item do marketplace e bloqueia novos pedidos. Comissões já liquidadas não são afetadas.auto_approve=truefaz com que pedidos pra este item específico virem aprovados sem moderação (útil pra produtos de baixo risco mesmo comrequires_approval=trueno programa).max_affiliates: limite opcional de quantos afiliados podem estar simultaneamente aprovados no item.- Para planos (recorrência):
commission_recurring_pct: comissão paga nas renovações. Em branco usacommission_pct.affiliate_recurring_months: cap em N renovações. Em branco = vitalícia enquanto a assinatura existir.
Importante — A comissão é calculada sobre o preço do produto, não sobre o valor cobrado do cliente. Em cartão parcelado, os juros não entram na base da comissão (juros são receita exclusiva da plataforma).
9.5.4. Moderar pedidos de afiliação
Quando um afiliado solicita seu produto via marketplace, ele aparece em:
GET /api/v1/organization/affiliate-applications/?status=pending
Resposta:
[
{
"id": "<uuid-app>",
"affiliate_slug": "joao-silva",
"product_slug": "ebook-marketing",
"plan_slug": null,
"target_name": "E-book Marketing",
"status": "pending",
"created_at": "2026-05-19T22:34:56Z"
}
]
Para aprovar, rejeitar ou revogar:
POST /api/v1/affiliate-applications/{app_id}/moderate/
{
"action": "approve",
"commission_pct_override": "35.00",
"commission_recurring_pct_override": "18.00"
}
action:approve,rejectourevoke.commission_pct_override/commission_recurring_pct_override: opcional — define comissão personalizada só pra este afiliado nesse produto/plano. Use pra parceiros estratégicos. Em branco, herda do produto/plano/programa.rejection_reason: usado emaction=reject, mostrado ao afiliado.
9.5.5. Tracking no checkout
Se você usa o checkout público da Vitrin (/c/{org}/{item}), não precisa fazer nada — a captura de ?aff= e o cookie de atribuição já são tratados pelo frontend hospedado.
Se você roda checkout próprio chamando /api/v1/checkout/ diretamente:
- Capture
?aff=na URL de chegada do cliente. - Guarde em cookie/localStorage de 1ª parte (TTL ≤
attribution_window_days). - (Opcional, mas recomendado) chame:
pra registrar o clique antes da conversão (alimenta o histórico do afiliado).POST /api/v1/public/affiliate-track/ { "affiliate_slug": "joao-silva", "org_slug": "minha-loja", "product_slug": "ebook-marketing" } - No POST do checkout, envie no body:
{ "affiliate_slug": "joao-silva", "affiliate_attribution_ts": 1716234567890 }affiliate_attribution_tsé o epoch em milissegundos do clique original — usado pra rechecar a janela no servidor. A Vitrin valida o slug, a janela e a permissão do afiliado no item; em qualquer falha, a venda segue sem atribuição (falha-soft, nunca quebra o checkout).
9.5.6. Relatório de comissões
GET /api/v1/organization/affiliate-report/
Resposta:
{
"totals": {
"settled": "1234.56",
"pending": "78.90",
"reversed": "12.00"
},
"recent": [
{
"id": "<uuid-comissao>",
"org_slug": "minha-loja",
"product_slug": "ebook-marketing",
"is_recurring": false,
"sale_amount": "100.00",
"commission_pct": "30.00",
"commission_amount": "30.00",
"status": "settled",
"settled_at": "2026-05-19T22:34:56Z",
"created_at": "2026-05-19T22:30:00Z"
}
]
}
totals agrega todas as comissões da loja por status. recent traz as 200 últimas. Use pra reconciliação e relatórios próprios — o webhook (§5) já entrega o detalhe transação a transação.
9.5.7. Pontos importantes
- Comissão de afiliado é debitada do líquido da sua loja, não da taxa Vitrin. Modelo idêntico ao Hotmart/Kiwify.
- Estorno proporcional: quando uma cobrança com comissão é estornada (refund ou chargeback), a comissão é revertida proporcionalmente. Se o afiliado já tinha sacado, o saldo dele fica negativo e a Vitrin compensa no próximo repasse — você não precisa cobrar do afiliado.
- Renovações de assinatura/Pix Automático propagam o afiliado automaticamente. O cap (
affiliate_recurring_months) limita por quantas renovações o afiliado continua ganhando. - Coprodutores (split fixo, sem janela de atribuição) são entidade separada de afiliado. A configuração é feita por produto no painel.
- White-label: o afiliado nunca vê dados da sua loja além do que você publica (nome, descrição, preço, comissão). Pra ele, a Vitrin é a plataforma. Pra você, ele aparece só como
slugno webhook.
10. Antecipação de Recebíveis
Antecipação te permite receber agora valores que cairiam no futuro (cartão D+30, parcelado D+(30·N), boleto D+2). A taxa é proporcional ao tempo restante até a liberação original e é aplicada por transação.
Pré-requisito: a org precisa ter
allow_anticipation=true(ativado pelo admin no signup). Tentativas com a feature desabilitada retornam HTTP 403.
Listar transações elegíveis
GET /api/v1/anticipations/?status=eligible
Lista as Transactions com status='received' que ainda não foram antecipadas. Use ?status=anticipated para ver as já antecipadas.
Simular antes de solicitar
Antes de gastar, simule pra ver quanto você recebe líquido:
POST /api/v1/anticipations/simulate/
{ "payment_id": "uuid-da-transacao" }
Resposta (vinda do processador):
{
"value": 100.00,
"fee": 5.50,
"netValue": 94.50
}
Em sandbox, o processador pode recusar a simulação (a conta de pagamentos de teste não tem permissão para antecipar). O dashboard cai em uma estimativa local com base nas suas taxas — programaticamente, trate o erro 502 e calcule você mesmo se quiser.
Solicitar antecipação (requer PIN)
POST /api/v1/anticipations/request/
{
"payment_id": "uuid-da-transacao",
"pin": "123456"
}
Resposta:
{
"transaction_id": "uuid",
"anticipated": true,
"anticipation_fee": 5.50,
"result": { ... resposta crua do processador ... }
}
A Transaction passa a ter anticipated=true e o valor cai em available no /balance/ em D+2 útil.
11. Defesa de Chargeback
Disputa é sempre manual. O lojista sustenta a defesa diretamente com a operadora do cartão. Não há endpoint de submissão automática. A Vitrin é o organizador: você anexa evidências aqui, baixa o dossiê pré-montado e encaminha pra operadora pelo canal manual.
Listar disputas em aberto
GET /api/v1/chargebacks/
Retorna transações com status chargeback ou disputed, com evidence_count por transação.
Anexar evidências
POST /api/v1/payments/{id}/evidences/
Content-Type: multipart/form-data
file: <arquivo> # PDF, imagem, etc — máx 25 MB
evidence_type: receipt # taxonomia abaixo
description: "" # opcional
Tipos válidos: delivery_proof, communication, contract, receipt, refund_attempt, ip_log, other.
GET /api/v1/payments/{id}/evidences/ # listar
DELETE /api/v1/payments/{id}/evidences/{ev_id}/ # remover (bloqueia se já submetida)
Storage é privado (URLs presigned com expiração de 1h). Pode coletar evidências preventivamente em transações ainda não disputadas (status received/confirmed) — útil pra ter o material pronto antes de qualquer disputa surgir.
Dossiê de defesa pré-montado
A Vitrin compila automaticamente o dossiê com tudo que ela tem da transação:
GET /api/v1/payments/{id}/chargeback-defense/
Resposta:
{
"transaction": {
"id": "uuid",
"amount": 99.90,
"billing_type": "CREDIT_CARD",
"installments": 1,
"paid_at": "2026-04-09T15:30:00Z",
"payer_ip": "189.6.x.y"
},
"customer": {
"name": "Maria Silva",
"cpf_cnpj": "12345678909",
"email": "maria@email.com",
"phone": "11988776655",
"address": "Rua das Flores, 42 - Centro",
"city_state": "São Paulo/SP"
},
"order": {
"product_name": "Curso Premium",
"fulfilled_at": "2026-04-09T16:00:00Z"
},
"subscription": null,
"previous_transactions": [
{ "id": "uuid", "amount": 49.90, "paid_at": "2026-03-15T..." }
],
"webhook_events": [
{ "event_type": "PAYMENT_CONFIRMED", "created_at": "..." },
{ "event_type": "PAYMENT_RECEIVED", "created_at": "..." }
],
"audit_trail": [
{ "action": "transaction.created", "timestamp": "...", "ip_address": "..." }
]
}
Use esses dados pra montar a contestação no painel da bandeira (Visa/Master) ou via processador. O ideal é guardar tudo: comprovante de entrega, IP do pagador, transações anteriores do mesmo cliente bem-sucedidas, fulfillment do pedido, eventos de webhook que comprovam a confirmação do pagamento.
Boas práticas para reduzir chargebacks: cobre só após confirmar identidade, marque pedidos como
fulfilledassim que entregar, mantenha cadastro de cliente completo (endereço!), responda chargebacks rapidamente.
12. Configurações da Org
Self-service de tudo que você pode customizar pelo dashboard /dashboard/settings/.
Ler configurações atuais
GET /api/v1/settings/
Resposta:
{
"enabled_payment_methods": ["PIX", "CREDIT_CARD", "BOLETO"],
"org_max_installments": 12,
"org_min_installment_value": "10.00",
"auto_anticipation_enabled": false,
"checkout_logo": "https://...",
"checkout_primary_color": "#00BCB5",
"webhook_url": "https://suaapp.com/webhooks/vitrin",
"antifraud_enabled": false,
"payout_frequency": "monthly",
"allowed_payment_methods": ["PIX", "CREDIT_CARD", "BOLETO"],
"max_installments": 12,
"min_installment_value": "10.00",
"allow_anticipation": true,
"allow_auto_anticipation": false,
"fee_percentage_pix": "5.49",
"fee_fixed_pix": "3.49",
"fee_percentage_credit": "5.49",
"anticipation_rate_credit_vista": "2.99",
"withdrawal_fee_fixed_pix": "3.49"
}
Os campos dividem-se em três blocos:
| Bloco | Editável pela org? |
|---|---|
Preferências da org (enabled_payment_methods, org_max_installments, auto_anticipation_enabled, antifraud_enabled, payout_frequency, checkout_logo, checkout_primary_color) | ✅ via PATCH |
Limites da plataforma (allowed_payment_methods, max_installments, allow_anticipation) | ❌ definido pelo admin no onboarding |
Tabela de taxas (fee_*, anticipation_*, withdrawal_*) | ❌ congelada no Anexo Comercial assinado |
Atualizar (parcial)
PATCH /api/v1/settings/
{
"enabled_payment_methods": ["PIX", "CREDIT_CARD"],
"checkout_primary_color": "#FD8000",
"auto_anticipation_enabled": true
}
A
enabled_payment_methodsprecisa ser um subconjunto deallowed_payment_methods. Tentar habilitar um método não-permitido retorna HTTP 400.
Configurar webhook (já documentado em §5)
PUT /api/v1/webhooks/config/ — requer PIN.
Conta bancária para repasse
GET /api/v1/settings/bank-account/
PUT /api/v1/settings/bank-account/ (requer PIN)
{
"bank_code": "237",
"bank_agency": "1234",
"bank_agency_digit": "0",
"bank_account": "987654",
"bank_account_digit": "5",
"bank_account_type": "CHECKING",
"pin": "123456"
}
bank_account_type: CHECKING (corrente) ou SAVINGS (poupança). O titular precisa estar no mesmo CPF/CNPJ da org — o processador rejeita repasse pra conta de terceiros.
Checklist pós-aprovação
GET /api/v1/onboarding/checklist/
Retorna uma lista de 4 passos (conta bancária, webhook, primeiro plano/produto, primeira cobrança) com status completed: true|false cada. Usado pelo dashboard pra renderizar o widget "ative sua conta" — útil também pra mostrar progresso ao usuário no seu próprio painel.
{
"is_approved": true,
"completed_count": 2,
"total_count": 4,
"all_completed": false,
"steps": [
{ "key": "bank_account", "completed": true, ... },
{ "key": "webhook", "completed": false, "cta_label": "Configurar", "cta_href": "/dashboard/webhooks" },
{ "key": "product_or_plan", "completed": true, ... },
{ "key": "first_charge", "completed": false, "cta_label": "Ver integração", "cta_href": "/docs/integracao" }
]
}
Export LGPD dos dados da org
A org pode pedir um snapshot completo dos próprios dados (clientes, transações, assinaturas, planos, produtos) atendendo ao direito de portabilidade da LGPD.
POST /api/v1/export/
{ "format": "json" } # ou "csv" (zip com 5 tabelas)
Cria um export pending, agenda processamento async, e retorna o registro. O processamento leva alguns segundos. Quando completa, a org recebe um email com link de download (URL presigned, 1h de validade).
GET /api/v1/export/ # histórico + status atuais
GET /api/v1/export/{id}/download/ # gera URL presigned fresca
Limites: 1 export pendente/processando por org (HTTP 429 se já tem um em andamento), retenção de 90 dias após conclusão.
13. Relatórios
Relatórios agregados sobre a operação da sua org. Todos retornam dados restritos à org autenticada.
Overview do dashboard
GET /api/v1/reports/overview/?days=30
Resposta:
{
"total_revenue": 28900.50,
"total_transactions": 142,
"active_subscriptions": 23,
"active_customers": 89,
"anticipated_value": 8500.00,
"by_method": {
"PIX": 18200.00,
"CREDIT_CARD": 9100.50,
"BOLETO": 1600.00
}
}
Receita por dia
GET /api/v1/reports/revenue/?days=30
Retorna uma série temporal com receita bruta e líquida por dia, pronta para gráficos.
Receita mensal
GET /api/v1/reports/revenue/monthly/?months=12
Transações com filtros
GET /api/v1/reports/transactions/?status=received&billing_type=PIX&from=2026-04-01&to=2026-04-30
Filtros: status, billing_type, customer_id, from, to, min_amount, max_amount.
Churn de assinaturas
GET /api/v1/reports/churn/
Retorna taxa de cancelamento por mês, MRR perdido e principais motivos.
Relatório de antecipações
GET /api/v1/reports/anticipation/?from=2026-04-01&to=2026-04-30
Resumo de antecipações solicitadas no período: total antecipado, taxa total paga, economia média de tempo.
14. Referência Rápida de Endpoints
Checkout
| Método | Endpoint | Descrição |
|---|---|---|
| POST | /checkout/pix/ | Cobrar via Pix |
| POST | /checkout/boleto/ | Cobrar via Boleto |
| POST | /checkout/tokenize/ | Tokenizar cartão |
| POST | /checkout/pay/ | Cobrar com token |
| POST | /checkout/guest/ | Checkout sem cadastro prévio |
| GET | /checkout/installments/?amount=X | Consultar parcelamento |
Pix Automático
| Método | Endpoint | Descrição |
|---|---|---|
| GET | /pix-automatic/authorizations/ | Listar autorizações |
| POST | /pix-automatic/authorizations/ | Criar (gera QR Code de aceite) |
| GET | /pix-automatic/authorizations/{id}/ | Detalhes |
| PATCH | /pix-automatic/authorizations/{id}/ | Alterar (value/min_limit_value/finish_date/description) |
| DELETE | /pix-automatic/authorizations/{id}/ | Cancelar (PIN) |
| POST | /pix-automatic/authorizations/{id}/charges/ | Criar cobrança recorrente (2-10 dias úteis) |
| GET | /pix-automatic/authorizations/{id}/instructions/ | Listar instruções da autorização |
| POST | /pix-automatic/authorizations/{id}/sync/ | Forçar sync com o provedor |
| GET | /pix-automatic/instructions/ | Todas as instruções da org |
| GET | /pix-automatic/instructions/{id}/ | Detalhe da instrução |
| POST | /pix-automatic/instructions/{id}/sync/ | Forçar sync da instrução |
| DELETE | /pix-automatic/instructions/{id}/ | Cancelar agendamento (PIN) |
Transações
| Método | Endpoint | Descrição |
|---|---|---|
| GET | /transactions/ | Listar transações |
| GET | /payments/{id}/status/ | Status (polling Pix) |
| POST | /payments/{id}/refund/ | Reembolsar (PIN) |
| POST | /payments/{id}/cancel/ | Cancelar pendente |
| GET | /payments/{id}/chargeback-defense/ | Dossiê de defesa |
Clientes
| Método | Endpoint | Descrição |
|---|---|---|
| GET | /customers/ | Listar clientes (paginado) |
| POST | /customers/ | Criar cliente |
| GET | /customers/{id}/ | Detalhes |
| PUT/PATCH | /customers/{id}/ | Atualizar |
| DELETE | /customers/{id}/ | Excluir (bloqueado se houver transações) |
Assinaturas
| Método | Endpoint | Descrição |
|---|---|---|
| GET | /subscriptions/ | Listar assinaturas |
| POST | /subscriptions/ | Criar assinatura |
| POST | /subscriptions/{id}/cancel/ | Cancelar |
| GET | /subscriptions/{id}/detail/ | Detalhe com dados do provedor |
Planos
| Método | Endpoint | Descrição |
|---|---|---|
| GET | /plans/ | Listar planos |
| POST | /plans/ | Criar plano |
| PUT | /plans/{id}/ | Atualizar |
| DELETE | /plans/{id}/ | Excluir |
Produtos e Pedidos
| Método | Endpoint | Descrição |
|---|---|---|
| GET | /products/ | Listar produtos |
| POST | /products/ | Criar produto |
| GET | /products/{id}/ | Detalhes |
| PUT/PATCH | /products/{id}/ | Atualizar |
| DELETE | /products/{id}/ | Excluir |
| POST | /orders/create/ | Criar pedido (gera cobrança) |
| GET | /orders/ | Listar pedidos |
| GET | /orders/{id}/ | Detalhes |
| POST | /orders/{id}/fulfill/ | Marcar entregue |
Contratos eletrônicos (read-only)
| Método | Endpoint | Descrição |
|---|---|---|
| GET | /contracts/status/ | Visão geral (pending + active + all_signed) |
| GET | /contracts/pending/ | Apenas contratos a assinar |
| GET | /contracts/active/ | Apenas vigentes |
| GET | /contracts/{id}/preview/ | Render do template para leitura |
| GET | /contracts/history/ | Histórico de aceitações |
| GET | /contracts/acceptances/{id}/ | Detalhe de uma aceitação |
Assinar é ação humana — feita em
/dashboard/contractcom PIN.
Onboarding (read-only)
| Método | Endpoint | Descrição |
|---|---|---|
| GET | /onboarding/status/ | Status do compliance interno |
| GET | /onboarding/checklist/ | Checklist pós-aprovação (4 passos) |
Upload de documentos é ação humana — feita em
/dashboard/onboarding.
Auth (rotação de chave)
| Método | Endpoint | Descrição |
|---|---|---|
| POST | /auth/api-key/regenerate/ | Rotaciona a API key (requer PIN) |
Cadastro, login, PIN e password reset são ações humanas — feitas no dashboard.
Financeiro
| Método | Endpoint | Descrição |
|---|---|---|
| GET | /balance/ | Saldo (disponível + pendente) |
| GET | /balance/scheduled/ | Cronograma de recebíveis (projeção heurística) |
| GET | /treasury/calendar/ | Calendário oficial de saques (Previsto/Depositado/Rejeitado — use pra "Caindo hoje") |
| GET | /finance/statement/ | Extrato financeiro |
| POST | /transfers/pix/ | Transferir via Pix (PIN) |
| POST | /transfers/bank/ | Transferir via banco (PIN) |
| GET | /transfers/ | Histórico de transferências |
Antecipação
| Método | Endpoint | Descrição |
|---|---|---|
| GET | /anticipations/ | Listar transações elegíveis |
| POST | /anticipations/simulate/ | Simular antecipação |
| POST | /anticipations/request/ | Solicitar (PIN) |
Configurações
| Método | Endpoint | Descrição |
|---|---|---|
| GET | /settings/ | Configurações da org |
| PATCH | /settings/ | Atualizar configurações |
| GET | /settings/bank-account/ | Conta bancária de repasse |
| PUT | /settings/bank-account/ | Atualizar conta bancária (PIN) |
Defesa de chargeback
| Método | Endpoint | Descrição |
|---|---|---|
| GET | /chargebacks/ | Lista de transações em disputa |
| GET | /payments/{id}/chargeback-defense/ | Dossiê pré-montado |
| GET | /payments/{id}/evidences/ | Evidências anexadas |
| POST | /payments/{id}/evidences/ | Upload de evidência (multipart, máx 25 MB) |
| DELETE | /payments/{id}/evidences/{ev_id}/ | Remover evidência |
Export LGPD
| Método | Endpoint | Descrição |
|---|---|---|
| GET | /export/ | Histórico de exports |
| POST | /export/ | Solicitar export (format: json ou csv) |
| GET | /export/{id}/download/ | URL presigned de download |
| PUT | /webhooks/config/ | Configurar webhook (PIN) |
| GET | /onboarding/status/ | Status do onboarding |
Relatórios
| Método | Endpoint | Descrição |
|---|---|---|
| GET | /reports/overview/ | Dashboard overview |
| GET | /reports/revenue/?days=30 | Receita por período |
| GET | /reports/transactions/ | Transações com filtros |
| GET | /reports/churn/ | Churn de assinaturas |
| GET | /reports/anticipation/ | Relatório de antecipações |
Programa de Afiliados
| Método | Endpoint | Descrição |
|---|---|---|
| GET | /organization/affiliate-program/ | Config do programa da loja |
| PATCH | /organization/affiliate-program/ | Ligar/desligar + janela + comissão default |
| GET | /products/{id}/affiliate-config/ | Comissão configurada no produto |
| PUT | /products/{id}/affiliate-config/ | Atualizar comissão do produto |
| GET | /plans/{id}/affiliate-config/ | Comissão configurada no plano |
| PUT | /plans/{id}/affiliate-config/ | Atualizar comissão do plano + cap renovações |
| GET | /organization/affiliate-applications/ | Pedidos de afiliação (filtro ?status=pending) |
| POST | /affiliate-applications/{id}/moderate/ | Aprovar / rejeitar / revogar |
| GET | /products/{id}/affiliates/ | Afiliados ativos no produto |
| GET | /organization/affiliate-report/ | Relatório de comissões pagas |
| POST | /public/affiliate-track/ | Registrar clique (sem auth; checkout próprio) |
15. Códigos de Erro
| Status | Significado |
|---|---|
| 200 | Sucesso |
| 201 | Criado com sucesso |
| 400 | Dados inválidos (verifique o corpo da requisição) |
| 401 | API key inválida ou expirada |
| 403 | Sem permissão (org suspensa ou feature desabilitada) |
| 404 | Recurso não encontrado |
| 429 | Rate limit excedido (100 req/min padrão) |
| 502 | Erro no processador de pagamentos (provedor indisponível) |
Todos os erros retornam JSON:
{ "error": "Descrição do erro." }
16. Prazos de Recebimento
| Método | Prazo padrão | Com antecipação |
|---|---|---|
| Pix | D+1 | D+2 útil |
| Boleto | D+2 | D+2 útil |
| Crédito à vista | D+30 | D+2 útil |
| Crédito parcelado | 1 parcela a cada 30 dias | Todas em D+2 útil |
17. Limites e Rate Limiting
| Recurso | Limite |
|---|---|
| API key (geral) | 100 req/min |
| Checkout | 30 req/min |
| Webhooks | 500 req/min |
Header Retry-After é retornado quando o limite é excedido.
Suporte
- Dashboard: https://vitrin.digital/dashboard
- Swagger / API Docs: https://api.vitrin.digital/api/docs/
- Email: contato@vitrin.digital