Autenticação
Crie uma chave em /voren-api, copie e cole no sistema que vai se conectar. É só isso — não existe nenhuma outra configuração do lado da Voren. Envie a chave no header Authorization: Bearer.
curl https://appvoren.com.br/api/public/v1/ping \
-H "Authorization: Bearer voren_live_xxx"Chaves antigas no formato client_id + client_secret continuam funcionando com HTTP Basic, sem prazo de desativação.
OpenAPI, Postman e geração automática de cliente
A API é descrita em um arquivo legível por máquina. Qualquer linguagem, ERP, CRM, ferramenta no-code ou gerador de SDK consegue criar o cliente sozinho a partir dele — sem ninguém precisar ler esta página.
- Especificação OpenAPI 3.1:
https://appvoren.com.br/api/public/v1/openapi.json - Coleção Postman/Insomnia:
https://appvoren.com.br/api/public/v1/postman.json(importe, preenchavoren_api_keye teste)
# Cliente pronto em qualquer linguagem, a partir da especificação:
npx @openapitools/openapi-generator-cli generate \
-i https://appvoren.com.br/api/public/v1/openapi.json \
-g typescript-fetch -o ./voren-sdkValidar a chave e o status da API
Na tela onde o seu usuário cola a chave da Voren, chame GET /v1/me antes de salvar: a resposta confirma se a chave é válida, em qual ambiente ela está, quais permissões possui e qual o limite de requisições por minuto. GET /v1/health não exige chave e serve para monitoramento.
curl https://appvoren.com.br/api/public/v1/me \
-H "Authorization: Bearer voren_live_xxx"
# {"success":true,"data":{"valid":true,"environment":"live",
# "scopes":["sales.write","sales.read","events.read"],
# "rate_limit_per_min":300,"account":{"id":"...","name":"..."}}}
curl https://appvoren.com.br/api/public/v1/health
# {"status":"ok","api_version":"v1","components":{"api":"ok","database":"ok"}}Toda resposta traz X-Request-Id (use no suporte) e os cabeçalhos X-RateLimit-Limit, X-RateLimit-Remaining e X-RateLimit-Reset.
Eventos (polling)
A Voren mantém um histórico de eventos por conta. O seu sistema lê esse histórico quando quiser, usando um cursor incremental — nada precisa ser configurado aqui e nenhuma URL precisa ser exposta na internet.
# Primeira chamada (sem cursor)
curl "https://appvoren.com.br/api/public/v1/events?limit=50" \
-H "Authorization: Bearer voren_live_xxx"
# Resposta
{
"success": true,
"data": [
{ "id":"...", "type":"payment.paid", "resource_type":"payment",
"resource_id":"...", "created_at":"...", "data":{ } }
],
"has_more": true,
"next_cursor": "MS5hZTZjODAwZmYyNzIzMDY3"
}
# Próximas chamadas: reenvie o next_cursor recebido
curl "https://appvoren.com.br/api/public/v1/events?cursor=MS5hZTZjODAwZmYyNzIzMDY3&limit=50" \
-H "Authorization: Bearer voren_live_xxx"
# Espera curta por eventos novos (até ~20s) em vez de repetir a chamada
curl "https://appvoren.com.br/api/public/v1/events?wait=true" \
-H "Authorization: Bearer voren_live_xxx"
# Filtrar tipos
curl "https://appvoren.com.br/api/public/v1/events?types=payment.paid,payment.refunded" \
-H "Authorization: Bearer voren_live_xxx"Guarde sempre o último next_cursor: repetir a chamada com ele nunca perde nem duplica evento. O cursor é opaco e válido apenas para a sua conta. Tipos disponíveis: payment.created, payment.paid, payment.failed, payment.refunded, payment.canceled, payment.chargeback, order.shipped, order.delivered, subscription.created, subscription.charged, subscription.updated, subscription.canceled. O histórico mantém 90 dias.
Clientes e saldo
curl "https://appvoren.com.br/api/public/v1/customers?limit=50" -H "Authorization: Bearer voren_live_xxx"
curl "https://appvoren.com.br/api/public/v1/balance" -H "Authorization: Bearer voren_live_xxx"/customers exige o escopo customers.read; /balance e /events exigem sales.read.
Quickstart em 3 passos
- Ative seu recebedor Voren e crie uma chave em /voren-api.
- Opcional: crie um produto via API. Se os produtos já existem no seu sistema, pule esta etapa e cobre por valor.
- Gere uma cobrança e receba o resultado por webhook.
# 1) Pix sem produto cadastrado na Voren (catálogo no seu sistema)
curl -X POST https://appvoren.com.br/api/public/v1/charges \
-u "$KEY_ID:$KEY_SECRET" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: order-1001" \
-d '{
"amount_cents":9700,
"item_name":"Curso de Trading - Plano Anual",
"external_reference":"pedido-1001",
"payment_method":"pix",
"customer":{"name":"Ana Silva","email":"ana@example.com","document":"12345678909","phone":"11999998888"}
}'
# 2) Pix usando um produto do catálogo Voren
curl -X POST https://appvoren.com.br/api/public/v1/charges \
-u "$KEY_ID:$KEY_SECRET" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: order-1002" \
-d '{
"product_id":"<uuid>",
"payment_method":"pix",
"customer":{"name":"Ana Silva","email":"ana@example.com","document":"12345678909","phone":"11999998888"}
}'Obrigatório em Pix e Boleto: customer.phone com DDD (ex.: 11999998888). Sem telefone válido o provedor recusa a cobrança. No cartão o telefone é opcional.
Cartão de crédito: envie o endereço de cobrança em customer.address(line_1, zip_code, city, state, opcional line_2 e country). O antifraude só é aplicado quando o endereço é enviado — sem ele a taxa de aprovação cai bastante.
# Cartão com endereço de cobrança
curl -X POST https://appvoren.com.br/api/public/v1/charges \
-u "$KEY_ID:$KEY_SECRET" \
-H "Content-Type: application/json" \
-d '{
"amount_cents":9700,
"item_name":"Curso de Trading",
"payment_method":"credit_card",
"card_token":"token_xxx",
"installments":1,
"customer":{
"name":"Ana Silva","email":"ana@example.com","document":"12345678909","phone":"11999998888",
"address":{"line_1":"Rua das Flores, 123, Centro","zip_code":"01001000","city":"Sao Paulo","state":"SP","country":"BR"}
}
}'Escopos e ambientes
Cada chave tem escopos e ambiente (live ou test). Chame apenas o mínimo necessário.
products.read,products.writesales.read,sales.write(inclui cobranças e estornos)subscriptions.read,subscriptions.write,customers.readevents.read,balance.read,shipments.write,tokens.writewebhooks.manage
Aceitamos aliases comuns de outras plataformas: payments.write/charges.write/refunds.write valem como sales.write, e payments.read/charges.read valem como sales.read. Uma chave com sales.write também pode ler suas vendas e seu catálogo.
Compatibilidade garantida: chaves antigas com sales.read continuam lendo eventos e saldo sem precisar ser recriadas.
Tokens de cartão
/v1/tokensscope: sales.writePara montar o checkout dentro do seu próprio sistema, troque os dados do cartão por um card_token de uso único e envie esse token em POST /v1/charges. A Voren não armazena o número do cartão.
curl -X POST https://appvoren.com.br/api/public/v1/tokens \
-u "$KEY_ID:$KEY_SECRET" \
-H "Content-Type: application/json" \
-d '{
"card":{
"number":"4000000000000010",
"holder_name":"ANA SILVA",
"exp_month":12,
"exp_year":2030,
"cvv":"123"
}
}'
# resposta
# { "object":"card_token", "id":"token_xxx", "card":{ "last_four_digits":"0010", "brand":"visa" } }Idempotência
Envie um header Idempotency-Key em POST. Requisições repetidas com a mesma chave retornam o mesmo recurso e o header Idempotency-Replayed: true.
Rate limits
300 requisições por minuto por chave. Ao exceder, respondemos 429 com header Retry-After em segundos.
Formato de erro
Todos os erros usam JSON estável:
{
"error": "invalid_request",
"message": "Descrição legível do problema.",
"request_id": "req_ab12cd34...",
"details": [{ "path": "customer.email", "message": "Invalid email" }]
}Códigos comuns: unauthorized, invalid_credentials, insufficient_scope, invalid_request, not_found, invalid_state, rate_limited, provider_error, internal_error.
Products
/v1/productsscope: products.read/v1/productsscope: products.write/v1/products/{id}scope: products.read/v1/products/{id}scope: products.writecurl -X POST https://appvoren.com.br/api/public/v1/products \
-u "$KEY_ID:$KEY_SECRET" \
-H "Content-Type: application/json" \
-d '{
"name":"Mentoria Voren",
"description":"3 meses de acompanhamento",
"price_cents":149700,
"status":"active",
"delivery_methods":["email"],
"support_email":"suporte@example.com"
}'Charges (cobranças)
/v1/chargesscope: sales.write/v1/chargesscope: sales.read/v1/charges/{id}scope: sales.readExistem dois modos, e você usa exatamente um deles por cobrança:
- Catálogo Voren — envie
product_id. Preço, oferta, cupom e order bumps vêm do produto cadastrado. - Item externo — envie
amount_cents+item_name. Não é preciso criar nenhum produto na Voren; o catálogo continua no seu sistema. Useexternal_referencepara amarrar a cobrança ao seu pedido (também dá para filtrar por ele emGET /v1/charges?external_reference=...).
No modo item externo, offer_id, coupon_code e bump_ids não se aplicam (dependem do catálogo Voren). A divisão (split), as taxas e a liquidação funcionam igual nos dois modos, e o valor final é sempre validado no servidor. Métodos: pix, boleto, credit_card.
# Item externo (sem produto na Voren)
curl -X POST https://appvoren.com.br/api/public/v1/charges \
-u "$KEY_ID:$KEY_SECRET" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: cli-2025-0001" \
-d '{
"amount_cents":149700,
"item_name":"Mentoria - Turma 12",
"external_reference":"pedido-8842",
"payment_method":"pix",
"customer":{"name":"Ana","email":"ana@x.com","document":"12345678909","phone":"11999998888"},
"metadata":{"origin":"checkout-proprio"}
}'
# Catálogo Voren
curl -X POST https://appvoren.com.br/api/public/v1/charges \
-u "$KEY_ID:$KEY_SECRET" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: cli-2025-0002" \
-d '{
"product_id":"<uuid>",
"offer_id":"<uuid>",
"payment_method":"credit_card",
"card_token":"card_token_xxx",
"installments":3,
"customer":{"name":"Ana","email":"ana@x.com","document":"12345678909","phone":"11999998888"},
"coupon_code":"BEMVINDO10",
"ref_code":"abc123",
"metadata":{"origin":"landing-a"}
}'Parcelamento no cartão: os juros são de responsabilidade do cliente final e somados automaticamente ao valor cobrado. A resposta traz amount_cents (valor do item),interest_cents (juros do parcelamento) e charged_amount_cents (total efetivamente cobrado no cartão). O repasse ao produtor é sempre calculado sobre amount_cents.
Refunds (estornos)
/v1/refundsscope: sales.writeSuporta estorno total ou parcial. Envie sale_id (ou charge_id) e opcionalmente amount_cents para parcial.
# Estorno total
curl -X POST https://appvoren.com.br/api/public/v1/refunds \
-u "$KEY_ID:$KEY_SECRET" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: refund-2025-0001" \
-d '{"sale_id":"<uuid>","reason":"solicitação do cliente"}'
# Estorno parcial (R$ 30,00)
curl -X POST https://appvoren.com.br/api/public/v1/refunds \
-u "$KEY_ID:$KEY_SECRET" \
-H "Content-Type: application/json" \
-d '{"sale_id":"<uuid>","amount_cents":3000}'Sales
/v1/salesscope: sales.read/v1/sales/{id}scope: sales.readFiltros: status, from, to, limit (máx 100), cursor.
Subscriptions (assinaturas recorrentes)
/v1/subscriptionsscope: subscriptions.write/v1/subscriptionsscope: subscriptions.read/v1/subscriptions/{id}scope: subscriptions.read/v1/subscriptions/{id}scope: subscriptions.write/v1/subscriptions/{id}scope: subscriptions.writeAssinaturas são cobradas sempre em cartão de crédito: gere o card_token em POST /v1/tokens antes de criar. Funciona em dois modos, igual a Charges: com product_id (item do catálogo Voren) ou avulso com amount_cents + item_name, sem nada cadastrado na Voren.customer.phone com DDD é obrigatório.
# Assinatura mensal avulsa (R$ 49,90/mês)
curl -X POST https://appvoren.com.br/api/public/v1/subscriptions \
-u "$KEY_ID:$KEY_SECRET" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: sub-2026-0001" \
-d '{
"amount_cents": 4990,
"item_name": "Plano Pro",
"external_reference": "plan_pro_mensal",
"interval": "month",
"interval_count": 1,
"card_token": "<token de /v1/tokens>",
"customer": {
"name": "Maria Silva",
"email": "maria@exemplo.com",
"document": "12345678909",
"phone": "11999998888"
}
}'
# Consultar
curl https://appvoren.com.br/api/public/v1/subscriptions/<id> -u "$KEY_ID:$KEY_SECRET"
# Cancelar (idempotente)
curl -X DELETE https://appvoren.com.br/api/public/v1/subscriptions/<id> -u "$KEY_ID:$KEY_SECRET"
# Alterar o valor de uma assinatura ativa (recria o split automaticamente)
curl -X PATCH https://appvoren.com.br/api/public/v1/subscriptions/<id> \
-u "$KEY_ID:$KEY_SECRET" \
-H "Content-Type: application/json" \
-d '{"price_cents": 6990}'PATCH /v1/subscriptions/{id} altera o valor da recorrência usando o cartão já salvo do cliente: a assinatura é recriada no gateway com o split e as taxas vigentes e a resposta traz recreated e previous_subscription_id. Nada é perdido — o registro antigo continua consultável com o vínculo para o novo, e o histórico de cobranças permanece intacto. Se o cartão salvo não estiver disponível, a resposta traz manual_required: true e nossa equipe é alertada automaticamente.
Cobranças seguintes são geradas automaticamente e confirmadas pela Voren em tempo real — acompanhe pelos webhooks subscription.charged e subscription.canceled. Filtros da listagem: status, external_reference, limit (máx 100), cursor.
Webhooks
/v1/webhooksscope: webhooks.manage/v1/webhooksscope: webhooks.manage/v1/webhooks/{id}scope: webhooks.manage/v1/webhooks/{id}scope: webhooks.manage/v1/webhooks/{id}/testscope: webhooks.manageEventos: sale.paid, sale.failed, sale.refunded, sale.chargeback, sale.canceled, subscription.created, subscription.charged, subscription.canceled, subscription.updated, test.ping. Use * para assinar todos.
Verificar assinatura de webhook
Cada entrega inclui o header X-Voren-Signature: t=<timestamp>,v1=<sha256_hex>. Calcule HMAC-SHA256 sobre ${timestamp}.${raw_body} com seu secret e compare em tempo constante. Rejeite entregas com timestamp com mais de 5 minutos.
// Node.js
import { createHmac, timingSafeEqual } from "node:crypto";
function verifyVorenSignature(rawBody, header, secret) {
const parts = Object.fromEntries(header.split(",").map(p => p.split("=")));
const timestamp = parts.t;
const v1 = parts.v1;
if (!timestamp || !v1) return false;
if (Math.abs(Date.now()/1000 - Number(timestamp)) > 300) return false;
const expected = createHmac("sha256", secret).update(`${timestamp}.${rawBody}`).digest("hex");
const a = Buffer.from(v1, "hex");
const b = Buffer.from(expected, "hex");
return a.length === b.length && timingSafeEqual(a, b);
}