# Throne API: documentação completa Atualizado em: 2026-11-03 Versão da API (OpenAPI info.version): 1.13.0 Índice curto: https://app.thronecorporate.com/llms.txt --- # Documentação da API > Mapa da API do Throne: Pix, cartão em várias moedas, internacional, saldo, saques e webhooks. Fonte: https://app.thronecorporate.com/docs ## O que dá para fazer com a API | Você quer | Use | Guia | |---|---|---| | Receber por Pix (QR Code e copia e cola) | `POST /transactions` com `payment_method: "pix"` | [Cobrar com Pix](/docs/pix) | | Receber cartão dentro da sua página, na moeda do comprador | `POST /elements/checkout` + Throne Elements | [Cartão: como funciona](/docs/cartao) | | Receber cartão do comprador brasileiro, com a página em português, o CPF pedido na tela e o botão e a moldura ajustados pela API | `POST /elements/checkout/br` + Throne Elements | [Checkout Brasil](/docs/cartao/brasil) | | Cobrar um segundo produto sem pedir o cartão de novo | `POST /elements/checkout` com `one_click_from` | [Upsell em um clique](/docs/cartao/oneclick) | | Cobrar no cartão a cada ciclo, sozinho | `POST /elements/checkout` com `recurrence` | [Recorrência](/docs/cartao/recorrencias) | | Receber pagamento internacional por botão embutido | `POST /global/checkout` + iframe | [Internacional (Paycore)](/docs/internacional) | | Ver o saldo da conta | `GET /balance` | [Saldo e saques](/docs/saques) | | Sacar por Pix ou cripto | `POST /withdrawals` | [Saldo e saques](/docs/saques) | | Saber na hora que uma venda foi paga | Webhook `transaction.paid` | [Webhooks](/docs/webhooks) | URL base de todas as chamadas: ```text https://app.thronecorporate.com/api/v1 ``` ## Por onde começar 1. Gere uma chave de API no painel, em [Chaves de API](https://app.thronecorporate.com/painel/api-keys). 2. Leia [Comece aqui](/docs/comecar): autenticação, `Idempotency-Key`, tempo de resposta e formato dos erros. São cinco minutos e evitam a maior parte dos problemas. 3. Veja [Limites e tempos](/docs/limites) e [Como testar](/docs/testar). Não existe ambiente de testes separado. 4. Siga o guia do meio de pagamento que você vai usar. 5. Cadastre a URL do seu webhook no painel e valide a assinatura, como em [Webhooks](/docs/webhooks). 6. Antes de abrir as vendas, passe pelo [Checklist de verificação](/docs/checklist). ## Cartão: qual guia é o seu O cartão é sempre o mesmo Throne Elements, criado pelo `POST /elements/checkout` ou, para o comprador brasileiro, pelo [Checkout Brasil](/docs/cartao/brasil). No checkout comum, o que muda é o comportamento do checkout, conforme a adquirente. Com mais de uma adquirente habilitada na conta, cada site escolhe a dela pelo campo `acquirer` ([como escolher](/docs/cartao/escolher)). As habilitadas aparecem no painel, em **Documentação da API**, e em `GET /elements/acquirers`. | Adquirente | Verificação do banco | Navegador de app (Instagram, Facebook) | Tentativas por checkout | Guia | |---|---|---|---|---| | Sterling | Não tem: aprova ou recusa na hora | Tudo dentro do elemento | 5 | [Sterling](/docs/cartao/sterling) | | Imperium | Não tem: o resultado sai na própria tentativa (uma recusa pode levar até 25 segundos) | Tudo dentro do elemento | 5 | [Imperium](/docs/cartao/imperium) | | NovaCorp | Abre uma janela de verificação; a tela do banco aparece nela quando ele pede | A própria aba vai para a página segura de pagamento, sem evento na sua página | 6 | [NovaCorp](/docs/cartao/novacorp) | | Eminence | Pode pedir; a tela do banco aparece dentro do próprio elemento | Tudo dentro do elemento | 5 | [Eminence](/docs/cartao/eminence) | | Apex | Não tem: aprova ou recusa na hora | Tudo dentro do elemento | 5 | [Apex](/docs/cartao/apex) | | ShadowBank | Não tem: aprova ou recusa na hora | Tudo dentro do elemento | 5 | [ShadowBank](/docs/cartao/shadowbank) | | Vortex | Não tem: aprova ou recusa na hora | Tudo dentro do elemento | 5 | [Vortex](/docs/cartao/vortex) | A validade do checkout é de 24 horas nas seis. O valor máximo por venda depende da adquirente e varia com a moeda e com a cotação do dia. A comparação completa está em [Comparar adquirentes](/docs/cartao/comparar). Nas seis, você manda o valor na moeda do comprador e ele paga nessa moeda; o seu saldo é creditado em reais, pela cotação fixada na criação. Veja [Moedas e limites](/docs/cartao/moeda). Duas formas de cobrar mais de uma vez entram pelo mesmo endpoint, como campos opcionais, e hoje só a Apex as aceita: [Upsell em um clique](/docs/cartao/oneclick), para cobrar um segundo produto sem pedir o cartão de novo, e [Recorrência](/docs/cartao/recorrencias), para a cobrança que se repete sozinha. ## Para assistentes de código e ferramentas automáticas Toda a documentação existe também em texto puro, para quem integra com ajuda de um assistente de código: - [`/llms.txt`](/llms.txt): índice curto, com um link por guia e a data da última atualização. - [`/docs/llms-full.txt`](/docs/llms-full.txt): todos os guias num arquivo só. - Qualquer guia em Markdown: acrescente `.md` ao endereço (por exemplo, [`/docs/pix.md`](/docs/pix.md)). - [`/openapi.json`](/openapi.json) e [`/openapi.yaml`](/openapi.yaml): especificação OpenAPI 3.1 com todos os campos e respostas. - [Changelog](/docs/changelog): o que mudou, com data. --- # Comece aqui > Chaves de API, URL base, Idempotency-Key com tabela única de repetição, tempo de resposta e formato das respostas. Fonte: https://app.thronecorporate.com/docs/comecar ## URL base Todas as chamadas usam HTTPS e esta base: ```text https://app.thronecorporate.com/api/v1 ``` O endereço `https://api.thronecorporate.com/api/v1` responde igual. O caminho sempre começa com `/api/v1`: sem o `/api`, a resposta é 404. ## Autenticação Toda chamada leva o header `Authorization` com a chave pública e a secreta separadas por dois-pontos: ```http Authorization: Bearer pk_live_xxxxxxxx:sk_live_xxxxxxxx ``` Envie exatamente assim, em texto. Não codifique em base64. Gere as chaves no painel, em [Chaves de API](https://app.thronecorporate.com/painel/api-keys). A chave secreta aparece uma vez só, na criação. Guarde no seu servidor e nunca coloque no navegador, no aplicativo ou num repositório de código. Teste a chave com uma chamada simples: ```bash cURL curl https://app.thronecorporate.com/api/v1/balance \ -H "Authorization: Bearer $THRONE_PK:$THRONE_SK" \ -H "Accept: application/json" ``` ```php PHP $ch = curl_init('https://app.thronecorporate.com/api/v1/balance'); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 20, CURLOPT_HTTPHEADER => [ 'Authorization: Bearer ' . getenv('THRONE_PK') . ':' . getenv('THRONE_SK'), 'Accept: application/json', ], ]); $saldo = json_decode(curl_exec($ch), true); ``` ```js Node.js const res = await fetch('https://app.thronecorporate.com/api/v1/balance', { headers: { Authorization: `Bearer ${process.env.THRONE_PK}:${process.env.THRONE_SK}`, Accept: 'application/json', }, signal: AbortSignal.timeout(20000), }); const saldo = await res.json(); ``` ```python Python import os, requests r = requests.get( "https://app.thronecorporate.com/api/v1/balance", headers={ "Authorization": f"Bearer {os.environ['THRONE_PK']}:{os.environ['THRONE_SK']}", "Accept": "application/json", }, timeout=20, ) saldo = r.json() ``` Se a autenticação falhar, a resposta é sempre `401` com `{"error": "unauthorized", "detail": "..."}`. O campo `detail` diz o motivo: | `detail` | O que aconteceu | |---|---| | `credentials_required` | O header `Authorization` não veio ou não começa com `Bearer ` (por exemplo, veio como `Basic ...`). | | `malformed_credentials` | O valor depois de `Bearer` não está no formato `pk:sk` (por exemplo, veio em base64). | | `invalid_credentials` | A chave não existe, foi revogada ou a conta está bloqueada. | | `ip_not_allowed` | A chave tem lista de IPs e o IP da chamada não está nela. | ## Permissões da chave Cada chave tem permissões (escopos). Sem a permissão certa, a resposta é `403` com `error: "scope_required"` e o campo `required_scope`. | Permissão | Libera | Vem ligada numa chave nova | |---|---|---| | `transactions:write` | Criar cobranças Pix e checkouts de cartão | Sim | | `transactions:read` | Consultar transações | Sim | | `balance:read` | Consultar saldo | Sim | | `withdrawals:read` | Consultar saques | Sim | | `withdrawals:write` | Pedir saque | Não. Ligue no painel e cadastre os IPs de saque (veja abaixo) | | `payment_links:write` e `payment_links:read` | Criar e consultar links de pagamento | Não. Ligue no painel | ### As duas listas de IP da chave | Lista | Vale para | Formato | Sem o IP na lista | |---|---|---|---| | **IPs da chave** | Todas as chamadas da chave | IP exato | `401 ip_not_allowed` | | **IPs de saque** | Só `POST /withdrawals` | IP exato ou faixa CIDR | `403 withdrawal_ip_not_authorized` | A lista de IPs da chave é opcional, mas uma chave de produção com a permissão de saque exige as duas listas. Por isso, use **uma chave só para saques**, rodando de um servidor com IP fixo, e outra para cobranças. ## Não existe ambiente de testes separado > [!WARNING] > Hoje as chaves `pk_test_` funcionam como as de produção. Um checkout de cartão cobra o cartão de verdade e um saque transfere dinheiro de verdade. Teste com valores baixos e com o seu próprio cartão ou chave Pix. O roteiro completo, com os erros que dá para provocar sem custo, está em [Como testar](/docs/testar). ## Idempotency-Key Toda chamada que cria algo exige o header `Idempotency-Key`: `POST /transactions`, `POST /elements/checkout`, `POST /global/checkout`, `POST /withdrawals` e `POST /payment-links`. Sem ele, a resposta é `400 idempotency_key_required`. ```http Idempotency-Key: 6f1c2b8e-3a4d-4e5f-9a7b-1c2d3e4f5a6b ``` Regras: - De 8 a 128 caracteres; fora disso, `400 idempotency_key_required`. Use um UUID v4 novo para cada cobrança, checkout ou saque. Não use número de pedido nem sequência, que são curtos e previsíveis. - A `Idempotency-Key` vale para a sua conta inteira por 24 horas, em todos os endpoints. - Gere a chave **antes** de chamar e guarde junto do seu pedido. É ela que permite repetir sem duplicar. - Mesma `Idempotency-Key` com corpo diferente: `422 idempotency_key_mismatch`, enquanto a chave valer. A ordem dos campos no JSON não importa. ### Como repetir uma chamada com segurança Esta é a regra única de repetição da API. Ela vale para todo `POST` que exige `Idempotency-Key` e mostra o que acontece quando você repete a chamada com a **mesma** chave e o **mesmo** corpo. | Resposta da primeira chamada | Repetir com a mesma chave e o mesmo corpo devolve | Pode usar uma chave nova? | |---|---|---| | `2xx` (criou) | A resposta original, com o header `Idempotent-Replay: true`. Nada é criado de novo | **Não.** Uma chave nova cria outra cobrança, outro checkout ou outro saque | | `4xx` (erro de campo ou de regra, inclusive `403`, `422` e `429`) | `409 idempotency_key_race` | Sim. Nada foi criado. Corrija o que for preciso e repita com uma chave nova | | `502` ou `503`, com o JSON da API (campo `error`), sem `retry_same_key` | `409 idempotency_key_processing`, por até 24 horas. Em `POST /elements/checkout` e no Pix de `POST /transactions`, a chamada roda de novo, como se fosse a primeira | Sim. Nada foi criado. No cartão, siga o campo `retryable` e o header `Retry-After` da resposta | | `502 acquirer_error` com `"retry_same_key": true` (Pix de `POST /transactions`) | A chamada roda de novo e leva à adquirente a mesma chave da primeira tentativa. Com outro corpo, `422 idempotency_key_mismatch` | **Não.** A cobrança pode ter sido criada, e uma chave nova pode gerar a segunda. Repita com a mesma chave e o mesmo corpo | | `500` (erro inesperado) | `409 idempotency_key_processing`, por até 24 horas. Em `POST /elements/checkout`, a chamada roda de novo quando nada foi criado | **Só depois de conferir no painel se algo foi criado.** Em `POST /elements/checkout`, repita antes a mesma chamada, igual: se ela rodar de novo, nada tinha sido criado; se vier `409 idempotency_key_processing` por mais de 2 minutos, a venda pode ter sido criada | | Ainda em andamento | `409 idempotency_key_processing` | **Não.** Espere alguns segundos e repita igual | | Nenhuma (falha de conexão, timeout do seu lado ou resposta sem o JSON da API, como uma página de erro `502` ou `504` do servidor) | Uma das linhas acima, conforme o que aconteceu no servidor | **Não.** Repita com a mesma chave até ter uma resposta. Veja o caso abaixo | As respostas `400 idempotency_key_required` e `401 unauthorized` saem antes da chave ser registrada: depois de corrigir, a mesma chave pode ser usada. **Timeout do seu lado.** Repita com a mesma chave e o mesmo corpo. Se a primeira chamada já terminou, você recebe a resposta dela. Se a resposta for `409 idempotency_key_processing`, espere alguns segundos e repita de novo. Se o `409 idempotency_key_processing` continuar por mais de 2 minutos, a primeira chamada terminou com erro de servidor: confira no painel se algo foi criado antes de seguir com uma chave nova. **`409 idempotency_key_in_use`.** Essa `Idempotency-Key` não pode ser usada pela sua conta. Gere outra. ## Tempo de resposta e timeouts Configure um timeout no seu cliente HTTP. Ele precisa ser maior que o tempo que a API leva para responder, senão você desiste de uma chamada que deu certo. | Chamada | Timeout recomendado | |---|---| | `POST /elements/checkout` e `POST /elements/checkout/br` (cartão) | 60 segundos | | Todas as outras | 20 segundos | A criação do checkout de cartão fala com a adquirente e costuma responder em poucos segundos. Em horário de pico ela pode demorar mais, por isso o valor maior. Se mesmo assim o timeout estourar, repita com a **mesma** `Idempotency-Key`, como na tabela acima. Os limites e o que fazer com `429` e `503` estão em [Limites e tempos](/docs/limites). ## Limites de requisição | O quê | Limite | |---|---| | Chamadas com chave válida | Sem limite geral | | Criação de cobrança (`POST /transactions`) | 10 por segundo por conta | | Criação de checkout de cartão (`POST /elements/checkout`) | Sem limite por conta. Quando a adquirente está sem capacidade, a resposta é `503 temporarily_unavailable` com `Retry-After` | | Pedido de saque (`POST /withdrawals`) | 1 por minuto por conta | | Chamadas sem credencial válida | 10 por segundo por IP | Passou do limite, a resposta é `429`. Espere o tempo do header `Retry-After` (em segundos) e tente de novo com uma `Idempotency-Key` nova. Ignore os headers `X-RateLimit-*` das respostas: eles não refletem os limites acima. ## Formato das respostas - Corpo sempre em JSON, com `Content-Type: application/json`. Envie também `Accept: application/json` em toda chamada. - Valores em **centavos inteiros** (`amount_cents`): R$ 49,90 é `4990`. No cartão, na menor unidade da moeda enviada. - Datas em ISO 8601. As da cobrança, do checkout e do saque vêm em UTC (`2026-10-03T14:20:00Z`); `payment_data.expires_at` do Pix e as datas dos links de pagamento vêm com fuso (por exemplo, `-03:00`). Use um leitor de ISO 8601 que aceite os dois formatos. - IDs de transação e de saque são UUID em letras minúsculas. Use o id exatamente como veio. - Campos novos podem aparecer nas respostas e nos webhooks, sempre como acréscimo. Ignore os campos que você não conhece. As mudanças ficam no [Changelog](/docs/changelog). ## Formato dos erros Erro de campo (validação) volta `422` com a lista por campo: ```json { "message": "O campo amount cents é obrigatório.", "errors": { "amount_cents": ["O campo amount cents é obrigatório."] } } ``` Erro de regra volta com um código fixo em `error` e um texto em `message`: ```json { "error": "invalid_request", "message": "Valor mínimo de cobrança: R$ 1,00." } ``` Alguns erros trazem campos extras para você decidir sem ler o texto. Por exemplo, uma indisponibilidade temporária no cartão: ```json { "error": "temporarily_unavailable", "message": "A adquirente está temporariamente indisponível. Tente em instantes.", "retryable": true } ``` Trate os erros pelo campo `error` (fixo) e mostre o `message` só como apoio, porque o texto pode mudar. A lista completa, com o que fazer em cada caso, está em [Erros](/docs/errors). ## Próximo passo - Vai receber por Pix: [Cobrar com Pix](/docs/pix). - Vai receber cartão: [Cartão: como funciona](/docs/cartao). - Precisa saber quando a venda foi paga: [Webhooks](/docs/webhooks). - Antes de vender: [Checklist de verificação](/docs/checklist). --- # Limites e tempos > Limites por endpoint e por adquirente, tempo de resposta, Retry-After e o que fazer em cada caso. Fonte: https://app.thronecorporate.com/docs/limites ## Resumo | O quê | Limite | Passou do limite | |---|---|---| | Chamadas com chave válida | Sem limite geral | Não se aplica | | `POST /transactions` (Pix) | 10 por segundo por conta | `429 rate_limit_exceeded`, com `Retry-After` | | `POST /elements/checkout` (cartão) | Sem limite por conta. Cada adquirente tem uma capacidade de criação, dividida entre todas as contas | `503 temporarily_unavailable`, com `Retry-After` | | `POST /withdrawals` | 1 por minuto por conta, mais o limite diário da conta | `429 rate_limited` ou `429 daily_limit_exceeded` | | Chamadas sem credencial válida | 10 por segundo por IP | `429`, com `Retry-After` | | Valor de uma venda no cartão | Mínimo e máximo por adquirente, que variam com a moeda e com a cotação do dia | `422 invalid_amount`, com o limite na moeda que você mandou | | Validade do checkout de cartão | 24 horas | A venda vira `expired` | | Validade da `Idempotency-Key` | 24 horas, na conta inteira | Não se aplica | ## Tempo de resposta | Chamada | Timeout recomendado no seu cliente HTTP | |---|---| | `POST /elements/checkout` e `POST /elements/checkout/br` | 60 segundos | | Todas as outras | 20 segundos | A criação do checkout de cartão fala com a adquirente. Na maior parte das vezes a resposta sai em poucos segundos, mas em pico pode passar de 40 segundos. Não corte antes de 60 segundos: se você desistir de uma chamada que deu certo, perde um checkout criado. Se o timeout estourar mesmo assim, repita com a **mesma** `Idempotency-Key` e o mesmo corpo. A regra completa está em [como repetir com segurança](/docs/comecar#como-repetir-uma-chamada-com-seguranca). ## Quando a resposta é 429 ou 503 `429` e `503` dizem que nada foi criado e que vale tentar de novo daqui a pouco. 1. Leia o header `Retry-After` (em segundos) e espere pelo menos esse tempo. 2. Repita com uma `Idempotency-Key` nova. 3. Se vier de novo, aumente a espera a cada tentativa (recuo exponencial) e some uma variação aleatória. Assim várias repetições não chegam todas no mesmo segundo. 4. Pare depois de poucas tentativas. Mostre ao comprador um "tente em instantes" ou ofereça outro meio de pagamento. No cartão, o `503` traz também `"retryable": true`. Ele aparece quando a adquirente está sem capacidade, instável ou em pausa. Com mais de uma adquirente habilitada, você pode criar o checkout na outra, com o campo `acquirer` ([como escolher](/docs/cartao/escolher)). Exemplo em Node.js, com timeout, repetição segura e recuo: ```js Node.js import { randomUUID } from 'node:crypto'; const esperar = (s) => new Promise((r) => setTimeout(r, s * 1000)); async function criarCheckout(corpo, tentativas = 4) { let chave = randomUUID(); // guarde junto do pedido for (let i = 0; i < tentativas; i++) { let res; try { res = await fetch('https://app.thronecorporate.com/api/v1/elements/checkout', { method: 'POST', headers: { Authorization: `Bearer ${process.env.THRONE_PK}:${process.env.THRONE_SK}`, 'Content-Type': 'application/json', Accept: 'application/json', 'Idempotency-Key': chave, }, body: JSON.stringify(corpo), signal: AbortSignal.timeout(60000), }); } catch { continue; // sem resposta (conexão caiu ou timeout): repete com a MESMA chave } if (res.status === 409) { const erro = await res.json().catch(() => ({})); if (erro.error === 'idempotency_key_processing') { await esperar(3); // a primeira chamada ainda está em andamento continue; } return res; } if (res.status !== 429 && res.status !== 503) { return res; // 201 ou um erro que não se resolve repetindo } // 429 ou 503: nada foi criado. Espera e repete com chave nova. const retryAfter = Number(res.headers.get('Retry-After')) || 1; await esperar(Math.max(retryAfter, Math.min(60, 2 ** i)) * (1 + Math.random() / 2)); chave = randomUUID(); } return null; // mostre "tente em instantes" ou outro meio de pagamento } ``` ## Mínimo e máximo por venda no cartão O mínimo e o máximo de uma venda dependem da adquirente e variam com a moeda e com a cotação do dia. Um preço perto do máximo pode passar num dia e não passar no outro. - **Consultar antes**: `GET /elements/acquirers?currency=chf` mostra o mínimo e o máximo atuais de cada adquirente, já na moeda pedida. Os valores são indicativos e acompanham a cotação. - **Acima do máximo**: a resposta é `422 invalid_amount`, com `max_amount_cents` na moeda que você mandou e `alternatives` com as outras adquirentes habilitadas que aceitam esse valor. Nada foi criado. Não repita o mesmo valor na mesma adquirente: divida o pedido ou crie com `acquirer` de uma das `alternatives`. - **Abaixo do mínimo**: a resposta é `422 invalid_amount`, com o mínimo na sua moeda no `message` e no campo `min_amount_cents`. Exemplo de resposta acima do máximo: ```json { "error": "invalid_amount", "message": "Valor acima do máximo aceito nesta adquirente: CHF 318,90.", "max_amount_cents": 31890, "currency": "CHF", "alternatives": ["sterling"] } ``` As moedas aceitas e o efeito da cotação no máximo estão em [Moedas e limites](/docs/cartao/moeda). ## Capacidade de criação no cartão O Throne não limita quantos checkouts a sua conta cria. Cada adquirente, porém, tem uma capacidade de criação dividida entre todas as contas que usam ela. Num pico grande, ou quando a adquirente fica instável, a criação responde `503 temporarily_unavailable` com `Retry-After`, sem criar nada. Para gastar menos capacidade e vender mais: - Crie o checkout quando o comprador clicar em comprar, não quando a página carrega. - Guarde o `transaction_id` na sessão do comprador e reaproveite o mesmo checkout por até 24 horas, se ele voltar à página. - Não crie um checkout novo a cada recarga da página nem a cada tentativa de cartão: o mesmo checkout aceita várias tentativas. ## Limites por comprador na página de pagamento A página de pagamento tem limites de uso por comprador, contra abuso e teste de cartão. Uma compra normal não chega perto deles. O que costuma esbarrar nos limites: - recarregar o elemento em laço; - abrir o mesmo checkout em muitas abas ao mesmo tempo; - tentar dezenas de cartões no mesmo checkout em poucos minutos. ## Webhooks - Responda `2xx` em até 3 segundos. - Sem `2xx`, o Throne tenta de novo, até 10 tentativas no total, com intervalos crescentes. Detalhes em [Webhooks](/docs/webhooks). --- # Como testar > Testar sem ambiente de testes: valor baixo, idioma do checkout e erros provocados de propósito. Fonte: https://app.thronecorporate.com/docs/testar ## Não existe ambiente de testes separado > [!WARNING] > As chaves `pk_test_` funcionam como as de produção. Um checkout de cartão pago cobra o cartão de verdade, um Pix pago cai na sua conta e um saque transfere dinheiro de verdade. Criar uma cobrança ou um checkout não cobra nada. Só o pagamento cobra. Um checkout que ninguém paga vira `expired` em 24 horas, sem custo. ## Pagar com valor baixo - **Pix**: a partir de R$ 1,00, pago com a sua própria conta. - **Cartão**: um valor pouco acima do mínimo, com o seu próprio cartão. O mínimo varia com a moeda e com a cotação do dia; algumas adquirentes têm mínimo maior. Na dúvida, mande menos de propósito: a resposta diz o mínimo na sua moeda. - **Estorno do teste**: não é feito pela API. Fale com o suporte do Throne. Não faça testes de recusa em série, com vários cartões inventados no mesmo checkout. Isso parece teste de cartão e pode bloquear as tentativas daquele comprador. ## Ver o checkout em outro idioma O checkout sai no idioma do país do comprador, identificado pelo IP. Na NovaCorp, na Eminence, na Apex, na ShadowBank e na Vortex, para ver como um comprador de outro país enxerga a página, abra o `checkout_url` da resposta com `?nl=` e o código do idioma: ```text ?nl=de ``` Se o endereço já tiver `?`, use `&nl=de`. O formulário, os números e as telas de espera saem nesse idioma; a tela do próprio banco, quando ele pede verificação, segue o banco. Códigos aceitos: `en`, `pt`, `es`, `fr`, `de`, `it`, `nl`, `pl`, `sv`, `da`, `no`, `fi`, `cs`, `sk`, `ro`, `hu`, `el`, `tr`, `ru`, `uk`, `bg`, `hr`, `ja`, `ko`, `zh`, `ar`, `he`, `th`, `id` e `vi`. Na Sterling e na Imperium o idioma segue só o IP. Para conferir, use uma conexão do país do comprador. No Checkout Brasil, a página sai sempre em português, e o `?nl=` não muda isso. ## Ver o Checkout Brasil sem pagar Abra no navegador o `checkout_url` da resposta do `POST /elements/checkout/br` para ver a página como o comprador vê: em português, com o campo CPF e com o botão e a moldura que você pediu em `appearance`. Nada é cobrado enquanto ninguém clica em pagar. ## Provocar erros de propósito Todos os casos abaixo voltam sem criar nada e sem cobrar. Use para conferir se a sua integração mostra a mensagem certa e não fica repetindo a chamada. | Para ver | Envie em `POST /elements/checkout` | Resposta | |---|---|---| | Valor abaixo do mínimo | `"amount_cents": 50, "currency": "usd"` | `422 invalid_amount`, com o mínimo na sua moeda | | Valor acima do máximo | Um valor acima do `max_amount_cents` que `GET /elements/acquirers?currency=...` mostra, com o `acquirer` dessa adquirente | `422 invalid_amount`, com `max_amount_cents` e `alternatives` | | Moeda fora da lista | `"currency": "xyz"` | `422 currency_not_supported` | | Adquirente desconhecida | `"acquirer": "teste"` | `422 invalid_acquirer` | | Adquirente não habilitada | `acquirer` com uma adquirente que não aparece em `GET /elements/acquirers` | `422 acquirer_not_enabled` | | Nome ou e-mail do comprador faltando (ShadowBank) | `"acquirer": "shadowbank"` sem `customer`, com a ShadowBank habilitada na conta | `422 customer_required_for_shadowbank` | | Nome ou e-mail do comprador faltando (Vortex) | `"acquirer": "vortex"` sem `customer`, com a Vortex habilitada na conta | `422 customer_required_for_vortex` | | Falta da chave de idempotência | A chamada sem o header `Idempotency-Key` | `400 idempotency_key_required` | | Chave de API errada | `Authorization: Bearer pk_x:sk_x` | `401 unauthorized` | | Corpo trocado | A mesma `Idempotency-Key` de uma chamada anterior, com outro corpo | `422 idempotency_key_mismatch` | No `POST /elements/checkout/br`, também sem custo: | Para ver | Envie | Resposta | |---|---|---| | Campo que não existe no Checkout Brasil | `"one_click_from": "a1b2c3d4-e5f6-4789-abcd-ef0123456789"` | `422 field_not_supported` | | Aparência fora da regra | `"appearance": { "button": { "text": "Pague R$ 9,90" } }` | `422 invalid_appearance`, com `field` | E um caso que dá certo, para ver a repetição segura: mande a mesma criação duas vezes, com a mesma `Idempotency-Key` e o mesmo corpo. A segunda resposta é igual à primeira e traz o header `Idempotent-Replay: true`. Só um checkout existe. Exemplo do valor abaixo do mínimo: ```bash curl -X POST https://app.thronecorporate.com/api/v1/elements/checkout \ -H "Authorization: Bearer $THRONE_PK:$THRONE_SK" \ -H "Content-Type: application/json" \ -H "Accept: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "amount_cents": 50, "currency": "usd" }' ``` ```json { "error": "invalid_amount", "message": "Valor abaixo do mínimo aceito: $ 0,95." } ``` O valor do mínimo muda com a cotação do dia. ## Testar o webhook - No painel, em [Webhooks](https://app.thronecorporate.com/painel/webhook-settings), o botão de teste envia um `test.ping` assinado, igual aos eventos de verdade. - O histórico fica em [Entregas de webhook](https://app.thronecorporate.com/painel/webhook-deliveries), com o status que o seu servidor respondeu. - Para ver um `transaction.paid` de verdade, pague um valor baixo. Confira a assinatura como em [Webhooks](/docs/webhooks#validar-a-assinatura). ## Conferir as chamadas no painel No painel, em **Documentação da API**, a lista **Últimas chamadas da API** mostra as criações de cobrança e de checkout que chegaram ao endpoint, com sucesso ou com falha, com o status e, no cartão, o código de erro. Nem toda resposta entra na lista. Os erros de autenticação (`401`), de permissão (`403 scope_required`), de limite de requisições (`429`) e os da `Idempotency-Key` que o Throne confere antes do endpoint (`400 idempotency_key_required`, `422 idempotency_key_mismatch`, `409 idempotency_key_processing` e `409 idempotency_key_race`) não aparecem. No Pix, os erros de validação do corpo também não. Por isso, três casos da tabela acima (sem `Idempotency-Key`, chave de API errada e corpo trocado) não aparecem no painel: confira esses pela resposta que o seu servidor recebeu. ## Próximo passo Siga o [Checklist de verificação](/docs/checklist) antes de abrir as vendas. --- # Checklist de verificação > Roteiro para conferir, passo a passo, se a integração está pronta para vender. Fonte: https://app.thronecorporate.com/docs/checklist ## Como usar Cada item diz o que conferir e como ver o resultado. Faça na ordem, com a chave de produção e valores baixos. Não existe ambiente de testes: veja [Como testar](/docs/testar). ## Chave e conta - [ ] `GET /balance` com a sua chave responde `200`. Um `401` mostra o motivo no campo `detail`. - [ ] A chave secreta fica só no seu servidor, nunca no navegador nem no aplicativo. - [ ] Toda chamada manda `Accept: application/json`. - [ ] O cliente HTTP tem timeout: 60 segundos em `POST /elements/checkout` e `POST /elements/checkout/br`, e 20 segundos nas demais ([por quê](/docs/limites#tempo-de-resposta)). ## Criar o checkout de cartão - [ ] `GET /elements/acquirers?currency=` com a moeda do seu público lista as adquirentes habilitadas, a padrão e os limites de valor atuais. - [ ] `POST /elements/checkout` responde `201`. Confira na resposta: `transaction_id`, `acquirer` com a adquirente esperada e `expires_at`. - [ ] A `Idempotency-Key` é gerada antes da chamada e gravada junto do pedido. Num timeout, a repetição usa a mesma chave. - [ ] O checkout é criado no clique de compra, e o `transaction_id` é reaproveitado se o mesmo comprador voltar em até 24 horas. ## Página de pagamento - [ ] O elemento aparece com o `data-id` certo e sem altura fixa. - [ ] Sem `sandbox` em volta do elemento, ou com a lista mínima de [Throne Elements](/docs/cartao/elements#boas-praticas). - [ ] Na NovaCorp, na Eminence, na Apex, na ShadowBank e na Vortex, o `checkout_url` aberto com `?nl=de` mostra o formulário em alemão. - [ ] O pagamento de um valor baixo, com o seu cartão, termina com `throne-elements:paid` na página. - [ ] Ao lado do botão de pagar, um aviso diz que o banco pode pedir a confirmação da compra: na NovaCorp, numa página segura; na Eminence, ali mesmo, dentro do elemento. ## Checkout Brasil (se você usa) - [ ] A página abre em português, com o campo CPF, e um CPF inválido não deixa pagar. - [ ] Com `appearance`, o botão sai com o texto e as cores pedidos, e você conferiu os avisos `low_contrast` em `warnings`. ## Confirmação da venda - [ ] O webhook `transaction.paid` chega no seu servidor e a assinatura `X-Throne-Signature` confere. - [ ] `GET /transactions/{id}` mostra `status: "paid"`, `currency`, `native_amount_cents` e `fx_rate`. - [ ] O pedido é liberado pelo webhook ou pelo `GET`, nunca só pelo evento da página. - [ ] A conversão da campanha é enviada pelo seu servidor ao receber `transaction.paid` ([como](/docs/webhooks#conversao-de-campanha-pelo-servidor)). - [ ] O histórico de entregas, em [Entregas de webhook](https://app.thronecorporate.com/painel/webhook-deliveries), mostra a entrega com `2xx`. ## Erros - [ ] Um valor abaixo do mínimo (`"amount_cents": 50, "currency": "usd"`) volta `422 invalid_amount`, e a sua tela mostra uma mensagem ao comprador sem repetir a chamada. - [ ] Um valor acima do `max_amount_cents` volta `422 invalid_amount`, e a sua integração usa `alternatives` ou divide o pedido. - [ ] `503 temporarily_unavailable` espera o `Retry-After` e repete com chave nova, poucas vezes. - [ ] `502 checkout_failed` com `"retryable": false` não é repetido. - [ ] No painel, em **Documentação da API**, a lista **Últimas chamadas da API** mostra as chamadas acima, com o status e o código de erro. ## Navegador de app (NovaCorp) - [ ] Abra a sua página dentro do navegador do Instagram ou do Facebook, no celular, e pague um valor baixo. A aba vai para a página segura de pagamento e termina lá. - [ ] Mesmo sem evento na página, o seu servidor recebe `transaction.paid` e libera o pedido. - [ ] A conversão dessa venda aparece na plataforma de anúncios, enviada pelo servidor. --- # Cobrar com Pix > Criar cobrança Pix, mostrar o QR Code, acompanhar o pagamento e vender produto físico. Fonte: https://app.thronecorporate.com/docs/pix ## Como funciona 1. O **seu servidor** cria a cobrança com `POST /transactions`. 2. Você mostra ao comprador o QR Code e o código copia e cola. 3. O comprador paga no aplicativo do banco. 4. O Throne envia o webhook `transaction.paid` para o seu servidor. Só então libere o pedido. A cobrança Pix é sempre em reais. Antes de começar, veja [autenticação e Idempotency-Key](/docs/comecar). O recebimento por Pix precisa estar habilitado na sua conta. Se não estiver, a criação responde `422 pix_not_enabled` e nada é criado; o cartão e os saques por Pix continuam normais. No painel, em Documentação da API, a linha do PIX aparece só quando o recebimento está habilitado. ## Criar a cobrança ```bash cURL curl -X POST https://app.thronecorporate.com/api/v1/transactions \ -H "Authorization: Bearer $THRONE_PK:$THRONE_SK" \ -H "Content-Type: application/json" \ -H "Accept: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "payment_method": "pix", "amount_cents": 4990, "customer": { "name": "Maria Souza", "document": "12345678909", "email": "maria@exemplo.com" }, "metadata": { "loja_pedido_id": "PED-1042" } }' ``` ```php PHP $payload = [ 'payment_method' => 'pix', 'amount_cents' => 4990, // R$ 49,90 'customer' => [ 'name' => 'Maria Souza', 'document' => '12345678909', // CPF ou CNPJ, só números 'email' => 'maria@exemplo.com', ], 'metadata' => ['loja_pedido_id' => 'PED-1042'], ]; $ch = curl_init('https://app.thronecorporate.com/api/v1/transactions'); curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => [ 'Authorization: Bearer ' . getenv('THRONE_PK') . ':' . getenv('THRONE_SK'), 'Content-Type: application/json', 'Accept: application/json', 'Idempotency-Key: ' . bin2hex(random_bytes(16)), ], CURLOPT_POSTFIELDS => json_encode($payload), ]); $cobranca = json_decode(curl_exec($ch), true); $status = curl_getinfo($ch, CURLINFO_HTTP_CODE); // 201 = criada ``` ```js Node.js import { randomUUID } from 'node:crypto'; const res = await fetch('https://app.thronecorporate.com/api/v1/transactions', { method: 'POST', headers: { Authorization: `Bearer ${process.env.THRONE_PK}:${process.env.THRONE_SK}`, 'Content-Type': 'application/json', Accept: 'application/json', 'Idempotency-Key': randomUUID(), }, body: JSON.stringify({ payment_method: 'pix', amount_cents: 4990, customer: { name: 'Maria Souza', document: '12345678909', email: 'maria@exemplo.com' }, metadata: { loja_pedido_id: 'PED-1042' }, }), }); const cobranca = await res.json(); // res.status === 201 ``` ```python Python import os, uuid, requests r = requests.post( "https://app.thronecorporate.com/api/v1/transactions", headers={ "Authorization": f"Bearer {os.environ['THRONE_PK']}:{os.environ['THRONE_SK']}", "Accept": "application/json", "Idempotency-Key": str(uuid.uuid4()), }, json={ "payment_method": "pix", "amount_cents": 4990, "customer": {"name": "Maria Souza", "document": "12345678909", "email": "maria@exemplo.com"}, "metadata": {"loja_pedido_id": "PED-1042"}, }, timeout=20, ) cobranca = r.json() # r.status_code == 201 ``` ### Campos | Campo | Obrigatório | Regra | |---|---|---| | `payment_method` | Sim | `"pix"` | | `amount_cents` | Sim | Inteiro em centavos, de `100` (R$ 1,00) a `5000000` (R$ 50.000,00) | | `customer.name` | Sim | De 3 a 255 caracteres | | `customer.document` | Sim | CPF (11 dígitos) ou CNPJ (14 dígitos), **só números** | | `customer.email` | Sim | E-mail válido | | `customer.phone` | Não | Até 20 caracteres, por exemplo `+5511999998888` | | `customer.birth_date` | Não | `AAAA-MM-DD` | | `customer.address` | Não | `zip_code`, `street`, `number`, `complement`, `neighborhood`, `city`, `state` (UF, 2 letras), `country` (2 letras) | | `expires_at` | Não | Data e hora ISO 8601. Se não vier, a cobrança vale 1 hora | | `metadata` | Não | Objeto livre que volta na consulta e no webhook. Use nomes de campo com um prefixo seu, como `loja_pedido_id` | | `items` | Não | Lista de produtos. Veja [produto físico](#produto-fisico) | | `shipping_address` | Não | Endereço de entrega. Veja [produto físico](#produto-fisico) | | `splits` | Não | Divisão do recebimento. Veja [dividir o recebimento](#dividir-o-recebimento) | A descrição que o comprador vê no banco é definida pelo Throne e não pode ser alterada. Campos fora desta lista são ignorados. ## Resposta `201 Created`: ```json { "id": "72394240-db89-4ec5-b370-4a3e7b998409", "status": "pending", "payment_method": "pix", "amount_cents": 4990, "fee_cents": 250, "net_amount_cents": 4740, "currency": "BRL", "native_amount_cents": 4990, "fx_rate": null, "paid_at": null, "expires_at": "2026-10-03T15:20:00Z", "payment_data": { "qr_code_payload": "00020126580014br.gov.bcb.pix0136...", "qr_code_image_base64": "iVBORw0KGgoAAAANSUhEUgAA...", "expires_at": "2026-10-03T12:20:00-03:00" }, "customer": { "name": "Maria Souza", "document": "12345678909", "email": "maria@exemplo.com" }, "shipping_address": null, "items": null, "metadata": { "loja_pedido_id": "PED-1042" }, "created_at": "2026-10-03T14:20:00Z" } ``` - **Guarde o `id`.** É com ele que você consulta a cobrança e reconhece o webhook. - `fee_cents` e `net_amount_cents` já vêm calculados pela taxa da sua conta. - `payment_data.qr_code_payload` é o copia e cola. `payment_data.qr_code_image_base64` é a imagem do QR Code em PNG, em base64 puro. ## Mostrar o QR Code ```html QR Code Pix ``` Se `qr_code_image_base64` vier `null`, gere o QR Code você mesmo a partir de `qr_code_payload` (qualquer biblioteca de QR Code serve). Se `qr_code_payload` vier vazio, não mostre nada ao comprador: crie outra cobrança com uma `Idempotency-Key` nova. `payment_data.expires_at` vem com fuso (`-03:00`) e o `expires_at` da cobrança vem em UTC: os dois representam o mesmo instante. ## Confirmar o pagamento A cobrança nasce `pending` e vira `paid` quando o pagamento é confirmado. Há duas formas de saber: 1. **Webhook `transaction.paid`** (recomendado). Chega no seu servidor assim que o pagamento é confirmado. Veja [Webhooks](/docs/webhooks) para validar a assinatura. 2. **Consulta** com `GET /transactions/{id}`, usando o `id` da criação. Precisa da permissão `transactions:read`. ```bash curl https://app.thronecorporate.com/api/v1/transactions/72394240-db89-4ec5-b370-4a3e7b998409 \ -H "Authorization: Bearer $THRONE_PK:$THRONE_SK" \ -H "Accept: application/json" ``` Payload do webhook de um Pix pago: ```json { "event_id": "8c1f3c5e-2c1a-4d1e-9a2b-1f0c9d7e6b5a", "type": "transaction.paid", "version": "2026-05-01", "created_at": "2026-10-03T14:21:07Z", "data": { "transaction_id": "72394240-db89-4ec5-b370-4a3e7b998409", "status": "paid", "payment_method": "pix", "amount_cents": 4990, "fee_cents": 250, "net_amount_cents": 4740, "paid_at": "2026-10-03T14:21:05Z", "customer": { "name": "Maria Souza", "document": "12345678909", "email": "maria@exemplo.com" }, "shipping_address": null, "items": null, "metadata": { "loja_pedido_id": "PED-1042" } } } ``` Regras que evitam problemas: - Libere o pedido **só** com o webhook `transaction.paid` ou com uma consulta que mostre `status: "paid"`. - Deduplique pelo `data.transaction_id`: o mesmo pagamento pode chegar mais de uma vez. - Trate o vencimento pelo `expires_at`, não pelo status: uma cobrança não paga costuma continuar `pending` depois do prazo. Um Pix pago depois do prazo ainda vira `paid` e gera `transaction.paid`. Não existe webhook de expiração. - Campos que vierem a mais no `data` (como `card_brand` ou `fx_rate` com valor `null`) podem ser ignorados. - A API não tem listagem de cobranças. Guarde o `id` de cada uma e use o webhook para conciliar. ### Ciclo da cobrança | Status | Quando | |---|---| | `pending` | Aguardando o pagamento. Continua assim depois do prazo se ninguém pagar | | `paid` | Pagamento confirmado | | `expired` | Raro: a cobrança venceu e foi encerrada. Mesmo assim, se o pagamento chegar, ela vira `paid` | Estorno de Pix não é feito pela API. Fale com o suporte do Throne. ## Produto físico Para vender produto físico, envie a lista de itens e o endereço de entrega. Os dois aparecem no painel e voltam no webhook, então você processa o pedido sem outra chamada. ```json { "payment_method": "pix", "amount_cents": 29800, "customer": { "name": "Maria Compradora", "document": "98765432100", "email": "maria@exemplo.com", "phone": "+5511988887777" }, "items": [ { "title": "Camisa Polo, Azul, M", "sku": "POLO-AZUL-M", "quantity": 2, "unit_amount_cents": 9900 }, { "title": "Boné Preto", "sku": "BONE-PRETO", "quantity": 1, "unit_amount_cents": 10000 } ], "shipping_address": { "recipient_name": "Maria Compradora", "recipient_phone": "+5511988887777", "zip_code": "04567000", "street": "Rua das Flores", "number": "250", "complement": "Bloco B, apto 304", "neighborhood": "Vila Mariana", "city": "São Paulo", "state": "SP", "country": "BR", "shipping_method": "standard" } } ``` - `items`: até 100 itens. Cada um exige `title`, `quantity` e `unit_amount_cents`; `sku`, `description`, `category` e `image_url` são opcionais. - **A soma de `quantity × unit_amount_cents` precisa ser exatamente `amount_cents`.** Senão: `422 items_total_mismatch`. - `shipping_address`: exige `recipient_name`, `zip_code`, `street`, `number`, `city` e `state` (UF). `shipping_method` aceita `standard`, `express` ou `pickup`. ## Dividir o recebimento Com `splits`, parte do valor de uma cobrança vai para outra conta do Throne quando ela é paga (por exemplo, num marketplace). ```json "splits": [ { "recipient_seller_id": 4821, "type": "percentage", "percent_basis_points": 1500 }, { "recipient_seller_id": 5310, "type": "fixed", "amount_cents": 500 } ] ``` - `type: "percentage"`: `percent_basis_points` de 1 a 10000 (1500 = 15%). - `type: "fixed"`: `amount_cents` a partir de 100. - Até 20 divisões por cobrança. Não é possível dividir para a sua própria conta (`422 split_invalid`). ## Links de pagamento Um link de pagamento é uma página pronta do Throne, que você compartilha por WhatsApp, e-mail ou redes. A chave precisa da permissão `payment_links:write` (e `payment_links:read` para consultar). ```bash curl -X POST https://app.thronecorporate.com/api/v1/payment-links \ -H "Authorization: Bearer $THRONE_PK:$THRONE_SK" \ -H "Content-Type: application/json" \ -H "Accept: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "description": "Consultoria de 1 hora", "amount_cents": 15000, "max_uses": 1 }' ``` | Campo | Obrigatório | Regra | |---|---|---| | `description` | Sim | Até 500 caracteres | | `amount_cents` | Sim | De `100` a `5000000` | | `customer_required` | Não | Se o comprador precisa preencher os dados (padrão `true`) | | `max_uses` | Não | Quantas vezes o link pode ser pago, de 1 a 100000 | | `expires_at` | Não | Data futura em ISO 8601 | | `redirect_url_success` | Não | Para onde o comprador vai depois de pagar | | `redirect_url_expired` | Não | Para onde vai se o link tiver expirado | | `metadata` | Não | Objeto livre | A resposta traz o campo `url`: é ele que você compartilha. Outros endpoints: - `GET /payment-links`: os 100 links mais recentes. - `GET /payment-links/{id}`: um link, com `used_count` e `usable`. - `POST /payment-links/{id}/revoke`: desativa o link. Se já estava desativado: `409 already_revoked`. Cada pagamento feito pelo link é uma cobrança Pix normal e gera `transaction.paid`. Como o link é sempre Pix, ele segue a mesma regra: com o recebimento por Pix desabilitado na conta, `POST /payment-links` responde `422 pix_not_enabled` e um link já criado mostra "Link indisponível" ao comprador até o recebimento ser habilitado de novo. ## Erros comuns | Status | `error` | O que fazer | |---|---|---| | `422` | (validação) | Corrija o campo indicado em `errors`. O caso mais comum é faltar `customer` ou `customer.document` | | `422` | `invalid_request` | Regra de valor ou método (por exemplo, abaixo de R$ 1,00). Leia o `message` | | `422` | `pix_not_enabled` | O recebimento por Pix não está habilitado nesta conta. Nada foi criado. Fale com o suporte; cartão e saques por Pix continuam | | `422` | `items_total_mismatch` | A soma dos itens não bate com `amount_cents` | | `422` | `split_invalid` | Divisão para a própria conta | | `409` | `idempotency_key_processing` | A primeira chamada com essa `Idempotency-Key` ainda está em andamento. Espere alguns segundos e repita a mesma chamada, igual. Veja [como repetir com segurança](/docs/comecar#como-repetir-uma-chamada-com-seguranca) | | `429` | `rate_limit_exceeded` | Mais de 10 cobranças por segundo. Espere o `Retry-After` e tente com uma `Idempotency-Key` nova | | `502` | `acquirer_error`, com `"retry_same_key": true` | A cobrança pode ter sido criada. Repita a mesma chamada, com a mesma `Idempotency-Key` e o mesmo corpo, e nunca com uma chave nova | | `502` | `acquirer_error`, sem `retry_same_key` | A adquirente recusou antes de criar, e nada foi criado. Tente de novo com uma `Idempotency-Key` nova | Lista completa em [Erros](/docs/errors). --- # Pix com a Shadow Pix > Regras e comportamento do Pix quando a conta é atendida pela Shadow Pix: faixa de R$ 10,00 a R$ 5.000,00, timeout de 60 segundos na emissão, prazo do código no banco, dedução por pedido, confirmação pelo webhook e saque por Pix. Fonte: https://app.thronecorporate.com/docs/pix/shadowpix ## O que é a Shadow Pix A Shadow Pix é uma das adquirentes de Pix do Throne. Ela atende as duas pontas do Pix da sua conta: - **entrada**: a cobrança com QR Code e copia e cola, criada em `POST /transactions` com `payment_method: "pix"`; - **saída**: o saque por Pix, pedido em `POST /withdrawals`. A sua integração não muda. São os mesmos endpoints, o mesmo corpo, os mesmos status e o mesmo webhook do guia [Cobrar com Pix](/docs/pix) e do guia [Saldo e saques](/docs/saques). Esta página reúne só o que é específico desta adquirente: a faixa de valor, o prazo do código, o tempo da emissão, o que fazer quando a emissão não se confirma e o que o comprador vê no banco. Se a sua conta usa outra adquirente de Pix, nada aqui se aplica. Leia o [guia de Pix](/docs/pix) e pronto. ## Você não escolhe a adquirente de Pix No cartão existe o campo `acquirer`, que escolhe a adquirente em cada chamada ([como escolher](/docs/cartao/escolher)). **No Pix esse campo não existe.** A adquirente de entrada e a adquirente de saída são apontadas na sua conta, uma de cada vez, e quem aponta é o Throne. O que isso significa na prática: - `POST /transactions` com `payment_method: "pix"` ignora qualquer campo de adquirente que você mandar. Não há erro: o campo simplesmente não existe nessa chamada. - A mesma adquirente de entrada atende a API, a tela Depositar do painel e os [links de pagamento](/docs/pix#links-de-pagamento). - A adquirente que paga os seus saques pode ser outra, diferente da que emite as suas cobranças. Isso não muda uma linha do seu código. - Para trocar, fale com o suporte. Não é uma configuração do painel do seller. > [!TIP] > Não escreva no seu código nada que dependa do nome da adquirente de Pix. Trate a faixa de valor como configuração sua, num lugar só, e você troca de adquirente sem mexer na integração. ## Faixa de valor É a única regra desta página capaz de quebrar uma integração que já funcionava. | O quê | Valor | |---|---| | Mínimo de uma cobrança | **R$ 10,00** (`1000`) | | Máximo de uma cobrança | **R$ 5.000,00** (`500000`) | | Mínimo de um saque | **R$ 10,00** (`1000`), e o mínimo da sua conta pode ser maior | A faixa do [guia de Pix](/docs/pix) (de `100` a `5000000`) é a faixa que a API aceita no corpo da chamada. A faixa desta adquirente é mais estreita e vale por cima dela. Um valor aceito na validação do corpo pode, ainda assim, não virar cobrança. Fora da faixa, a cobrança **não é emitida**. Nada é cobrado e nenhum QR Code é gerado. A resposta é `502 acquirer_error`, **sem** o campo `retry_same_key`: ```json { "error": "acquirer_error", "message": "Não foi possível gerar a cobrança agora. Tente de novo em instantes, com uma Idempotency-Key nova." } ``` > [!WARNING] > **Antes de repetir qualquer `502`, olhe o campo `retry_same_key`.** Sem ele, como aqui, nada foi criado e a chave nova é segura. Com `"retry_same_key": true`, a cobrança **pode** existir do outro lado, e chave nova gera um segundo código pagável para o mesmo pedido. Esse caso está em [o que fazer num `502`](#o-que-fazer-num-502). Confira o valor antes de repetir: repetir o mesmo valor falha de novo, quantas vezes você tentar. O que acontece com a `Idempotency-Key` daquela tentativa: a tentativa **não vira venda nenhuma** (não existe venda `failed` para consultar depois) e a chave é **liberada na hora**, sem ficar presa. Corrija o valor e repita com uma chave nova, como a mensagem pede. > [!WARNING] > **Cuidado com os links de pagamento.** `POST /payment-links` aceita de `100` a `5000000` e essa validação não conhece a faixa da adquirente. Um link de R$ 7,00 ou de R$ 8.000,00 é criado sem erro nenhum e só falha quando o comprador clica para pagar. Crie links dentro de R$ 10,00 a R$ 5.000,00. ## Criar a cobrança Idêntico ao [guia de Pix](/docs/pix#criar-a-cobranca). Nenhum campo novo, nenhum campo a menos: ```bash cURL curl -X POST https://app.thronecorporate.com/api/v1/transactions \ --max-time 60 \ -H "Authorization: Bearer $THRONE_PK:$THRONE_SK" \ -H "Content-Type: application/json" \ -H "Accept: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "payment_method": "pix", "amount_cents": 4990, "customer": { "name": "Maria Souza", "document": "12345678909", "email": "maria@exemplo.com", "phone": "+5511988887777" }, "metadata": { "loja_pedido_id": "PED-1042" } }' ``` Os campos de `customer` são os do guia de Pix: `name`, `document` e `email` obrigatórios, `phone` opcional. Mande os dados do seu comprador, como em qualquer cobrança Pix. A resposta é a de sempre, `201` com `status: "pending"`, `payment_data.qr_code_payload` (o copia e cola) e `payment_data.qr_code_image_base64` (o PNG do QR Code, gerado pelo Throne). `fee_cents` e `net_amount_cents` saem da taxa da sua conta. ### O timeout do seu cliente aqui é 60 segundos O padrão da casa para esta chamada é 20 segundos ([tempo de resposta e timeouts](/docs/comecar#tempo-de-resposta-e-timeouts)). **Nesta adquirente use 60.** O motivo: quando a adquirente responde "ocupado", ou quando a conexão com ela cai, o Throne **repete a emissão com a mesma chave**, dentro da sua chamada, em vez de criar uma segunda cobrança. Essa janela tem teto de 40 segundos. Um timeout de 20 segundos corta você no meio dela, e o pior não é a espera perdida: a cobrança pode nascer depois que o seu cliente já desistiu. - **Sem resposta, repita a MESMA chamada, igual, com a MESMA `Idempotency-Key`.** Se a cobrança nasceu, você recebe a original. - **Pode vir `409 idempotency_key_processing` em vez da cobrança.** Significa que a primeira chamada ainda está rodando. Espere alguns segundos e repita igual. - **Pode vir `502` com `"retry_same_key": true`.** A emissão não se confirmou e a cobrança pode existir. A saída está em [o que fazer num `502`](#o-que-fazer-num-502). - **Nunca com uma chave nova.** Chave nova é cobrança nova: duas cobranças para o mesmo pedido, e as duas pagáveis ao mesmo tempo (veja [o prazo do código](#o-prazo-do-codigo)). Dois detalhes desta adquirente que vale conhecer: - **O `metadata` fica no Throne.** Ele volta na consulta e no webhook, como sempre, e não viaja para a adquirente. O mesmo vale para `items` e `shipping_address`. - **Não existe descrição de produto no Pix.** Nada do que você manda aparece no aplicativo do banco do comprador (veja [o que o comprador vê](#o-que-o-comprador-ve)). ## O prazo do código Aqui há uma diferença de verdade com o [guia de Pix](/docs/pix), e ela vale uma leitura atenta. **O prazo que você manda não chega à adquirente.** Todo código que ela emite fica pagável no banco por cerca de **2 dias**, contados da emissão, seja qual for o seu prazo. | Campo | O que é | Quanto dura | |---|---|---| | `expires_at` da cobrança | O seu prazo, o que vale na consulta e no painel | O que você mandou, ou 1 hora quando não manda nada | | `payment_data.expires_at` | O menor entre o seu prazo e o fim do código na adquirente | Igual ao `expires_at` quando o seu prazo é de até 2 dias. Cerca de 2 dias depois da emissão quando você manda mais que isso | Os dois campos só mostram o mesmo instante quando o seu prazo é o menor. Se você mandar mais de 2 dias, eles **não coincidem**: o `expires_at` continua com o que você mandou e o `payment_data.expires_at` mostra quando o código deixa de ser pagável. Para o código, vale o `payment_data.expires_at`. **Com prazo de até 2 dias, o código continua pagável no banco depois dele, e nenhum campo mostra isso.** Os dois campos ficam no seu prazo, e o código segue vivo até completar cerca de 2 dias da emissão. Pix pago fora do prazo ainda vira `paid` e ainda gera `transaction.paid`, como já está no guia de Pix. Nesta adquirente a janela é de 2 dias, então o caso deixa de ser raro. O que fazer com isso: - **Trate `transaction.paid` atrasado como pagamento normal**, mesmo horas depois. Nunca como erro. - **Nunca crie uma segunda cobrança para um pedido que já tem uma `pending`.** Guarde o `id` da primeira e mostre sempre o mesmo `qr_code_payload` (`GET /transactions/{id}`). O código anterior segue pagável por cerca de 2 dias mesmo depois do `expires_at`: duas cobranças do mesmo pedido são dois códigos vivos ao mesmo tempo, e o comprador pode pagar os dois. - **Prazo acima de 2 dias não estica o código.** Ele deixa de ser pagável no `payment_data.expires_at`, e não no seu `expires_at`. Pare de mostrar o QR Code nesse instante. - **Deduplique por PEDIDO, não só por `data.transaction_id`.** Dois `transaction.paid` de duas cobranças diferentes do mesmo pedido têm `transaction_id` diferentes e passam direto pela sua dedução. Antes de liberar, confira no seu banco se aquele pedido já está pago. - **Um Pix pago duas vezes não volta sozinho.** Não existe estorno de Pix pela API. A devolução é manual, pelo suporte, e demora. - **Se o seu pedido tem prazo curto, controle do seu lado**: pare de mostrar o QR Code e feche o pedido no seu sistema quando quiser. Só deixe decidido o que você faz se o pagamento chegar depois disso, porque ele pode chegar. - **Não existe webhook de expiração.** Uma cobrança não paga continua `pending`. ## O que o comprador vê - **O nome do recebedor é definido pelo Throne**, é o mesmo para todas as contas e não pode ser alterado. Não é o nome da sua loja, nem o seu CNPJ, nem nada que você mande na chamada. - **Não aparece nome de produto nem descrição.** O comprador vê valor e recebedor, e nada mais. - **O valor aparece exato**, sem acréscimo e sem arredondamento. Avise o comprador na sua própria página, antes de ele abrir o aplicativo do banco: diga que o nome do recebedor é diferente do nome da sua loja. É o assunto que mais gera dúvida e desistência no pagamento por Pix. Uma linha de texto ao lado do QR Code resolve. ## Confirmar o pagamento **A cobrança nasce `pending`. Sempre.** A resposta da criação diz que o QR Code existe, não que alguém pagou. O pagamento chega depois, pelo webhook. Libere o pedido só por um destes dois caminhos: 1. **Webhook `transaction.paid`** (recomendado). Chega logo depois da confirmação do pagamento. Deduplique pelo `data.transaction_id` (o mesmo pagamento pode chegar mais de uma vez) **e pelo seu pedido**, pelo motivo da seção anterior. Veja [Webhooks](/docs/webhooks). 2. **Consulta** `GET /transactions/{id}` mostrando `status: "paid"`. > [!WARNING] > **Nunca libere pedido pela resposta da criação.** O `201` sai antes de o comprador abrir o aplicativo do banco. Um pedido liberado no `201` é um pedido entregue de graça. O ciclo é o do guia de Pix, sem nada a mais: `pending` enquanto espera, `paid` quando o pagamento é confirmado. Uma cobrança não paga fica `pending`, inclusive depois do prazo. ## Saque por Pix Igual ao [guia de Saldo e saques](/docs/saques), sem campo novo: ```bash cURL curl -X POST https://app.thronecorporate.com/api/v1/withdrawals \ -H "Authorization: Bearer $THRONE_PK:$THRONE_SK" \ -H "Content-Type: application/json" \ -H "Accept: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "amount_cents": 50000, "totp_code": "123456", "destination": { "key": "12345678909", "key_type": "cpf" } }' ``` O que vale nesta adquirente: - **Os cinco tipos de chave funcionam**: `cpf`, `cnpj`, `email`, `phone` (formato internacional, `+5511999998888`) e `evp` (a chave aleatória, com hífens). Um `key_type` fora dessa lista é recusado na validação do corpo. - **Mínimo de R$ 10,00** na adquirente. O mínimo da sua conta costuma ser maior: o padrão do Throne é R$ 50,00, e o seu aparece no painel, na tela [Saque](https://app.thronecorporate.com/painel/withdraw). - **Só Pix.** Esta adquirente não paga saque em cripto. Um pedido com `destination.address` numa conta que não tem cripto configurado termina `failed` e o valor volta inteiro para o saldo. - O saque nasce `requested` e passa por `processing`. Quase sempre termina `completed` ou `failed`, e **nem todo saque gera webhook**: consulte `GET /withdrawals/{id}`. O `failure_reason` do `GET` vem em linguagem pronta para mostrar ao seu cliente. - Um saque **recusado** (`failed`) devolve o valor inteiro, taxa incluída, para o saldo. - **Um saque pode ficar em `processing` sem desfecho.** Quando o envio à adquirente não se confirma (por exemplo: ela não responde, responde erro de servidor ou aceita o pedido sem devolver o identificador dele), o Throne não reenvia e não devolve o valor no escuro: reenviar pagaria duas vezes, porque o Pix pode ter saído. O saque continua `processing`, e o valor segue reservado e fora do seu saldo disponível. - **Com um saque em `processing`, consulte `GET /withdrawals/{id}`**, porque esse desfecho pode chegar sem webhook. Não trate `processing` como falha e não peça o mesmo saque de novo: o valor só volta ao saldo quando o saque termina `failed`. Se ele continuar `processing` uma hora depois do pedido, abra um pedido no suporte com o `id` do saque. As regras de segurança do saque não mudam: permissão `withdrawals:write` na chave, IPs de saque cadastrados e `totp_code` em cada pedido. Leia [antes do primeiro saque](/docs/saques#antes-do-primeiro-saque). > [!WARNING] > Um saque repetido por engano é dinheiro saindo duas vezes. Nunca gere uma `Idempotency-Key` nova para um saque cujo resultado você não conhece. A ordem certa está em [repetir um pedido de saque](/docs/saques#repetir-um-pedido-de-saque). ## Estorno Estorno de Pix não é feito pela API, nesta adquirente nem em nenhuma outra. Não existe endpoint de estorno de Pix no Throne. Se precisar devolver um valor ao comprador, fale com o suporte. A devolução é manual e demora, então vale mais evitar a cobrança dobrada do que contar com o estorno. ## Erros Os erros são os do [guia de Pix](/docs/pix#erros-comuns), com uma diferença que pesa: no `502`, quem diz se a chave nova é segura é o campo `retry_same_key`. Os que mais aparecem nesta adquirente: | Status | `error` | O que aconteceu | O que fazer | |---|---|---|---| | (sem resposta) | | Timeout do seu lado, conexão cortada ou resposta sem o JSON da API. A cobrança **pode** ter sido criada | Repita a MESMA chamada, igual, com a MESMA `Idempotency-Key`. Nunca com chave nova: chave nova cria uma segunda cobrança para o mesmo pedido, e os dois códigos ficam pagáveis juntos por cerca de 2 dias. Se vier `409` ou `502`, leia a linha dele abaixo | | `502` | `acquirer_error`, com `"retry_same_key": true` | A emissão não se confirmou. **A cobrança pode existir do outro lado.** Nada fica gravado na sua conta e a chave é liberada para a repetição do mesmo corpo | Repita a MESMA chamada, com a MESMA `Idempotency-Key` e o MESMO corpo. Nunca com chave nova. Veja [o que fazer num `502`](#o-que-fazer-num-502) | | `502` | `acquirer_error`, sem `retry_same_key` | Nada foi cobrado para este pedido e nenhum código dele existe. O motivo mais comum é valor fora de R$ 10,00 a R$ 5.000,00. O mesmo `502` sai quando a adquirente mostra que a chave já está ligada a uma cobrança de outro pedido | Confira o valor e repita com uma `Idempotency-Key` nova. Não insista com a mesma chave: ligada a outro pedido, ela devolve sempre este erro | | `422` | `pix_not_enabled` | O recebimento por Pix não está habilitado nesta conta. Nada foi criado | Fale com o suporte. O saque por Pix continua funcionando | | `422` | (validação) | Campo do corpo inválido. O mais comum é faltar `customer` ou `customer.document` | Corrija o campo indicado em `errors` | | `409` | `idempotency_key_processing` | A primeira chamada com essa chave ainda está rodando. A emissão pode levar até 40 segundos | Espere alguns segundos e repita igual. Não troque a chave por causa deste `409` | | `409` | `idempotency_key_race` | Duas chamadas ao mesmo tempo com a mesma chave, ou uma chave cuja primeira chamada recebeu um `4xx` | Depois de um `4xx`, nada foi criado: use uma chave nova. Se foram duas chamadas ao mesmo tempo, espere alguns segundos e repita a mesma chamada, igual, porque a outra pode ter criado a cobrança | | `422` | `idempotency_key_mismatch` | A mesma chave voltou com um corpo diferente (valor corrigido, nome do comprador reescrito) | Use uma chave nova, e só depois de ter certeza de que a tentativa anterior não criou cobrança | | `429` | `rate_limit_exceeded` | Mais de 10 cobranças por segundo | Espere o `Retry-After` e repita com uma chave nova | Lista completa em [Erros](/docs/errors). > [!WARNING] > **Nesta rota, o `502` desta página vale por cima de qualquer regra geral.** Onde outra página tratar o `502` como "nada foi criado, repita com uma chave nova", isso vale só para o `502` **sem** `retry_same_key`. Com `"retry_same_key": true`, siga a seção abaixo. ### O que fazer num 502 Todo `502` desta rota traz o mesmo `error`, `acquirer_error`. Quem separa os dois desfechos é o campo `retry_same_key`: - **Sem `retry_same_key`: nada foi cobrado.** A cobrança foi recusada antes de ser criada, aqui ou na adquirente. O motivo mais comum é valor fora de R$ 10,00 a R$ 5.000,00. Confira o valor, corrija e repita com uma `Idempotency-Key` nova. O mesmo `502` sai quando a adquirente mostra que a chave que você mandou já está ligada, do lado dela, a uma cobrança de **outro pedido**, o que acontece com chave reaproveitada. Nenhuma cobrança deste pedido existe lá, e repetir com essa chave devolve sempre este mesmo erro. Use uma chave nova: um UUID v4 por cobrança. - **Com `"retry_same_key": true`: a cobrança pode existir.** O pedido saiu daqui e o desfecho ficou em aberto. Por exemplo: a adquirente seguiu "ocupada" até o fim da janela de 40 segundos, a conexão caiu no meio do envio, ela devolveu erro de servidor, ou devolveu a cobrança sem o copia e cola. A resposta do segundo caso é esta: ```json { "error": "acquirer_error", "message": "A cobrança pode ter sido criada e não foi confirmada. Repita esta chamada com a mesma Idempotency-Key e o mesmo corpo da primeira tentativa. Não use uma chave nova para este pedido: ela pode gerar uma segunda cobrança.", "retry_same_key": true } ``` Com `"retry_same_key": true`, a ordem é esta: 1. **Não crie outra cobrança.** Chave nova é código novo. Os dois ficam pagáveis ao mesmo tempo por cerca de 2 dias e um Pix pago duas vezes não volta pela API (veja [Estorno](#estorno)). 2. **Repita em seguida a MESMA chamada, com a MESMA `Idempotency-Key`.** A chave é liberada no instante do `502`, então a repetição roda de novo e leva à adquirente a mesma chave da primeira tentativa. Se a cobrança tinha nascido, ela volta em `201`, com o mesmo copia e cola. Se não tinha, nasce agora. Nos dois casos, uma só. Se o comprador já tinha pago aquele código, a repetição também volta em `201`, mas com o `qr_code_payload` vazio: a venda foi registrada e não há nada para pagar de novo. Não mostre QR Code nenhum. O pagamento se confirma pelo caminho de sempre, o webhook `transaction.paid` ou um `GET` com `status: "paid"`. 3. **Repita com o MESMO corpo.** Com outro valor ou outro nome do comprador, a repetição é recusada aqui mesmo, com `422 idempotency_key_mismatch`, e não chega à adquirente. Para mudar o pedido, resolva antes esta tentativa. 4. **Não tente consultar a venda.** A tentativa que falhou não deixa venda nenhuma no Throne: não há `id` para o `GET /transactions/{id}` e nunca existe uma venda `failed` para ler depois. A repetição é a consulta. 5. **Se a repetição também voltar `502` com `retry_same_key`, pare e abra um pedido no suporte com a `Idempotency-Key` daquela tentativa**, antes de criar qualquer cobrança nova para o pedido. Faça o mesmo se ela voltar `502` **sem** o campo: a repetição foi recusada, mas isso não descarta a cobrança da primeira tentativa, e só o suporte confirma que ela não existe. É a mesma ordem do saque sem desfecho. O código e a mensagem que a adquirente devolveu ficam no nosso registro interno e não aparecem na resposta, nem no `metadata` da venda, nem no webhook: não tente ler o motivo pela API. Quem precisa do motivo exato pede ao suporte com a `Idempotency-Key`. ## Checklist - [ ] Cobranças sempre dentro de R$ 10,00 a R$ 5.000,00, conferido no seu código antes da chamada. - [ ] Links de pagamento criados dentro da mesma faixa. - [ ] Pedido liberado só pelo webhook `transaction.paid` ou por um `GET` com `status: "paid"`. Nunca pela resposta da criação. - [ ] Webhook deduplicado pelo `data.transaction_id` **e** pelo seu pedido. - [ ] Um pedido com cobrança `pending` reaproveita o mesmo `qr_code_payload`, em vez de criar outra cobrança. - [ ] `transaction.paid` atrasado tratado como pagamento normal, não como erro. - [ ] Com prazo acima de 2 dias, QR Code escondido no `payment_data.expires_at`, e não no `expires_at`. - [ ] Aviso ao comprador, ao lado do QR Code, de que o nome do recebedor é diferente do nome da sua loja. - [ ] Timeout de 60 segundos no cliente HTTP da criação, e `Idempotency-Key` nova só para cobrança nova: a MESMA chave em toda repetição da mesma cobrança. - [ ] Tratamento de `502` que lê o campo `retry_same_key`: com `true`, repete a mesma chamada com a mesma chave e o mesmo corpo, e nunca gera chave nova. Veja [o que fazer num `502`](#o-que-fazer-num-502). - [ ] Um `201` com `qr_code_payload` vazio tratado como cobrança já paga: nenhum QR Code na tela, e o pedido liberado como qualquer outro, pelo `transaction.paid` ou por um `GET` com `status: "paid"`. - [ ] Para saque: permissão `withdrawals:write`, IPs de saque cadastrados, `totp_code` em cada pedido e `GET /withdrawals/{id}` consultado até `completed` ou `failed`. Um saque que continua `processing` uma hora depois do pedido vai ao suporte com o `id`, e nunca é pedido de novo. - [ ] Uma cobrança de valor baixo paga de verdade, de ponta a ponta, antes de abrir ao público. Veja [Como testar](/docs/testar). --- # Cartão: como funciona > Checkout de cartão embutido na sua página, comum às adquirentes Sterling, Imperium, NovaCorp, Eminence, Apex, ShadowBank e Vortex, com escolha da adquirente por chamada e venda em 48 moedas. Fonte: https://app.thronecorporate.com/docs/cartao ## Visão geral O cartão do Throne é um checkout **embutido na sua página**. O comprador digita o cartão dentro de um elemento do Throne (o Throne Elements), sem sair do seu site. O seu servidor nunca toca no número do cartão: não envie número, validade nem CVV em nenhuma chamada da API. O checkout comum é o `POST /elements/checkout`, e o script é um só, o `throne-elements.js`. Para o comprador brasileiro, com a página em português e o CPF pedido na tela, existe também o [Checkout Brasil](/docs/cartao/brasil), com o mesmo script. Por trás, a cobrança passa por uma **adquirente de cartão habilitada na sua conta**: Sterling, Imperium, NovaCorp, Eminence, Apex ou ShadowBank. Com mais de uma habilitada, cada chamada escolhe a sua pelo campo `acquirer` (veja [escolher a adquirente](#escolher-a-adquirente)). Campos, respostas, erros e webhook são os mesmos nas seis. O que muda é o comportamento do checkout: | Adquirente | Valor de `acquirer` | Verificação do banco | Navegador de app (Instagram, Facebook) | Tentativas por checkout | Guia | |---|---|---|---|---|---| | Sterling | `sterling` | Não tem: aprova ou recusa na hora | Tudo dentro do elemento | 5 | [Sterling](/docs/cartao/sterling) | | Imperium | `imperium` | Não tem: o resultado sai na própria tentativa (uma recusa pode levar até 25 segundos) | Tudo dentro do elemento | 5 | [Imperium](/docs/cartao/imperium) | | NovaCorp | `novacorp` | Abre uma janela de verificação; a tela do banco aparece nela quando ele pede | A própria aba vai para a página segura de pagamento, sem evento na sua página | 6 | [NovaCorp](/docs/cartao/novacorp) | | Eminence | `eminence` | Pode pedir; a tela do banco aparece dentro do próprio elemento | Tudo dentro do elemento | 5 | [Eminence](/docs/cartao/eminence) | | Apex | `apex` | Não tem: aprova ou recusa na hora | Tudo dentro do elemento | 5 | [Apex](/docs/cartao/apex) | | ShadowBank | `shadowbank` | Não tem: aprova ou recusa na hora | Tudo dentro do elemento | 5 | [ShadowBank](/docs/cartao/shadowbank) | | Vortex | `vortex` | Não tem: aprova ou recusa na hora | Tudo dentro do elemento | 5 | [Vortex](/docs/cartao/vortex) | O máximo por venda depende da adquirente e varia com a moeda e com a cotação do dia. A comparação completa, item por item, está em [Comparar adquirentes](/docs/cartao/comparar). ## Escolher a adquirente Com mais de uma adquirente habilitada, cada site (source) escolhe a dela com o campo `acquirer` no `POST /elements/checkout`: ```bash curl -X POST https://app.thronecorporate.com/api/v1/elements/checkout \ -H "Authorization: Bearer $THRONE_PK:$THRONE_SK" \ -H "Content-Type: application/json" \ -H "Accept: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "amount_cents": 4990, "currency": "chf", "acquirer": "novacorp" }' ``` - **Sem o campo**, vale a adquirente padrão da conta, como sempre foi (ou, com o [roteamento por valor](/docs/cartao/escolher#roteamento-por-valor) ligado, outra habilitada que aceite o valor). - Os valores são `sterling`, `imperium`, `novacorp`, `eminence`, `apex`, `shadowbank` e `vortex`. Só vale uma adquirente habilitada na sua conta; outra volta `422 acquirer_not_enabled` e nada é criado. - **Com o campo, o Throne nunca troca a adquirente que você pediu por outra.** - A resposta traz `acquirer` com a adquirente que ficou com a venda. A venda fica nela até o fim. As habilitadas aparecem no painel, em **Documentação da API**, e em `GET /elements/acquirers`, que também mostra o mínimo e o máximo por venda na moeda que você pedir. Tudo sobre a escolha, os limites, o roteamento por valor e a adquirente em pausa está em [Escolher a adquirente](/docs/cartao/escolher). ## Passo a passo 1. O **seu servidor** cria o checkout com `POST /elements/checkout`, na moeda do comprador, quando ele clica em comprar. Guarde o `transaction_id`. 2. A **sua página** coloca o container do Throne Elements com esse `transaction_id` e carrega o script do Throne. 3. O comprador paga dentro do elemento. 4. O Throne envia o webhook `transaction.paid`. Libere o pedido só nesse momento. ## 1. Criar o checkout A criação fala com a adquirente. Use **60 segundos** de timeout no seu cliente HTTP e gere a `Idempotency-Key` antes de chamar, guardando junto do pedido. ```bash cURL curl -X POST https://app.thronecorporate.com/api/v1/elements/checkout \ --max-time 60 \ -H "Authorization: Bearer $THRONE_PK:$THRONE_SK" \ -H "Content-Type: application/json" \ -H "Accept: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "amount_cents": 4990, "currency": "usd", "customer": { "name": "Emma Walker", "email": "emma@example.com" } }' ``` ```php PHP $chave = bin2hex(random_bytes(16)); // guarde junto do pedido antes de chamar $ch = curl_init('https://app.thronecorporate.com/api/v1/elements/checkout'); curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_CONNECTTIMEOUT => 10, CURLOPT_TIMEOUT => 60, CURLOPT_HTTPHEADER => [ 'Authorization: Bearer ' . getenv('THRONE_PK') . ':' . getenv('THRONE_SK'), 'Content-Type: application/json', 'Accept: application/json', 'Idempotency-Key: ' . $chave, ], CURLOPT_POSTFIELDS => json_encode([ 'amount_cents' => 4990, // US$ 49,90 'currency' => 'usd', 'customer' => ['name' => 'Emma Walker', 'email' => 'emma@example.com'], ]), ]); $raw = curl_exec($ch); if ($raw === false) { // sem resposta (conexão caiu ou timeout): repita com a MESMA $chave } $checkout = json_decode((string) $raw, true); // entregue só o transaction_id para a sua página echo json_encode(['transaction_id' => $checkout['transaction_id'] ?? null]); ``` ```js Node.js import { randomUUID } from 'node:crypto'; const chave = randomUUID(); // guarde junto do pedido antes de chamar const res = await fetch('https://app.thronecorporate.com/api/v1/elements/checkout', { method: 'POST', headers: { Authorization: `Bearer ${process.env.THRONE_PK}:${process.env.THRONE_SK}`, 'Content-Type': 'application/json', Accept: 'application/json', 'Idempotency-Key': chave, }, body: JSON.stringify({ amount_cents: 4990, currency: 'usd', customer: { name: 'Emma Walker', email: 'emma@example.com' }, }), signal: AbortSignal.timeout(60000), // num timeout, repita com a MESMA chave }); const { transaction_id } = await res.json(); // res.status === 201 ``` ```python Python import os, uuid, requests chave = str(uuid.uuid4()) # guarde junto do pedido antes de chamar r = requests.post( "https://app.thronecorporate.com/api/v1/elements/checkout", headers={ "Authorization": f"Bearer {os.environ['THRONE_PK']}:{os.environ['THRONE_SK']}", "Accept": "application/json", "Idempotency-Key": chave, }, json={"amount_cents": 4990, "currency": "usd", "customer": {"name": "Emma Walker", "email": "emma@example.com"}}, timeout=60, # num timeout, repita com a MESMA chave ) transaction_id = r.json()["transaction_id"] # r.status_code == 201 ``` ### Campos | Campo | Obrigatório | Regra | |---|---|---| | `amount_cents` | Sim | Inteiro na **menor unidade da moeda** (US$ 49,90 = `4990`). Em moedas sem casa decimal, a unidade inteira (¥ 5000 = `5000`) | | `currency` | Sim | Código de 3 letras, maiúsculas ou minúsculas. Veja a [lista](/docs/cartao/moeda#moedas-aceitas) | | `acquirer` | Não | `sterling`, `imperium`, `novacorp`, `eminence`, `apex`, `shadowbank` ou `vortex`. Escolhe a adquirente desta venda entre as habilitadas na conta. Sem o campo, vale a padrão. Veja [escolher a adquirente](#escolher-a-adquirente) | | `customer.name` | Não, salvo na ShadowBank e na Vortex | Até 255 caracteres (na ShadowBank e na Vortex, até 120). O Throne mantém só letras, espaços, apóstrofo e hífen e corta em 100. Vai pré-preenchido no campo "nome no cartão"; o comprador pode corrigir | | `customer.email` | Não, salvo na ShadowBank e na Vortex | E-mail do comprador. Um e-mail inválido é descartado. Na ShadowBank, um e-mail inválido ou com mais de 180 caracteres volta `422 customer_required_for_shadowbank`; na Vortex, `422 customer_required_for_vortex` | | `customer.document` | Não | CPF, CNPJ ou passaporte, de 5 a 20 caracteres, com ou sem pontos, traços e barra. `document_type` (`cpf`, `cnpj` ou `passport`) é opcional: sem ele, 11 dígitos valem como CPF, 14 como CNPJ, e um documento com letras ou com outra quantidade de dígitos, como passaporte | | `customer.phone` | Não | Objeto `{ "country_code": "55", "area_code": "11", "number": "999998888" }`. Aceita espaço, hífen, `+`, parênteses, ponto e barra; o Throne guarda só os dígitos | | `customer.address` | Não | `line_1`, `line_2`, `zip_code`, `city`, `state` e `country` (2 letras, ISO 3166; 3 letras é convertido) | O checkout sempre pede ao comprador **número do cartão, validade, CVV e nome impresso no cartão**, e nada além disso. Os dados de `customer` ficam registrados na venda e voltam no webhook e na consulta já normalizados: documento só com dígitos (passaporte em maiúsculas), `document_type` em maiúsculas e telefone só com dígitos. Fora da ShadowBank, nenhum campo de `customer` gera erro. Um campo fora do formato é ajustado ou descartado, a venda é criada e a resposta `201` avisa em `warnings`. Só `amount_cents`, `currency`, `acquirer`, `recurrence`, um `one_click_from` fora do formato (texto de até 64 caracteres) e, na ShadowBank, a falta de `customer.name` ou `customer.email` (`customer_required_for_shadowbank`, veja [Cartão com a ShadowBank](/docs/cartao/shadowbank)) podem dar `422`. Este endpoint não aceita `metadata` nem itens: guarde o `transaction_id` para ligar a venda ao seu pedido. ### Resposta `201 Created`: ```json { "transaction_id": "ae3bf1da-0c9b-4818-9140-c6432152c23e", "status": "pending", "checkout_url": "https://pay.example.com/checkout/0f1e2d3c4b5a69788796a5b4c3d2e1f0", "acquirer": "sterling", "display": { "currency": "USD", "amount_cents": 4990 }, "expires_at": "2026-10-08T14:20:00Z" } ``` - `acquirer` é a adquirente que ficou com a venda. - `checkout_url` é o endereço do checkout hospedado. Use como veio: o host e o formato podem mudar. O recomendado é embutir com o Throne Elements. - `display` repete a moeda e o valor que você mandou. Contas com integração antiga podem não receber esse campo. - `expires_at` é o fim da validade do checkout, em UTC. Como `display`, pode faltar em contas com integração antiga. - `warnings` aparece só quando há aviso. No checkout comum, o aviso é sobre um campo de `customer` que foi ajustado ou descartado: cada aviso traz `field`, `code` (`converted`, `inferred` ou `discarded`) e `message`. No [Checkout Brasil](/docs/cartao/brasil), `code` também pode ser `low_contrast`, sobre `appearance`. Serve para log: a venda foi criada. ```json "warnings": [ { "field": "customer.address.country", "code": "converted", "message": "País em 3 letras convertido para o código de 2 letras (ISO 3166)." } ] ``` O valor creditado no seu saldo e o `fx_rate` aparecem só quando a venda é paga. ## 2. Embutir na sua página ```html
``` | Atributo | O que faz | |---|---| | `data-id` | O `transaction_id` da criação. Precisa ser o UUID exato, senão nada aparece | | `data-color` | Cor do botão e dos realces, no formato `#rrggbb` | | `data-theme` | `light` ou `dark`. O elemento também detecta sozinho a cor de fundo do seu site e se ajusta a ela | | `data-radius` | Arredondamento dos cantos, de `0` a `32` (px). Padrão `10` | Boas práticas: - Carregue o script **do endereço acima**. Não hospede uma cópia. - Não fixe a altura do container nem esconda o overflow. - Não coloque o elemento dentro de outro iframe com `sandbox`. Se não der para tirar, ele precisa incluir no mínimo `allow-scripts allow-same-origin allow-forms allow-popups allow-popups-to-escape-sandbox allow-top-navigation-by-user-activation`. - Em página de uma tela só (React, Vue, etc.), depois de inserir o container chame `window.ThroneElements.mount()`. Para trocar de checkout, use um container novo: mudar o `data-id` de um container já montado não troca o checkout ([detalhes](/docs/cartao/elements#pagina-de-uma-tela-so-react-vue-e-outras)). - Se o seu site usa Content-Security-Policy, libere `https://app.thronecorporate.com` em `script-src` e em `img-src`, e em `frame-src` libere esse domínio e o domínio do `checkout_url`. A referência completa do elemento, com navegador de app e página de uma tela só, está em [Throne Elements](/docs/cartao/elements). ### Eventos na sua página ```js window.addEventListener('throne-elements:ready', () => { // elemento montado, pronto para receber o cartão }); window.addEventListener('throne-elements:paid', (e) => { // e.detail.transaction_id: o comprador concluiu. Mostre o "obrigado". }); window.addEventListener('throne-elements:error', (e) => { const code = e.detail.code || e.detail.message; // declined, attempts_exhausted, network... }); ``` Os eventos são **sinais de tela**. A confirmação que vale para liberar o pedido é o webhook `transaction.paid` ou o `GET /transactions/{id}` com `status: "paid"`. Na NovaCorp, em navegador de app, nenhum evento chega à sua página. Os códigos de erro, por adquirente, estão em [Throne Elements](/docs/cartao/elements#codigos-de-erro). ### Idioma do checkout O checkout sai no idioma do país do comprador, identificado pelo IP. Compradores no Brasil, em Portugal ou de país não identificado veem em inglês. O idioma não é configurado pela API. Para vender ao comprador brasileiro com a página em português e o CPF pedido na tela, use o [Checkout Brasil](/docs/cartao/brasil). ## Moeda e limites Você manda o valor **na moeda do comprador** e ele paga nessa moeda; não mexa no valor no seu site. O saldo da sua conta é em reais, e a cotação que credita o seu saldo é **fixada na criação**: ela não muda depois, mesmo que o comprador pague horas mais tarde, e volta como `fx_rate` na venda paga. - São 48 moedas. Em JPY, KRW, VND, CLP, HUF, TWD e PYG, `amount_cents` é a unidade inteira. - **Mínimo**: varia com a moeda e com a cotação do dia, e algumas adquirentes têm um mínimo próprio, maior. Abaixo dele, `422 invalid_amount` com `min_amount_cents` na moeda que você mandou. - **Máximo**: depende da adquirente e varia com a moeda e com a cotação do dia. Acima dele, `422 invalid_amount` com `max_amount_cents` na moeda que você mandou. `GET /elements/acquirers?currency=` mostra o máximo atual. - Na fatura, a compra aparece sem o nome da sua loja. Na NovaCorp e na Eminence, o banco do comprador pode pedir uma confirmação da compra. Avise isso ao lado do botão de pagar. A lista de moedas, o efeito da cotação no máximo e um exemplo em franco suíço estão em [Moedas e limites](/docs/cartao/moeda). ## 3. Confirmar a venda ### Webhook `transaction.paid` ```json { "event_id": "1b9d6bcd-bbfd-4b2d-9b5d-ab8dfbbd4bed", "type": "transaction.paid", "version": "2026-05-01", "created_at": "2026-10-03T14:31:02Z", "data": { "transaction_id": "ae3bf1da-0c9b-4818-9140-c6432152c23e", "status": "paid", "payment_method": "credit_card", "amount_cents": 31237, "fee_cents": 4148, "net_amount_cents": 27089, "paid_at": "2026-10-03T14:30:58Z", "card_brand": "visa", "card_last4": "4242", "currency": "CHF", "native_amount_cents": 4990, "fx_rate": "6.26000000", "customer": { "name": "Luca Meier", "email": "luca@example.ch" }, "shipping_address": null, "items": null, "metadata": {} } } ``` - `currency` e `native_amount_cents` são a venda na moeda do comprador, como você criou. - `amount_cents` é o valor **bruto** creditado no seu saldo, em reais (a moeda da sua conta); `net_amount_cents` é o que entra de fato, já descontada a taxa de cartão. - `fx_rate` é a cotação usada para creditar o seu saldo, fixada na criação (reais por 1 unidade da moeda da venda). **No webhook ele chega como texto com 8 casas** (`"6.26000000"`); no `GET` chega como número. Trate como decimal antes de calcular. Em venda criada em BRL, `fx_rate` é `null`. - `card_brand` vem em minúsculo (`visa`, `mastercard`, `elo`, `amex`, `hipercard`, entre outras). Bandeira e final do cartão vêm só no webhook. - O formato é idêntico nas seis adquirentes. Valide a assinatura sobre o corpo cru, como em [Webhooks](/docs/webhooks). **Conversão de campanha.** Envie a conversão de compra para a plataforma de anúncios pelo seu servidor, ao receber `transaction.paid`, e não só pelo evento `throne-elements:paid` da página. O evento de tela pode não chegar, como no navegador de app da NovaCorp. Veja [Webhooks](/docs/webhooks#conversao-de-campanha-pelo-servidor). ### Consulta `GET /transactions/{id}` com o `transaction_id`: - **Antes do pagamento**: `status: "pending"`, `amount_cents`, `fee_cents` e `net_amount_cents` iguais a 0, `fx_rate` e `paid_at` nulos. `currency` e `native_amount_cents` já vêm com o que você mandou. - **Depois do pagamento**: `status: "paid"`, `amount_cents`, `fee_cents` e `net_amount_cents` preenchidos (o seu saldo, em reais) e `fx_rate` como número. - `acquirer` traz a adquirente da venda. ## Ciclo da venda | Status | Quando | |---|---| | `pending` | Checkout aberto. **Uma recusa com tentativas sobrando não muda o status**: o comprador pode tentar de novo | | `paid` | Pagamento confirmado | | `failed` | O checkout fechou por recusa. A regra muda por adquirente: veja [Status e ciclo de vida](/docs/cartao/status#quando-a-venda-vira-failed) | | `expired` | O checkout passou da validade (24 horas) sem pagamento | | `refunded` | Estornada | | `chargeback` | Contestada pelo comprador | - `failed` e `expired` não são definitivos: se o pagamento for confirmado depois, a venda passa a `paid` e o webhook é enviado. Libere o pedido **sempre** pelo webhook ou pela consulta, nunca pela falta de erro. - Para vender de novo depois de `failed` ou `expired`, crie um checkout novo. - No cartão, o único webhook é `transaction.paid`. Recusa, expiração, estorno e contestação aparecem na consulta. - **Estorno** não é feito pela API. Fale com o suporte do Throne. ## Dois jeitos de cobrar mais de uma vez Além da venda única, o mesmo endpoint de criação aceita dois campos opcionais: - **[Upsell em um clique](/docs/cartao/oneclick)**: `one_click_from` cobra um segundo produto sem pedir o cartão de novo, depois de uma venda aprovada. - **[Recorrência](/docs/cartao/recorrencias)**: `recurrence` cria a cobrança que se repete sozinha, no ciclo que você escolher. Os dois dependem da adquirente: a recorrência funciona na Apex e na Vortex, e o upsell em um clique só na Apex. Sem esses campos, nada muda: o checkout é o mesmo de sempre. ## Testar A resposta `201` só repete a moeda e o valor que você mandou (`display`); o valor creditado no seu saldo e o `fx_rate` aparecem quando a venda é paga. Como o cartão é processado de verdade, o teste de ponta a ponta (ver a venda `paid` com `currency`, `native_amount_cents` e `fx_rate`) usa um cartão de verdade com valor baixo. Os erros que dá para provocar sem custo estão em [Como testar](/docs/testar), e o roteiro de conferência em [Checklist de verificação](/docs/checklist). ## Erros | Status | `error` | O que fazer | Repetir | |---|---|---|---| | `400` | `idempotency_key_required` | Envie o header `Idempotency-Key` | Sim, depois de corrigir | | `403` | `scope_required` | A chave precisa da permissão `transactions:write` | Com chave nova, depois de corrigir | | `422` | (validação) | Corrija o campo indicado em `errors` | Com chave nova, depois de corrigir | | `422` | `currency_not_supported` | Use uma das 48 moedas | Com chave nova, depois de corrigir | | `422` | `fx_unavailable` | Espere o `Retry-After` | Com chave nova | | `422` | `invalid_amount` | Abaixo do mínimo ou acima do máximo da adquirente, na moeda enviada e pela cotação do dia. Acima do máximo, use `alternatives` ou divida o pedido | Não com o mesmo valor na mesma adquirente | | `422` | `card_not_enabled` | A conta não tem cartão habilitado. Fale com o suporte | Não | | `422` | `invalid_acquirer` | `acquirer` fora da lista. Use `sterling`, `imperium`, `novacorp`, `eminence`, `apex`, `shadowbank` ou `vortex` | Com chave nova, depois de corrigir | | `422` | `acquirer_not_enabled` | A adquirente pedida não está habilitada na conta. A mensagem lista as habilitadas | Com chave nova, depois de corrigir | | `422` | `customer_required_for_shadowbank` | Só na ShadowBank: a criação veio sem `customer.name` ou `customer.email`, com e-mail inválido ou fora dos tamanhos (nome até 120 caracteres, e-mail até 180). Nada é criado | Com chave nova, depois de corrigir | | `422` | `idempotency_key_mismatch` | Mesma `Idempotency-Key` com corpo diferente | Com chave nova | | `409` | `idempotency_key_processing` | A primeira chamada ainda está em andamento | A mesma chamada, igual. Veja [como repetir com segurança](/docs/comecar#como-repetir-uma-chamada-com-seguranca) | | `429` | | Limite de requisições | Com chave nova, depois do `Retry-After` | | `503` | `temporarily_unavailable` | Adquirente em pausa, instável ou sem capacidade. Nada foi criado | Com chave nova, depois do `Retry-After`, ou com outra adquirente | | `502` | `checkout_failed` | O checkout não foi criado. Veja o campo `retryable` | Só com `"retryable": true`, com chave nova | | `500` | | Erro inesperado no servidor | A mesma chamada, igual: se nada foi criado, ela roda de novo. Se vier `409 idempotency_key_processing` por mais de 2 minutos, confira no painel antes de usar uma chave nova | Exemplo de `502`: ```json { "error": "checkout_failed", "message": "Não foi possível criar o checkout.", "retryable": false } ``` --- # Comparar adquirentes > Sterling, Imperium, NovaCorp, Eminence, Apex, ShadowBank e Vortex lado a lado: verificação do banco, caminhos de pagamento, tentativas, limites, eventos e webhook. Fonte: https://app.thronecorporate.com/docs/cartao/comparar ## O que é igual nas seis O endpoint (`POST /elements/checkout`), os campos, o Throne Elements, o formato do webhook `transaction.paid` e a consulta `GET /transactions/{id}` são os mesmos nas seis adquirentes. A cobrança é sempre à vista, em uma parcela, na moeda em que você criou a venda, com a cotação do seu saldo fixada na criação. O que muda é o comportamento do checkout, na tabela abaixo. ## Lado a lado | | Sterling | Imperium | NovaCorp | Eminence | Apex | ShadowBank | Vortex | |---|---|---|---|---|---|---|---| | Valor de `acquirer` | `sterling` | `imperium` | `novacorp` | `eminence` | `apex` | `shadowbank` | `vortex` | | Verificação do banco | Não tem hoje: aprova ou recusa na hora | Não tem: o resultado sai na própria tentativa | Uma janela de verificação; a tela do banco aparece nela quando ele pede | A tela do banco aparece **dentro do próprio elemento** quando ele pede | Não tem: aprova ou recusa na hora | Não tem: aprova ou recusa na hora | Não tem: aprova ou recusa na hora | | No computador | Tudo dentro do elemento | Tudo dentro do elemento | Abre uma janela de verificação; o resultado volta para o elemento | Tudo dentro do elemento; a verificação aparece ali quando pedida | Tudo dentro do elemento | Tudo dentro do elemento | Tudo dentro do elemento | | No celular, em navegador comum | Tudo dentro do elemento | Tudo dentro do elemento | A janela abre como nova aba e fecha quando o resultado sai | Tudo dentro do elemento | Tudo dentro do elemento | Tudo dentro do elemento | Tudo dentro do elemento | | Em navegador de app (Instagram, Facebook, Google, TikTok) | Tudo dentro do elemento | Tudo dentro do elemento | A própria aba vai para a página segura de pagamento e termina lá | Tudo dentro do elemento | Tudo dentro do elemento | Tudo dentro do elemento | Tudo dentro do elemento | | Eventos na sua página | Em todos os caminhos | Em todos os caminhos | No computador e no celular; **nenhum** no navegador de app | Em todos os caminhos | Em todos os caminhos | Em todos os caminhos | Em todos os caminhos | | Tempo do resultado | Poucos segundos | Aprovação em segundos; uma recusa pode levar até 25 segundos | Depende do tempo que o comprador leva na tela do banco | Poucos segundos; mais quando o banco pede verificação | Poucos segundos; mais quando entra em análise antifraude | Poucos segundos; mais quando entra em análise antifraude | Poucos segundos; mais quando entra em análise antifraude | | Tentativas por checkout | 5 | 5 | 6 | 5 | 5 | 5 | 5 | | Validade do checkout | 24 horas | 24 horas | 24 horas | 24 horas | 24 horas | 24 horas | 24 horas | | Fechamento por recusa (`failed`) | Na 5ª recusa, ou 10 minutos depois de uma recusa sem nova tentativa | Na 5ª recusa, ou 10 minutos depois de uma recusa sem nova tentativa | Na 6ª recusa, ou a partir de 10 minutos depois da última recusa, sem nova tentativa e com o comprador fora da página | Na 5ª recusa, ou 10 minutos depois de uma recusa sem nova tentativa. Uma recusa depois da análise antifraude ou de uma confirmação demorada, ou um valor que a Eminence não aceita, fecha na hora | Na 5ª recusa, ou 10 minutos depois de uma recusa sem nova tentativa. Um valor que a Apex não aceita fecha na hora | Na 5ª recusa, ou 10 minutos depois de uma recusa sem nova tentativa | Na 5ª recusa, ou 10 minutos depois de uma recusa sem nova tentativa | | Mínimo por venda | Varia com a moeda e com a cotação do dia, ou o mínimo próprio da adquirente | Varia com a moeda e com a cotação do dia, ou o mínimo próprio da adquirente | Varia com a moeda e com a cotação do dia, ou o mínimo próprio da adquirente | Varia com a moeda e com a cotação do dia, ou o mínimo próprio da adquirente | Varia com a moeda e com a cotação do dia, ou o mínimo próprio da adquirente | Varia com a moeda e com a cotação do dia, ou o mínimo próprio da adquirente | Varia com a moeda e com a cotação do dia, ou o mínimo próprio da adquirente | | Máximo por venda | Varia com a moeda e com a cotação do dia | Varia com a moeda e com a cotação do dia | Varia com a moeda e com a cotação do dia. Hoje é o mais baixo das sete | Varia com a moeda e com a cotação do dia | Varia com a moeda e com a cotação do dia | Varia com a moeda e com a cotação do dia | Varia com a moeda e com a cotação do dia | | Moedas | As 48 da [lista](/docs/cartao/moeda#moedas-aceitas) | As 48 da lista | As 48 da lista | As 48 da lista | As 48 da lista | As 48 da lista | As 48 da lista | | Recorrência | Não | Não | Não | Não | Sim, nos sete ciclos de `weekly` a `yearly` | Não | Sim, nos sete ciclos de `weekly` a `yearly`. A assinatura só começa a valer quando a primeira cobrança aprova | | Upsell em um clique | Não | Não | Não | Não | Sim, com habilitação por conta | Não | Não | | Idioma do checkout | Pelo país do comprador | Pelo país do comprador | Pelo país do comprador; `?nl=` mostra outro idioma para teste | Pelo país do comprador; `?nl=` mostra outro idioma para teste | Pelo país do comprador; `?nl=` mostra outro idioma para teste | Pelo país do comprador; `?nl=` mostra outro idioma para teste | Pelo país do comprador; `?nl=` mostra outro idioma para teste | | Idioma da mensagem de recusa | Pelo header `Accept-Language` da criação: português, inglês ou espanhol | Pelo header `Accept-Language` da criação: português, inglês ou espanhol | Pelo país do comprador, como o resto do checkout | Pelo país do comprador, como o resto do checkout | Pelo país do comprador, como o resto do checkout | Pelo país do comprador, como o resto do checkout | Pelo país do comprador, como o resto do checkout | | Códigos em `throne-elements:error` | `declined`, `attempts_exhausted`, `network`, `token`, `expired`, `ended` | Em `message`, sem `code`: `network`, `expired` e `ended`. Uma recusa chega como o texto mostrado ao comprador, inclusive a última; `attempts_exhausted` só ao abrir um checkout já fechado | `declined`, `attempts_exhausted`, `closed`, `window_closed`, `canceled`, `popup_blocked`, `timeout`, `network`, `expired`, `ended` | `declined`, `attempts_exhausted`, `token`, `network`, `timeout`, `limit`, `expired`, `ended` | `declined`, `attempts_exhausted`, `token`, `network`, `timeout`, `limit`, `expired`, `ended` | `declined`, `attempts_exhausted`, `token`, `network`, `timeout`, `expired`, `ended` | `declined`, `attempts_exhausted`, `token`, `network`, `timeout`, `expired`, `ended` | | Pagamento em análise | Pode acontecer (análise antifraude); a venda fica `pending` até a decisão | Pode aparecer enquanto a confirmação sai; a venda fica `pending` | Pode aparecer enquanto a confirmação sai; a venda fica `pending` | Pode acontecer (análise antifraude, ou confirmação demorada); a venda fica `pending` até a decisão | Pode acontecer (análise antifraude); a venda fica `pending` até a decisão. Reprovado conta como uma recusa | Pode acontecer (análise antifraude); a venda fica `pending` até a decisão. Reprovado conta como uma recusa | Pode acontecer (análise antifraude); a venda fica `pending` até a decisão. Reprovado conta como uma recusa | | Estorno | Pelo suporte. Total vira `refunded`; parcial mantém `paid` com valores menores | Pelo suporte. Vira `refunded` | Pelo suporte. Vira `refunded` | Pelo suporte. Total vira `refunded`; parcial mantém `paid` com valores menores | Pelo suporte. Total vira `refunded`; parcial mantém `paid` com valores menores | Pelo suporte. Total vira `refunded`; parcial mantém `paid` com valores menores | Pelo suporte. Total vira `refunded`; parcial mantém `paid` com valores menores | | Contestação | Vira `chargeback` e o valor líquido sai da carteira | Vira `chargeback`; se revertida a seu favor, volta a `paid` | Vira `chargeback`; se revertida a seu favor, volta a `paid` | Vira `chargeback` e o valor líquido sai da carteira; se revertida a seu favor, volta a `paid` | Vira `chargeback` e o valor líquido sai da carteira; se revertida a seu favor, volta a `paid` | Vira `chargeback` e o valor líquido sai da carteira; se revertida a seu favor, volta a `paid` | Vira `chargeback` e o valor líquido sai da carteira; se revertida a seu favor, volta a `paid` | | Quando chega o webhook | Logo depois da aprovação, na maior parte das vendas | Logo depois da aprovação, na maior parte das vendas | Logo depois da aprovação quando o comprador volta da tela do banco; se ele sair antes, pela conferência automática, alguns minutos depois | Logo depois da aprovação; quando o banco pede verificação, depois que o comprador a conclui | Logo depois da aprovação; numa venda em análise, quando a análise aprova | Logo depois da aprovação; numa venda em análise, quando a análise aprova | Logo depois da aprovação; numa venda em análise, quando a análise aprova | | Guia | [Sterling](/docs/cartao/sterling) | [Imperium](/docs/cartao/imperium) | [NovaCorp](/docs/cartao/novacorp) | [Eminence](/docs/cartao/eminence) | [Apex](/docs/cartao/apex) | [ShadowBank](/docs/cartao/shadowbank) | [Vortex](/docs/cartao/vortex) | O máximo por venda de cada adquirente, já na moeda do seu público, está em `GET /elements/acquirers?currency=...`. Acima dele, a resposta é `422 invalid_amount` com `max_amount_cents` e `alternatives`. Veja [Escolher a adquirente](/docs/cartao/escolher#quando-o-valor-passa-do-maximo). Recorrência e upsell em um clique são campos opcionais do mesmo endpoint de criação: veja [Recorrência](/docs/cartao/recorrencias) e [Upsell em um clique](/docs/cartao/oneclick). ## Qual escolher - **Público que chega por anúncio em app (Instagram, Facebook, TikTok)**: Sterling, Imperium, Eminence, Apex e ShadowBank mantêm o comprador dentro da sua página e os eventos chegam sempre. Na NovaCorp, envie a conversão pelo servidor. - **Tíquete alto**: confira o máximo de cada adquirente na moeda do seu público antes de escolher. Com mais de uma habilitada, crie as vendas altas com o `acquirer` de uma que aceite o valor. - **Banco que pede verificação**: a NovaCorp abre a tela do banco numa janela; a Eminence mostra a verificação dentro do próprio elemento. Sterling, Imperium, Apex e ShadowBank não passam por essa etapa: o resultado sai na própria tentativa. Recusa, expiração, estorno e contestação não geram webhook em nenhuma das seis. Acompanhe pelo `GET /transactions/{id}`. Os status estão em [Status e ciclo de vida](/docs/cartao/status). --- # Escolher a adquirente > O campo acquirer, as adquirentes habilitadas na conta, os limites de cada uma e o que fazer quando o valor passa do máximo. Fonte: https://app.thronecorporate.com/docs/cartao/escolher ## O campo acquirer Uma conta pode ter mais de uma adquirente de cartão habilitada. Nesse caso, cada site (source) escolhe a dela com o campo `acquirer` no `POST /elements/checkout`. Assim a mesma conta, com a mesma chave, vende pela Sterling num site, pela NovaCorp em outro, pela Eminence num terceiro, pela Apex num quarto e pela ShadowBank num quinto. ```bash curl -X POST https://app.thronecorporate.com/api/v1/elements/checkout \ -H "Authorization: Bearer $THRONE_PK:$THRONE_SK" \ -H "Content-Type: application/json" \ -H "Accept: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "amount_cents": 4990, "currency": "chf", "acquirer": "novacorp" }' ``` - **Sem o campo**, vale a adquirente padrão da conta, como sempre foi. Integrações que já estão no ar continuam iguais. A única exceção é o [roteamento por valor](#roteamento-por-valor), que vem desligado. - Os valores são `sterling`, `imperium`, `novacorp`, `eminence`, `apex`, `shadowbank` e `vortex`, em minúsculas. - Só vale uma adquirente **habilitada na sua conta**. Se pedir outra, a chamada volta `422 acquirer_not_enabled` com a lista das habilitadas e nada é criado. - **Com o campo, o Throne nunca troca a adquirente que você pediu por outra.** Se ela não aceitar o valor ou estiver indisponível, a resposta é um erro, e você decide o que fazer. - Valor fora da lista: `422 invalid_acquirer`. - A resposta traz `acquirer` com a adquirente que ficou com a venda. A venda fica nela até o fim: pagamento, estorno e contestação. O `GET /transactions/{id}` também traz o `acquirer`. ## Adquirentes habilitadas e limites As adquirentes habilitadas na sua conta aparecem no painel, em **Documentação da API**, e também na API: ```bash curl "https://app.thronecorporate.com/api/v1/elements/acquirers?currency=chf" \ -H "Authorization: Bearer $THRONE_PK:$THRONE_SK" \ -H "Accept: application/json" ``` ```json { "data": [ { "acquirer": "sterling", "name": "Sterling", "default": true, "available": true, "limits": { "currency": "CHF", "min_amount_cents": 80, "max_amount_cents": 500000 } }, { "acquirer": "novacorp", "name": "NovaCorp", "default": false, "available": true, "limits": { "currency": "CHF", "min_amount_cents": 80, "max_amount_cents": 31890 } }, { "acquirer": "eminence", "name": "Eminence", "default": false, "available": true, "limits": { "currency": "CHF", "min_amount_cents": 80, "max_amount_cents": 500000 } }, { "acquirer": "apex", "name": "Apex", "default": false, "available": true, "limits": { "currency": "CHF", "min_amount_cents": 80, "max_amount_cents": 500000 } }, { "acquirer": "shadowbank", "name": "ShadowBank", "default": false, "available": true, "limits": { "currency": "CHF", "min_amount_cents": 80, "max_amount_cents": 500000 } } ] } ``` Os números acima são só um exemplo. | Campo | O que é | |---|---| | `acquirer` | O valor para mandar no campo `acquirer` do checkout | | `name` | O nome para mostrar | | `default` | `true` na adquirente que vale quando o campo não é enviado | | `available` | `false` quando a adquirente está em pausa ou indisponível no momento. Criar nela volta `503 temporarily_unavailable` | | `limits` | Só com `?currency=`. Mínimo e máximo por venda **na moeda pedida**, pela cotação do momento. Vem nulo quando a cotação dessa moeda está indisponível agora | Os limites são indicativos: a cotação muda ao longo do dia, e a criação confere o valor de novo na hora. Moeda fora da lista volta `422 currency_not_supported`. A chave precisa da permissão `transactions:write`, a mesma de criar o checkout. Lista vazia: a conta não tem cartão habilitado; fale com o suporte. ## Quando o valor passa do máximo Cada adquirente tem um máximo por venda, que varia com a moeda e com a cotação do dia. Acima dele, nada é criado e a resposta é: ```json { "error": "invalid_amount", "message": "Valor acima do máximo aceito nesta adquirente: CHF 318,90.", "max_amount_cents": 31890, "currency": "CHF", "alternatives": ["sterling"] } ``` - `max_amount_cents` é o máximo na moeda que você mandou, pela cotação do momento. - `alternatives` lista as outras adquirentes habilitadas na sua conta que aceitam esse valor. Lista vazia: nenhuma aceita. - **Não repita o mesmo valor na mesma adquirente.** Crie com o `acquirer` de uma das `alternatives`, ou divida o pedido em vendas menores. Abaixo do mínimo, a resposta é a mesma `422 invalid_amount`, com o mínimo na sua moeda no `message` e em `min_amount_cents`. Em casos raros a adquirente recusa um valor acima do máximo que ela ainda não tinha informado. A resposta é a mesma `422 invalid_amount`, sem `max_amount_cents`: trate igual, com as `alternatives`. ## Roteamento por valor Sem o campo `acquirer`, a venda vai para a adquirente padrão. Contas com o **roteamento por valor** ligado têm uma regra a mais: se o valor passa do máximo da padrão, o Throne cria a venda na próxima adquirente habilitada que aceita o valor. - O roteamento vem desligado. Para ligar, fale com o suporte. Taxa e perfil de aprovação mudam entre adquirentes. - A resposta traz em `acquirer` a adquirente que ficou com a venda. Guarde junto do `transaction_id`. - Com o campo `acquirer`, o roteamento não vale: o Throne nunca troca a adquirente pedida. ## Adquirente em pausa ou sem capacidade Uma adquirente pode ficar indisponível por alguns minutos: pausa de manutenção, instabilidade ou pico de criação. Nesse caso, a criação responde: ```json { "error": "temporarily_unavailable", "message": "A adquirente novacorp está temporariamente indisponível. Tente em instantes ou use outra habilitada: sterling.", "retryable": true } ``` com o header `Retry-After` em segundos. Nada foi criado. Espere o `Retry-After` e repita com uma `Idempotency-Key` nova, ou crie com o `acquirer` de outra adquirente habilitada. Uma adquirente em pausa ou indisponível aparece em `GET /elements/acquirers` com `available: false`. ## Webhook e consulta O webhook e a consulta são iguais nas seis. Para saber em qual adquirente uma venda foi feita, use o `acquirer` da resposta de criação ou do `GET /transactions/{id}`. --- # Moedas e limites > As 48 moedas aceitas, o mínimo e o máximo por moeda, como a cotação do dia muda os limites e como o seu saldo é creditado. Fonte: https://app.thronecorporate.com/docs/cartao/moeda ## Como funciona Você cria a venda **na moeda do comprador** e ele paga nessa moeda: o checkout mostra o valor só nela, e você não mexe no valor no seu site. O saldo da sua conta no Throne é em reais. Por isso, na criação da venda, o Throne fixa a cotação que vai creditar o seu saldo e **não muda mais essa cotação**, mesmo que o comprador pague horas depois. Ela volta como `fx_rate` quando a venda é paga. Vale igual para Sterling, Imperium, NovaCorp, Eminence, Apex, ShadowBank e Vortex. ## Moedas aceitas São **48 moedas**: ```text brl usd eur gbp chf aud cad nzd jpy sgd hkd mxn ars clp cop pen uyu pyg bob dop gtq crc pab aed sar zar nok sek dkk pln czk huf ron try ils krw inr cny thb myr idr php twd vnd ngn egp kes mad ``` - `currency` aceita maiúsculas ou minúsculas. - Moedas **sem casa decimal**: JPY, KRW, VND, CLP, HUF, TWD e PYG. Nelas, `amount_cents` é a unidade inteira (¥ 5000 = `5000`), sem multiplicar por 100. - Moeda fora da lista: `422 currency_not_supported`. - Cotação momentaneamente indisponível: `422 fx_unavailable`, com o header `Retry-After: 60`. Nada foi criado; espere e tente de novo com uma `Idempotency-Key` nova. ## Mínimo e máximo O mínimo e o máximo de uma venda dependem da adquirente e **variam com a moeda e com a cotação do dia**: o mesmo limite fica um pouco diferente de um dia para o outro na moeda do comprador. - **Mínimo**: baixo, e algumas adquirentes têm um mínimo próprio, maior. Abaixo dele, `422 invalid_amount` com o mínimo já escrito na moeda que você mandou (por exemplo, "Valor abaixo do mínimo aceito: CHF 0,80.") e no campo `min_amount_cents`. - **Máximo**: depende da adquirente. Acima dele, `422 invalid_amount` com `max_amount_cents` na moeda que você mandou e `alternatives` com as outras adquirentes habilitadas que aceitam o valor. - **Limite do campo**: `amount_cents` vai de 50 a 100.000.000 na menor unidade da moeda. Fora disso, erro de campo (`422` com `errors.amount_cents`). Em moedas de valor baixo (como IDR, COP, VND e ARS), esse limite do campo pode chegar antes do máximo da adquirente. Para saber o mínimo e o máximo de hoje, já na moeda do seu público: ```bash curl "https://app.thronecorporate.com/api/v1/elements/acquirers?currency=chf" \ -H "Authorization: Bearer $THRONE_PK:$THRONE_SK" \ -H "Accept: application/json" ``` Cada adquirente da lista traz `limits.min_amount_cents` e `limits.max_amount_cents` em francos. Detalhes em [Escolher a adquirente](/docs/cartao/escolher#adquirentes-habilitadas-e-limites). ## Como a cotação muda o máximo O máximo de cada adquirente acompanha a cotação do dia. Na moeda do comprador, ele sobe ou desce um pouco de um dia para o outro. Na prática: - Um produto de CHF 300,00 pode ficar abaixo do máximo de uma adquirente numa semana e acima dele na outra, sem você mudar nada. - Preços longe do máximo não sentem a diferença. - Para tíquetes altos, consulte `GET /elements/acquirers?currency=` uma vez por dia, ou trate o `422 invalid_amount` criando com o `acquirer` de uma das `alternatives`. ## Exemplo em franco suíço O comprador paga CHF 49,90: você manda `amount_cents: 4990` e `currency: "chf"`, e é esse valor, em francos, que ele vê no botão de pagar. Na venda paga você recebe: | Campo | Valor | O que é | |---|---|---| | `currency` | `"CHF"` | A moeda da venda, a do comprador | | `native_amount_cents` | `4990` | A venda na moeda do comprador | | `amount_cents` | `31237` | O valor bruto creditado no seu saldo, em reais (a moeda da sua conta) | | `fx_rate` | `"6.26000000"` no webhook, `6.26` no `GET` | A cotação usada para creditar o seu saldo, fixada na criação: reais por 1 unidade da moeda da venda | `net_amount_cents` é o que entra de fato, já descontada a taxa de cartão da sua conta. ## O que o comprador vê e paga - O checkout mostra o valor só na moeda do comprador (por exemplo, em francos no botão de pagar), e é nessa moeda que ele paga. - Na fatura do cartão, a compra aparece sem o nome da sua loja. - Na NovaCorp e na Eminence, o banco do comprador pode pedir uma confirmação da compra (código por SMS, aplicativo do banco). Na NovaCorp ela abre numa página segura; na Eminence, dentro do próprio elemento. Vale avisar isso ao lado do botão de pagar, para o comprador não estranhar. Textos sugeridos estão nos guias da [NovaCorp](/docs/cartao/novacorp#o-que-o-comprador-ve) e da [Eminence](/docs/cartao/eminence#o-que-o-comprador-ve). ## No seu saldo A venda entra no seu saldo em reais, como saldo a liberar, já descontada a taxa de cartão da sua conta. A resposta `201` só repete a moeda e o valor que você mandou (`display`). `amount_cents`, `net_amount_cents` e `fx_rate` aparecem quando a venda é paga, no webhook `transaction.paid` e em `GET /transactions/{id}`. --- # Throne Elements > Atributos do elemento, eventos com código estável, sandbox, página de uma tela só e navegador de app. Fonte: https://app.thronecorporate.com/docs/cartao/elements ## O que é O Throne Elements é o checkout de cartão embutido na sua página. Você cria o checkout no seu servidor com `POST /elements/checkout` (ou com `POST /elements/checkout/br`, o [Checkout Brasil](/docs/cartao/brasil)) e coloca o `transaction_id` num container. O script do Throne monta o elemento ali dentro. O comprador digita o cartão no elemento e o seu servidor nunca toca no número do cartão. O mesmo elemento serve para as seis adquirentes. O que muda entre elas está em [Comparar adquirentes](/docs/cartao/comparar). ## Colocar na página ```html
``` | Atributo | O que faz | |---|---| | `data-throne-elements` | Marca o container. Pode haver mais de um na página | | `data-id` | O `transaction_id` da criação. Precisa ser o UUID exato, senão nada aparece | | `data-color` | Cor do botão e dos realces, no formato `#rrggbb` | | `data-theme` | `light` ou `dark`. O elemento também detecta sozinho a cor de fundo do seu site e se ajusta a ela | | `data-radius` | Arredondamento dos cantos, de `0` a `32` (px). Padrão `10` | > [!NOTE] > No Checkout Brasil, a criação também pode pedir o texto do botão, as cores, o formato e uma moldura pelo objeto `appearance`, e o que vier nele vence `data-color` e `data-radius`. Veja [Aparência](/docs/cartao/brasil#aparencia). ## Quando criar o checkout - Crie no clique de compra, não quando a página carrega. Assim você não gasta checkout com quem só olhou a página. - Guarde o `transaction_id` na sessão do comprador. Se ele voltar à página em até 24 horas, mostre o mesmo checkout em vez de criar outro. - O mesmo checkout aceita várias tentativas de cartão. Crie um novo só depois de `failed` ou `expired`. ## Boas práticas - Carregue o script **do endereço acima**. Não hospede uma cópia: o elemento depende do endereço de origem do script. - Não fixe a altura do container nem esconda o overflow. O elemento ajusta a própria altura conforme a tela do checkout. - Não coloque o elemento dentro de outro iframe com `sandbox`. Se não der para tirar o `sandbox`, ele precisa incluir no mínimo `allow-scripts allow-same-origin allow-forms allow-popups allow-popups-to-escape-sandbox allow-top-navigation-by-user-activation`. Sem isso, a verificação do banco e o pagamento em navegador de app não funcionam em algumas adquirentes. - O pagamento precisa começar pelo clique do comprador no botão do elemento. Não dispare o pagamento por código. - Se a sua página envia o header `Cross-Origin-Opener-Policy: same-origin`, a janela de verificação do banco pode perder o contato com o elemento. Sem esse header, nada a fazer. Com ele, teste um pagamento completo no computador e no celular antes de publicar. - Se o seu site usa Content-Security-Policy, libere `https://app.thronecorporate.com` em `script-src` (o script) e em `img-src`, e em `frame-src` libere esse domínio e o domínio do checkout (o mesmo do `checkout_url` da resposta). ## Página de uma tela só (React, Vue e outras) O script monta os containers que existem quando ele carrega. Se você inserir o container depois, chame: ```js window.ThroneElements.mount(); ``` Chame de novo sempre que inserir um container novo. Um container já montado não muda: trocar o `data-id` dele e chamar `mount()` não troca o checkout, e o elemento continua mostrando o anterior, com o valor antigo. Para mostrar outro checkout, remova o container antigo, insira um novo com o `data-id` novo e chame `mount()`. Em React, use o `transaction_id` como `key` do container (`
`) e chame `mount()` num `useEffect` que depende do `transactionId`: o React cria um container novo a cada checkout. ## Eventos na sua página ```js window.addEventListener('throne-elements:ready', () => { // elemento montado, pronto para receber o cartão }); window.addEventListener('throne-elements:paid', (e) => { // e.detail.transaction_id: o comprador concluiu. Mostre o "obrigado". }); window.addEventListener('throne-elements:error', (e) => { const code = e.detail.code || e.detail.message; // code é fixo; message é texto // decida pelo code; mostre o texto só como apoio }); ``` Também funcionam as funções `window.ThroneElements.onready`, `onpaid` e `onerror`, que recebem o mesmo objeto de `e.detail`. Os eventos são **sinais de tela**. A confirmação que vale para liberar o pedido é o webhook `transaction.paid` ou o `GET /transactions/{id}` com `status: "paid"`. O elemento pode terminar sem `paid` mesmo numa venda aprovada: o comprador pode fechar a página, a conexão pode cair ou o pagamento pode seguir por outro caminho, como o do navegador de app abaixo. ### Códigos de erro Em `throne-elements:error`, decida pelo campo `e.detail.code`. Ele é um código fixo, em inglês, que não muda com o idioma do comprador. O campo `e.detail.message` traz o texto mostrado ao comprador, no idioma dele, e serve só para log. Se `code` não vier, use `message` quando ele for um dos códigos abaixo e trate qualquer outro texto como `declined`. | `code` | Significado | Sterling | Imperium | NovaCorp | Eminence | Apex | ShadowBank | Vortex | |---|---|---|---|---|---|---|---|---| | `declined` | Cartão recusado. O comprador pode tentar de novo | Sim | Sim | Sim | Sim | Sim | Sim | Sim | | `attempts_exhausted` | Acabaram as tentativas deste checkout | Sim | Em parte (veja abaixo) | Sim | Sim | Sim | Sim | Sim | | `closed` | O checkout fechou por recusa enquanto a página estava aberta | Não | Não | Sim | Não | Não | Não | Não | | `window_closed` | O comprador fechou a janela de verificação sem concluir | Não | Não | Sim | Não | Não | Não | Não | | `canceled` | O comprador cancelou a verificação do banco | Não | Não | Sim | Não | Não | Não | Não | | `popup_blocked` | A janela de verificação fechou logo depois de abrir, antes do envio do cartão. Nada foi cobrado e o comprador pode tentar de novo | Não | Não | Sim | Não | Não | Não | Não | | `timeout` | A confirmação demorou mais que o esperado. O resultado final chega pelo webhook ou pela consulta | Não | Não | Sim | Sim | Sim | Sim | Sim | | `limit` | A adquirente não aceita o valor desta venda no cartão. O checkout fechou | Não | Não | Não | Sim | Sim | Não | Não | | `network` | Falha de conexão do comprador | Sim | Sim | Sim | Sim | Sim | Sim | Sim | | `token` | Os dados do cartão não passaram na validação inicial. Não consome tentativa | Sim | Não | Não | Sim (veja abaixo) | Sim | Sim | Sim | | `expired` | O checkout passou da validade | Sim | Sim | Sim | Sim | Sim | Sim | Sim | | `ended` | O checkout foi encerrado | Sim | Sim | Sim | Sim | Sim | Sim | Sim | Uma janela de verificação bloqueada na abertura não gera `popup_blocked`: na NovaCorp, o pagamento segue pela própria aba, como no [navegador de app](#navegador-de-app). Na Eminence, a verificação do banco aparece dentro do próprio elemento, sem janela, e o comprador pode sair dela pelo botão Cancelar. O Cancelar não gera evento na hora: o elemento espera o banco encerrar a verificação pendente e então envia `declined` ou `network`; se isso não acontecer em alguns minutos, envia `timeout`. `token` também chega quando a Eminence recusa os dados do cartão depois do envio; nesse caso a tentativa é contada. `limit` fecha o checkout como `failed`: crie um checkout novo com outra adquirente habilitada. Na Apex não há verificação do banco: o resultado sai na própria tentativa, dentro do elemento. `timeout` chega quando a análise antifraude demora; o resultado final vem pelo webhook ou pela consulta. `token` chega quando os dados do cartão não passam na validação, antes da cobrança, e não consome tentativa. `limit` fecha o checkout como `failed`, como na Eminence: crie um checkout novo com outra adquirente habilitada. Na ShadowBank também não há verificação do banco: o resultado sai na própria tentativa, dentro do elemento. `timeout` chega quando a análise antifraude demora; o resultado final vem pelo webhook ou pela consulta. `token` chega quando os dados do cartão não passam na validação, antes da cobrança, e não consome tentativa. A ShadowBank não envia `limit`. Na Imperium, `e.detail` não traz `code`, só `message`. Por isso o exemplo acima lê `e.detail.code || e.detail.message`. Falha de conexão, validade e encerramento chegam como `network`, `expired` e `ended`. Numa recusa durante o pagamento, inclusive a última, `message` traz o texto mostrado ao comprador, no idioma dele, e não diz se ainda há tentativa: `attempts_exhausted` só chega quando o elemento abre num checkout que já fechou. Trate esse texto como `declined` e deixe o próprio elemento mostrar ao comprador se ele pode tentar de novo. Para saber se o checkout fechou, consulte `GET /transactions/{id}`: com `failed`, crie um checkout novo. ## Navegador de app Instagram, Facebook, o app do Google, TikTok e outros apps abrem links num navegador próprio. Esses navegadores não abrem uma segunda janela. - **Sterling, Imperium, Eminence, Apex, ShadowBank e Vortex**: nada muda. O pagamento acontece todo dentro do elemento e os eventos chegam normalmente. - **NovaCorp**: a própria aba sai da sua página e vai para a página segura de pagamento. O comprador passa pela tela do banco e termina lá. A sua página **não recebe** `throne-elements:paid` nem outro evento, e o comprador não volta sozinho para a sua loja. O mesmo acontece quando um navegador comum bloqueia a janela de verificação. A venda continua sendo confirmada normalmente pelo webhook `transaction.paid` e pelo `GET`. Por isso: - libere o pedido pelo webhook, nunca só pelo evento da página; - envie a conversão da campanha pelo seu servidor ao receber `transaction.paid`, como em [Webhooks](/docs/webhooks#conversao-de-campanha-pelo-servidor); - mande o e-mail de confirmação do pedido pelo servidor, porque o comprador pode não ver a sua página de obrigado. Se o comprador voltar à sua página pelo botão voltar, o elemento pode montar de novo já pago e disparar `throne-elements:paid`. Isso não é garantido: não dependa disso. ## Idioma O checkout sai no idioma do país do comprador, identificado pelo IP. Compradores no Brasil, em Portugal ou de país não identificado veem em inglês. O idioma não é configurado pela API. Detalhes por adquirente em [Comparar adquirentes](/docs/cartao/comparar). No [Checkout Brasil](/docs/cartao/brasil), a página sai sempre em português. --- # Checkout Brasil > Checkout de cartão para o comprador brasileiro: página sempre em português, CPF pedido na tela, comprador de qualquer país e botão e moldura ajustáveis pela API, com o mesmo Throne Elements e o mesmo webhook. Fonte: https://app.thronecorporate.com/docs/cartao/brasil ## O que é O Checkout Brasil é o checkout de cartão para o comprador brasileiro. Você cria o checkout com `POST /elements/checkout/br` e coloca o `transaction_id` no mesmo Throne Elements que já usa. O que muda está na página que o comprador vê: ela sai **sempre em português**, pede o **CPF** junto com o cartão e abre para comprador de qualquer país. O nome e o CPF que o comprador digita vão para a adquirente junto com o cartão, na autorização. São os dados que o emissor de um cartão brasileiro espera receber numa compra. > [!NOTE] > Nada a mudar no que já está no ar. O `POST /elements/checkout` continua igual, com a mesma resposta e o mesmo webhook. O Checkout Brasil é um endpoint a mais, para a página do seu funil que vende ao comprador brasileiro. ## Antes de começar O Checkout Brasil funciona **nas adquirentes habilitadas para o Checkout Brasil na sua conta**. As adquirentes de cartão da sua conta estão em `GET /elements/acquirers`, mas nem toda adquirente atende o Checkout Brasil: se nenhuma das suas atender, a criação responde `422 acquirer_not_enabled` e nada é criado. Para pedir a habilitação, fale com o suporte. - A chave de API é a mesma, com a permissão `transactions:write`. Nenhuma chave precisa ser gerada de novo. - O comprador precisa ter CPF. Sem um CPF válido, o pagamento não sai da página. Para o comprador sem CPF, use o checkout comum. - A `Idempotency-Key`, o timeout de 60 segundos e a regra de repetição são os de sempre. Veja [Comece aqui](/docs/comecar#como-repetir-uma-chamada-com-seguranca). ## Criar A criação fala com a adquirente. Use **60 segundos** de timeout no seu cliente HTTP e gere a `Idempotency-Key` antes de chamar, guardando junto do pedido. Use uma chave nova, nunca a de uma chamada ao `POST /elements/checkout`: a chave devolve a resposta da chamada em que foi usada. ```bash curl -X POST https://app.thronecorporate.com/api/v1/elements/checkout/br \ --max-time 60 \ -H "Authorization: Bearer $THRONE_PK:$THRONE_SK" \ -H "Content-Type: application/json" \ -H "Accept: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "amount_cents": 9700, "currency": "brl", "customer": { "name": "Maria Souza", "email": "maria@exemplo.com" } }' ``` `201 Created`: ```json { "transaction_id": "c4d7a2b1-6e3f-4a9c-8b5d-2f1e0a9c7b63", "status": "pending", "checkout_url": "https://pay.example.com/checkout/5a4b3c2d1e0f99887766554433221100", "display": { "currency": "BRL", "amount_cents": 9700 }, "expires_at": "2026-10-20T14:20:00Z" } ``` A resposta é a mesma do checkout comum: `transaction_id`, `status`, `checkout_url`, `acquirer` (a adquirente que ficou com a venda), `display`, `expires_at` e, quando houver aviso, `warnings`. Veja [a resposta do checkout comum](/docs/cartao#resposta). O que é próprio do Checkout Brasil está em [Referência dos campos](#referencia-dos-campos). Na sua página, nada muda: ```html
``` O elemento monta a página em português, com o campo de CPF junto dos dados do cartão. O comprador digita o cartão, o nome e o CPF, e paga. Os eventos `throne-elements:ready`, `throne-elements:paid` e `throne-elements:error` chegam como sempre. A confirmação que vale para liberar o pedido continua sendo o webhook `transaction.paid` ou o `GET /transactions/{id}` com `status: "paid"`. ## O que muda em relação ao checkout comum | Item | Checkout comum | Checkout Brasil | |---|---|---| | Endpoint | `POST /elements/checkout` | `POST /elements/checkout/br` | | Idioma da página | Pelo país do comprador | Sempre português, seja qual for o país do comprador, o idioma do navegador ou o `?nl=` no `checkout_url` | | CPF | Não pede | Pede na tela. Obrigatório e conferido antes do envio | | País do comprador | Segue o país do comprador | Qualquer país: a página abre e o pagamento vai para a adquirente seja qual for o país do comprador ou do cartão | | Adquirente | A pedida em `acquirer` ou a padrão da conta | A habilitada para o Checkout Brasil na conta. `acquirer` é opcional e só aceita uma que atenda o Checkout Brasil. O roteamento fixado na conta e o roteamento por valor não valem aqui | | `one_click_from` e `recurrence` | Aceitos | Não existem: `422 field_not_supported` | | Aparência da página | `data-color`, `data-theme` e `data-radius` do elemento | Os mesmos, mais o objeto `appearance` na criação: texto do botão, valor no botão, cores, formato, tamanho e moldura. Veja [Aparência](#aparencia) | | Throne Elements, eventos, moedas, limites, tentativas, validade de 24 horas, status e webhook | Iguais | Iguais | O comportamento da página depois do clique em pagar (verificação do banco, tentativas, códigos em `throne-elements:error`) é o da adquirente que ficou com a venda, como no checkout comum: veja o guia dela em [Comparar adquirentes](/docs/cartao/comparar). ## Aparência Você pode ajustar a página do Checkout Brasil na própria criação, com o objeto `appearance`: o texto do botão, se o valor aparece nele, as cores, o arredondamento, a altura e a largura do botão, e uma moldura em volta dos campos. Tudo é opcional. O que você não mandar segue o padrão e os atributos `data-color` e `data-radius` do elemento, como hoje. ```bash curl -X POST https://app.thronecorporate.com/api/v1/elements/checkout/br \ --max-time 60 \ -H "Authorization: Bearer $THRONE_PK:$THRONE_SK" \ -H "Content-Type: application/json" \ -H "Accept: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "amount_cents": 9700, "currency": "brl", "appearance": { "button": { "text": "Finalizar compra", "background_color": "#15803d" }, "fields": { "radius": 8 }, "frame": { "border_width": 1, "padding": 24, "shadow": "md" } } }' ``` O botão sai "Finalizar compra R$ 97,00" em verde, com o texto em branco escolhido sozinho para ficar legível, e os campos ficam dentro de uma moldura com borda e sombra. | Campo | Aceita | Padrão | O que faz | |---|---|---|---| | `button.text` | Texto de 2 a 32 caracteres: letras, com ou sem acento, espaço e `! ? , - '`, com pelo menos uma letra | `Pagar` | Texto do botão. Número, símbolo de moeda e as palavras da lista abaixo não entram: o valor aparece sozinho | | `button.show_amount` | `true` ou `false` | `true` | Mostra o valor depois do texto. Com `false`, o botão mostra só o texto e o valor aparece fora dele: logo acima do botão, ou no topo do formulário quando a venda é criada em outra moeda | | `button.background_color` | Cor `#rrggbb` | O `data-color` | Cor do botão | | `button.text_color` | Cor `#rrggbb`, só junto de `background_color` | Escolhida sozinha | Cor do texto do botão. O contraste com o fundo precisa ser de 3:1 ou mais | | `button.radius` | Inteiro de `0` a `32` | O dos campos | Arredondamento do botão, em px. Com a metade da altura, o botão vira pílula | | `button.height` | Inteiro de `44` a `64` | `48` | Altura mínima do botão, em px. Texto longo quebra a linha e o botão cresce | | `button.full_width` | `true` ou `false` | `true` | Com `false`, o botão fica do tamanho do texto, centralizado, com no mínimo 200 px | | `fields.focus_color` | Cor `#rrggbb` | `button.background_color`, senão o `data-color` | Cor de destaque: a borda do campo em foco, o contorno de foco do botão e o indicador da tela de espera | | `fields.radius` | Inteiro de `0` a `32` | O `data-radius`, senão `10` | Arredondamento dos campos, em px | | `frame.max_width` | Inteiro de `320` a `720` | `460` | Largura máxima do checkout, em px | | `frame.padding` | Inteiro de `0` a `40` | `0` | Espaço entre a moldura e os campos, em px. Em tela de até 400 px, no máximo 6 | | `frame.border_width` | Inteiro de `0` a `3` | `0` | Espessura da borda da moldura. Com só `border_color`, vale `1`. Em tela de até 400 px, no máximo 1 | | `frame.border_color` | Cor `#rrggbb` | A cor das linhas dos campos | Cor da borda | | `frame.radius` | Inteiro de `0` a `32` | `12` | Arredondamento da moldura | | `frame.shadow` | `none`, `sm`, `md` ou `lg` | `none` | Sombra da moldura. Em site escuro, prefira a borda: a sombra quase não aparece | Como a validação funciona: - Mande JSON com os tipos certos: `48`, não `"48"`; `false`, não `"false"`. Uma chave com `null` vale como ausente. - Chave que não existe, valor fora da regra ou combinação impossível: `422 invalid_appearance` e nada é criado. A resposta traz `field` com o caminho da chave, por exemplo `appearance.button.text`. - O texto do botão não aceita, em maiúsculas ou minúsculas, as palavras real, reais, centavo, centavos, grátis, gratis, gratuito, gratuita, free, zero, cancelar, voltar, fechar e sair: o botão sempre paga o valor da venda. - Contraste: `text_color` com contraste menor que 3:1 sobre `background_color` é recusado. De 3:1 até 4,5:1 é aceito, com o aviso `low_contrast` em `warnings`. Branco sobre `#16a34a` dá 3,29:1 (aceito, com aviso); branco sobre `#15803d`, 5,01:1. - `fields.focus_color` que quase some sobre fundo claro ou escuro também gera `low_contrast`. - A moldura precisa deixar pelo menos 256 px para os campos: `frame.max_width`, menos 32 (ou 56 com `shadow: lg`), menos duas vezes a soma de `frame.padding` e `frame.border_width`. Quem vence quando você também usa os atributos do elemento: | Aspecto | Sem a chave em `appearance` | Com a chave | |---|---|---| | Cor do botão | `data-color` | `button.background_color` | | Cor de destaque dos campos | `data-color` | `fields.focus_color`, senão `button.background_color` | | Arredondamento | `data-radius`, nos campos e no botão | `fields.radius` e `button.radius` | | Tema e cor de fundo | `data-theme` e a detecção do fundo do seu site | Os mesmos: `appearance` não muda | As chaves que você manda ficam gravadas na criação e não mudam pela URL do checkout; as que você não manda seguem os atributos do elemento. Para mudar o que foi gravado, crie um checkout novo. O que não muda: os nomes e os exemplos dos campos (número do cartão, nome no cartão, validade, CVV e CPF), a frase de segurança abaixo do botão, os textos de processamento, de verificação do banco, de aprovação e de recusa, o botão "Tentar de novo", a fonte e as bandeiras. São os textos que o comprador e o banco esperam ver numa página de pagamento. O botão em cada momento: antes do clique, o seu texto, com ou sem o valor; durante o envio, "Processando", desativado e nas suas cores; recusado, a tela de recusa com "Tentar de novo", e de volta ao formulário o botão volta ao seu texto; aprovado, a tela de pagamento aprovado. ## Erros Os erros do checkout comum valem aqui, com o mesmo significado: `invalid_amount`, `currency_not_supported`, `fx_unavailable`, `card_not_enabled`, `invalid_acquirer`, os de `Idempotency-Key`, `502 checkout_failed` e `503 temporarily_unavailable`. Veja [a tabela do checkout comum](/docs/cartao#erros). O que é próprio do Checkout Brasil: | Status | `error` | `message` | Quando | Repetir | |---|---|---|---|---| | `422` | `acquirer_not_enabled` | O Checkout Brasil não está habilitado para nenhuma adquirente da sua conta. Fale com o suporte. | A conta não tem adquirente habilitada para o Checkout Brasil. Nada foi criado | Não, até habilitar | | `422` | `acquirer_not_enabled` | A adquirente pedida não está habilitada para o Checkout Brasil na sua conta. | O campo `acquirer` pediu uma adquirente que não atende o Checkout Brasil. Nada foi criado | Com chave nova, sem o campo ou com outra adquirente | | `422` | `field_not_supported` | O campo one_click_from não é aceito no Checkout Brasil. Remova o campo e repita a chamada. | O corpo trouxe `one_click_from` ou `recurrence`. Nada foi criado | Com chave nova, sem o campo | | `422` | `invalid_appearance` | appearance.button.color não existe. Em appearance.button valem: text, show_amount, background_color, text_color, radius, height e full_width. | O objeto `appearance` trouxe chave que não existe, valor fora da regra ou combinação impossível. O campo `field` diz qual chave. Nada foi criado | Com chave nova, depois de corrigir | No Checkout Brasil, `invalid_amount` vem com `alternatives` vazio e o `503` não sugere outra adquirente: acima do máximo, divida o pedido. Decida sempre pelo campo `error`. O `message` é texto de apoio e pode mudar. ## Perguntas que aparecem sempre **O CPF do comprador fica guardado?** Não. Ele vai para a adquirente na autorização do pagamento e só. O Throne não guarda o CPF digitado, e ele não volta na consulta nem no webhook. Se você quiser o documento registrado na venda, mande `customer.document` na criação, como no checkout comum. **Serve para Pix?** Não. O Checkout Brasil é só cartão. Para Pix, use `POST /transactions` com `payment_method: "pix"`, como em [Cobrar com Pix](/docs/pix). **Posso usar o mesmo Throne Elements?** Sim. O script, os atributos e os eventos são os mesmos: só troque o `data-id` pelo `transaction_id` novo. Vale a regra de sempre: para trocar de checkout, use um container novo ([detalhes](/docs/cartao/elements#pagina-de-uma-tela-so-react-vue-e-outras)). **Preciso de outra chave de API?** Não. A mesma chave, com `transactions:write`, cria os dois checkouts. **E se o comprador não tiver CPF?** O pagamento não sai: o campo é obrigatório e conferido na tela. Para esse comprador, crie o checkout comum. **O `customer.document` da criação preenche o CPF?** Não. O campo CPF da página nasce vazio e o comprador digita o dele. O que vai para a adquirente é o CPF digitado na tela; o `customer.document` fica registrado na venda e volta no webhook, como no checkout comum. **O `?nl=` no `checkout_url` muda o idioma?** Não. No Checkout Brasil a página sai em português sempre, inclusive para teste. **A venda aparece diferente na consulta ou no webhook?** Não. É uma venda de cartão como as outras: mesmo `GET /transactions/{id}`, mesmo `transaction.paid`, mesmo `acquirer`. **Posso mudar a frase de segurança ou o nome dos campos?** Não. Só o botão, as cores, o formato e a moldura mudam, pelo objeto `appearance`. O resto é o texto que o comprador e o banco esperam ver numa página de pagamento. **O `appearance` funciona no `POST /elements/checkout`?** Não. Ele só existe no Checkout Brasil; no checkout comum o campo é ignorado. ## Referência dos campos Requisição, `POST /elements/checkout/br`: | Campo | Obrigatório | Regra | |---|---|---| | `amount_cents` | Sim | Inteiro na menor unidade da moeda (R$ 97,00 = `9700`). Mínimo e máximo como no checkout comum: pela adquirente, pela moeda e pela cotação do dia | | `currency` | Sim | Código de 3 letras, maiúsculas ou minúsculas. As mesmas 48 moedas do checkout comum; para o comprador brasileiro, `brl` | | `acquirer` | Não | Só uma adquirente habilitada para o Checkout Brasil na sua conta; outra volta `422 acquirer_not_enabled`. Sem o campo, a venda vai para a habilitada | | `customer.name` | Não | Como no checkout comum: vai pré-preenchido no nome do cartão, e o comprador pode corrigir | | `customer.email`, `customer.document`, `customer.document_type`, `customer.phone`, `customer.address` | Não | Como no checkout comum: ficam registrados na venda e voltam no webhook. Nenhum gera erro; o que vier fora do formato é ajustado ou descartado, com aviso em `warnings` | | `appearance` | Não | Objeto com `button`, `fields` e `frame`. Veja [Aparência](#aparencia). Fora da regra: `422 invalid_appearance` | | `one_click_from`, `recurrence` | Não existem | `422 field_not_supported` | `metadata` e itens não são aceitos, como no checkout comum: guarde o `transaction_id` para ligar a venda ao seu pedido. Resposta `201`: os mesmos campos do checkout comum. Veja a [referência da resposta](/docs/cartao#resposta). O que é próprio do Checkout Brasil: - `warnings` pode trazer, além dos avisos de `customer`, o aviso `low_contrast`, sobre `appearance`. Veja também [Cartão: como funciona](/docs/cartao) e [Throne Elements](/docs/cartao/elements). --- # Status e ciclo de vida > Os status de uma venda de cartão, quando cada um acontece em cada adquirente e o que fazer. Fonte: https://app.thronecorporate.com/docs/cartao/status ## Os status | Status | Quando | O que fazer | |---|---|---| | `pending` | Checkout aberto. **Uma recusa com tentativas sobrando não muda o status**: o comprador pode tentar de novo | Esperar. Não libere o pedido | | `paid` | Pagamento confirmado | Liberar o pedido | | `failed` | O checkout fechou por recusa (regra por adquirente abaixo) | Para vender de novo, criar um checkout novo | | `expired` | O checkout passou da validade (24 horas) sem pagamento | Para vender de novo, criar um checkout novo | | `refunded` | Estornada | Nada a fazer pela API | | `chargeback` | Contestada pelo comprador | Acompanhar pelo painel | Uma venda que entra em análise (o comprador vê "Pagamento em análise") continua `pending` até a decisão. Ela termina `paid` ou `failed`. ## Ciclo de vida ```text pending -> paid -> refunded -> chargeback -> paid (contestação revertida a seu favor) pending -> failed -> paid (pagamento confirmado depois do fechamento) pending -> expired -> paid (pagamento confirmado depois da validade) ``` - `failed` e `expired` não são definitivos: se o pagamento for confirmado depois, a venda passa a `paid` e o webhook `transaction.paid` é enviado. Libere o pedido **sempre** pelo webhook ou pela consulta, nunca pela falta de erro. - No cartão, o único webhook é `transaction.paid`. Recusa, fechamento, expiração, estorno e contestação aparecem só na consulta. - **Estorno** não é feito pela API. Fale com o suporte do Throne. ## Quando a venda vira failed | Adquirente | Fecha como `failed` | |---|---| | Sterling | Na 5ª recusa, ou 10 minutos depois de uma recusa sem nova tentativa. Uma recusa depois da análise antifraude também fecha na hora | | Imperium | Na 5ª recusa, ou 10 minutos depois de uma recusa sem nova tentativa | | NovaCorp | Logo depois da 6ª recusa. Com tentativas sobrando, a partir de 10 minutos depois da última recusa, sem nova tentativa e com o comprador fora da página de pagamento. Pode levar alguns minutos a mais | | Eminence | Na 5ª recusa, ou 10 minutos depois de uma recusa sem nova tentativa. Uma recusa depois da análise antifraude ou de uma confirmação demorada também fecha na hora. Se a Eminence não aceitar o valor da venda, fecha já na primeira tentativa (a página recebe `limit`) | | Apex | Na 5ª recusa, ou 10 minutos depois de uma recusa sem nova tentativa. Uma reprovação na análise antifraude conta como recusa e gasta uma tentativa. Se a Apex não aceitar o valor da venda, fecha já na primeira tentativa (a página recebe `limit`) | | ShadowBank | Na 5ª recusa, ou 10 minutos depois de uma recusa sem nova tentativa. Uma reprovação na análise antifraude conta como recusa e gasta uma tentativa | | Vortex | Na 5ª recusa, ou 10 minutos depois de uma recusa sem nova tentativa. Uma reprovação na análise antifraude conta como recusa e gasta uma tentativa | As tentativas são contadas por checkout, no servidor. Recarregar a página não devolve tentativas. ## O que a consulta mostra em cada status `GET /transactions/{id}` com o `transaction_id`: | Status | Valores do seu saldo | Outros campos | |---|---|---| | `pending`, `failed`, `expired` | `amount_cents`, `fee_cents` e `net_amount_cents` iguais a 0 | `currency` e `native_amount_cents` com o que você mandou; `fx_rate` e `paid_at` nulos | | `paid` | Preenchidos, em reais (a moeda da sua conta) | `fx_rate` como número (a cotação fixada na criação) e `paid_at` com a data do pagamento | A consulta também traz `acquirer`, com a adquirente da venda. No Pix, `acquirer` vem nulo. ## Quanto tempo esperar Não cancele um pedido por falta de webhook antes da validade de 24 horas. Na dúvida, consulte `GET /transactions/{id}`. Quando o webhook costuma chegar em cada adquirente está em [Comparar adquirentes](/docs/cartao/comparar) e em [Webhooks](/docs/webhooks#quando-o-webhook-chega). --- # Upsell em um clique > Cobrar um segundo produto sem pedir o cartão de novo: o campo one_click_from, a resposta one_click e o que fazer quando ela vem false. Fonte: https://app.thronecorporate.com/docs/cartao/oneclick ## O que é Depois que o comprador paga uma venda de cartão, você pode oferecer um segundo produto e cobrar **sem pedir o cartão de novo**. É o upsell: o comprador vê o cartão que ele já usou, confirma e pronto. Funciona com o mesmo `POST /elements/checkout` e o mesmo Throne Elements que você já usa. **Um campo novo** na criação, e nada mais muda: campos, respostas, erros, moedas, webhook e o elemento na página continuam iguais. > [!NOTE] > Nada a mudar no que já está no ar. Sem o campo novo, o checkout é o de hoje. O upsell é uma página nova do seu funil, não uma alteração nas suas páginas atuais. ## Antes de começar O upsell em um clique é **habilitado por conta**, uma a uma, pelo time do Throne. Enquanto a sua conta não estiver habilitada, o campo é aceito, a resposta vem `one_click: false` e o comprador digita o cartão. Vale também para o passado: só serve de origem uma venda paga **depois** de a sua conta ser habilitada. Para pedir a habilitação, fale com o suporte. ## Como funciona, em três passos **1. A primeira venda é a de sempre.** Você cria o checkout, o comprador paga, e você recebe o `transaction_id` no `201` e o `transaction.paid` no webhook. **2. Na página de upsell, crie o checkout com `one_click_from`.** O valor é o `transaction_id` da venda que acabou de ser paga: ```bash curl -X POST https://app.thronecorporate.com/api/v1/elements/checkout \ --max-time 60 \ -H "Authorization: Bearer $THRONE_PK:$THRONE_SK" \ -H "Content-Type: application/json" \ -H "Accept: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "amount_cents": 9700, "currency": "brl", "one_click_from": "a1b2c3d4-e5f6-4789-abcd-ef0123456789" }' ``` **3. Olhe o campo `one_click` na resposta.** ```json { "transaction_id": "9f8e7d6c-5b4a-4321-9876-543210fedcba", "status": "pending", "checkout_url": "https://pay.example.com/checkout/7a6b5c4d3e2f10998877665544332211", "acquirer": "apex", "one_click": true, "one_click_card": { "brand": "mastercard", "last4": "2006" } } ``` Com `one_click: true`, o elemento abre mostrando o cartão terminado em 2006 e um botão. Com `one_click: false`, ele abre o formulário de sempre e o comprador digita o cartão. **Nos dois casos a venda acontece**, e o seu código é o mesmo: você pode usar `one_click_card` para escrever na sua página algo como "confirmar no cartão terminado em 2006". Os dois campos novos aparecem **só** quando você envia `one_click_from`. Sem ele, a resposta não ganha chave nenhuma. ## Quando `one_click` vem `false` Nunca é erro, nunca é `4xx`: o checkout nasce normal e o comprador paga digitando. Acontece quando: | Motivo | Detalhe | |---|---| | O upsell não está habilitado na sua conta | Habilitação por conta, pelo suporte | | A venda citada não é da sua conta | Só o `transaction_id` de uma venda sua funciona, e o uso cruzado fica registrado | | O valor não tem a forma de um `transaction_id` | Texto fora do formato, dentro dos 64 caracteres, não vira erro: vira `false` | | A venda não está paga | Pendente, recusada, estornada ou contestada não serve | | A venda de origem não tem cartão guardado | A origem tem de ser uma venda em que o comprador digitou o cartão. Upsell pago em um clique e cobrança de recorrência não deixam cartão guardado | | Passou da janela | Padrão de **120 minutos** contados do pagamento | | Teto de upsells atingido | Até **5** por venda paga. Checkout que morreu sem cobrar (recusado ou expirado) não ocupa vaga | | Adquirente diferente | O upsell precisa cair na mesma adquirente da venda de origem | | O comprador declarado é outro | Quando você envia `customer` no upsell, ele tem de ser a mesma pessoa da venda de origem | Por isso: **escreva a sua página de upsell para funcionar nos dois casos.** Mostre o botão quando vier `true` e o elemento comum quando vier `false`. > [!WARNING] > O `customer` do upsell, quando enviado, tem de ser o mesmo comprador da venda de origem. É o que impede cobrar o cartão guardado de uma pessoa numa venda de outra, dentro da mesma conta. O caminho mais simples é **não** enviar `customer` no upsell: os dados vêm da venda de origem. ## O que o comprador vê Com `one_click: true`, o elemento mostra o produto, o valor, a bandeira com os quatro últimos dígitos e um botão. Há também um link discreto para usar outro cartão, que abre o formulário completo na mesma tela. Em alguns casos o banco pede o **código de segurança** para confirmar a recompra. Quando isso acontece, o elemento mostra um campo de três dígitos (quatro no Amex) abaixo do cartão, e o comprador confirma ali mesmo. Continua sem digitar número, validade ou nome, e **nenhuma tentativa é gasta** quando o código é pedido. Quando o cartão guardado não serve mais no momento do clique, o formulário de sempre volta sozinho, sem aviso e sem gastar tentativa: para o comprador, nada aconteceu. ## Eventos na sua página Os mesmos de sempre, sem evento novo: `throne-elements:ready`, `throne-elements:paid`, `throne-elements:error` e `throne-elements:resize`. Veja [Throne Elements](/docs/cartao/elements). ## Perguntas que aparecem sempre **Preciso guardar o cartão do meu cliente?** Não. Você não recebe, não guarda e não manuseia dado de cartão em nenhum momento. Quem cuida disso é o Throne, e o que você usa é só o `transaction_id` da venda anterior, que você já tem. **E se o comprador tiver fechado a aba entre a compra e o upsell?** Enquanto estiver dentro da janela de 120 minutos e você mandar o `one_click_from` certo, funciona. O reconhecimento não depende de cookie nem de sessão no navegador. **Posso usar o mesmo `one_click_from` em dois upsells?** Sim, até 5 por venda paga. Cada um é uma venda independente, com o seu próprio `transaction_id` e o seu próprio webhook. **Dá para fazer upsell de upsell?** Sim, mas a origem continua sendo a venda em que o comprador digitou o cartão: use sempre o `transaction_id` **dela** em `one_click_from`, inclusive no segundo e no terceiro upsell, dentro dos 120 minutos e do teto de 5. O `transaction_id` de um upsell pago em um clique não serve de origem: a resposta vem `one_click: false`. **A janela conta do primeiro pagamento ou do último?** Do pagamento da venda que você citou em `one_click_from`. **O valor pode ser diferente da primeira venda?** Sim, é uma venda nova: valor, moeda e produto são seus. **O que aparece no extrato do comprador?** Duas cobranças separadas, com o mesmo descritor das suas vendas de cartão. **Serve para Pix?** Não. O Pix não tem o que guardar: cada cobrança é um QR Code novo. O campo é aceito com um `transaction_id` de Pix, mas a resposta vem `one_click: false`. ## Em quais adquirentes funciona No `POST /elements/checkout`, o campo `one_click_from` pode ser enviado sempre, em qualquer adquirente de cartão: o que muda é a resposta. No [Checkout Brasil](/docs/cartao/brasil), ele não existe e volta `422 field_not_supported`. Neste endpoint o um clique está disponível na **Apex**; nas outras, o campo é aceito e `one_click` volta `false`, sem erro. Consulte `GET /elements/acquirers` para ver o que a sua conta tem habilitado. **Na Vortex o um clique existe por uma rota própria**, dentro da integração direta: o [`POST /api/v1/vortex/upsell`](/docs/cartao/vortex-direto#upsell-em-um-clique). Ele não usa `one_click_from` nem `card_token`, cobra o cartão guardado da venda de origem e pode ele mesmo ser recorrente. Precisa estar liberado na conta, à parte da integração direta. Com roteamento de adquirente fixado na sua conta, o upsell cai na mesma adquirente de tudo o que você vende, e é justamente o caso em que ele acende sozinho. ## Referência do campo | Campo | Onde | Tipo | Obrigatório | Descrição | |---|---|---|---|---| | `one_click_from` | corpo do `POST /elements/checkout` | string, até 64 caracteres | não | `transaction_id` de uma venda sua, paga, da mesma adquirente | | `one_click` | resposta `201` | booleano | só quando `one_click_from` foi enviado | o checkout abre em um clique | | `one_click_card` | resposta `201` | objeto | só quando `one_click` é `true` | `brand` e `last4`, para a sua página | Veja também [Recorrência](/docs/cartao/recorrencias), para a cobrança que se repete sozinha. --- # Recorrência > Cobrança que se repete sozinha no cartão: os sete ciclos, o campo recurrence, as situações, o cancelamento e como acompanhar cada cobrança. Fonte: https://app.thronecorporate.com/docs/cartao/recorrencias ## O que é Uma cobrança que se repete sozinha no cartão do comprador, no intervalo que você escolher. Ele paga uma vez, no checkout de sempre, e as cobranças seguintes saem automaticamente. A integração é a mesma: `POST /elements/checkout` com **um campo novo**, o mesmo Throne Elements na página, o mesmo webhook `transaction.paid` a cada cobrança. > [!NOTE] > O checkout é idêntico ao de uma venda única. Nenhum texto na tela diz que é recorrência, nenhuma data de cobrança aparece, o botão é o mesmo. Quem avisa o comprador é você, na sua página. > [!NOTE] > Nada a mudar no que já está no ar. Sem o campo novo, a requisição, a resposta e o webhook são os de hoje. ## Criar ```bash curl -X POST https://app.thronecorporate.com/api/v1/elements/checkout \ --max-time 60 \ -H "Authorization: Bearer $THRONE_PK:$THRONE_SK" \ -H "Content-Type: application/json" \ -H "Accept: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "amount_cents": 4990, "currency": "brl", "acquirer": "apex", "recurrence": { "cycle": "monthly", "max_payments": 12 }, "customer": { "name": "Ana Souza", "email": "ana@example.com" } }' ``` Resposta `201`, com três campos novos além dos de sempre: ```json { "transaction_id": "a1b2c3d4-e5f6-4789-abcd-ef0123456789", "status": "pending", "checkout_url": "https://pay.example.com/checkout/0f1e2d3c4b5a69788796a5b4c3d2e1f0", "acquirer": "apex", "recurrence_id": "rec_7f3a9c1e5b2d4a6f8c0e1b3d", "cycle": "monthly", "next_charge_at": "2026-11-18T15:00:00Z" } ``` O `transaction_id` é a **primeira cobrança**, que acontece no checkout. O `recurrence_id` é a assinatura, que você usa para consultar e cancelar. **Guarde o `recurrence_id`**: é por ele que você acompanha a cadência. Os três campos aparecem só quando você envia `recurrence`. > [!WARNING] > Se `recurrence_id` vier `null` (com `next_charge_at` também `null`), a venda foi criada e o comprador pode pagar, mas **a assinatura não abriu**: não vão existir cobranças seguintes. Não repita a chamada com a mesma `Idempotency-Key`. Confira na sua conta se a venda nasceu e fale com o suporte. ## Ciclos aceitos | Valor em `cycle` | Intervalo | |---|---| | `weekly` | semanal, a cada 7 dias | | `biweekly` | quinzenal, a cada 14 dias | | `monthly` | mensal | | `bimonthly` | bimestral, a cada 2 meses | | `quarterly` | trimestral, a cada 3 meses | | `semiannually` | semestral, a cada 6 meses | | `yearly` | anual | Qualquer outro valor devolve `422 invalid_cycle` com a lista aceita. Nada é criado. `max_payments` é opcional, de 2 a 120, e conta **todas** as cobranças, inclusive a primeira: `12` em um ciclo mensal significa doze meses. Quando a recorrência chega ao teto, ela vira `ended` e `next_charge_at` fica nulo. Sem o campo, ela segue até você cancelar. O `next_charge_at` é sempre uma **estimativa** pelo ciclo: na criação, contada do dia da criação; depois, contada da data em que a última cobrança foi paga. Na consulta, ele vem nulo em `pending`, até a primeira cobrança ser confirmada, e depois que a recorrência para (`canceled` ou `ended`). A cobrança pode sair um dia antes ou depois. Não use essa data para liberar nem para cortar acesso: use o `transaction.paid` de cada cobrança. ## A primeira cobrança A primeira cobrança é a venda do checkout, cobrada na hora, com uma passada só no cartão. Enquanto ela não aprova, a recorrência fica em `pending`. Se o cartão é recusado, **nada é assinado**: o comprador vê a mensagem de recusa de sempre, tenta de novo no mesmo checkout (dentro do limite de tentativas) e a recorrência só passa para `active` quando uma tentativa aprovar. Um checkout que expira sem pagamento deixa a recorrência em `pending` para sempre, sem nunca cobrar nada. Em adquirente cuja aprovação não é imediata, como a [Vortex](/docs/cartao/vortex#recorrencia), a primeira cobrança pode ficar em análise por alguns instantes. Nesse intervalo a venda fica `pending`, a recorrência fica `pending` e nenhuma cobrança seguinte sai; as duas seguem quando a análise libera. Se a análise reprovar, nada é assinado, e a tentativa é gasta como uma recusa. ## Como acompanhar **Cada cobrança é uma venda**, com `transaction_id` próprio, e chega no seu webhook no formato de sempre. A primeira acontece no checkout; as seguintes aparecem sozinhas, no dia do ciclo. > [!NOTE] > O webhook não ganha campo novo: o formato é o mesmo de todas as suas vendas de cartão. Para acompanhar a assinatura, consulte a recorrência. No painel, a área Recorrências lista as cobranças de cada uma. ```bash curl https://app.thronecorporate.com/api/v1/recurrences/rec_7f3a9c1e5b2d4a6f8c0e1b3d \ -H "Authorization: Bearer $THRONE_PK:$THRONE_SK" \ -H "Accept: application/json" ``` ```json { "recurrence_id": "rec_7f3a9c1e5b2d4a6f8c0e1b3d", "status": "active", "cycle": "monthly", "amount_cents": 4990, "currency": "BRL", "native_amount_cents": 4990, "max_payments": 12, "charges_count": 3, "last_charge_at": "2026-12-18T18:07:22Z", "next_charge_at": "2027-01-18T15:00:00Z", "product_name": null, "origin_transaction_id": "a1b2c3d4-e5f6-4789-abcd-ef0123456789", "canceled_at": null, "created_at": "2026-10-18T18:04:11Z", "metadata": {} } ``` - `currency` e `native_amount_cents` são a moeda e o valor que você mandou; `amount_cents` é o valor em reais, pela cotação fixada na criação. A mesma convenção das vendas de cartão. - `origin_transaction_id` é a venda da primeira cobrança, o mesmo id que o `201` devolveu. - `charges_count` é quantas cobranças desta recorrência existem na sua conta, e `last_charge_at` é a data da última. **Quem diz se ela ainda cobra é o `status`**, não essa conta. - `product_name` fica nulo: a criação do checkout não recebe nome de produto. `GET /recurrences` lista as suas recorrências, as mais recentes primeiro, em `data`, com a paginação em `meta`, como nos outros endpoints de listagem. Aceita `per_page`, de 1 a 100 (padrão 25). Os três endpoints usam as permissões que **já existem**: `transactions:read` nas duas leituras e `transactions:write` no cancelamento. Nenhuma permissão nova foi criada e nenhuma chave precisa ser gerada de novo. Se a sua chave só cria cobrança, ela cancela, mas para ler a recorrência ligue também `transactions:read` nela. ## Situações | `status` | O que significa | |---|---| | `pending` | criada, esperando o pagamento da primeira cobrança | | `active` | cobrando no ciclo | | `past_due` | a última cobrança foi recusada. A assinatura continua, e volta para `active` quando uma cobrança aprovar | | `canceled` | você cancelou. Não cobra mais | | `ended` | chegou ao teto de `max_payments` e parou sozinha | **Cobrança recusada não cria venda**: nenhum `transaction.paid` chega e nenhuma venda nova aparece na sua conta. Você vê a situação `past_due` na consulta e no painel. ## Cancelar ```bash curl -X POST https://app.thronecorporate.com/api/v1/recurrences/rec_7f3a9c1e5b2d4a6f8c0e1b3d/cancel \ -H "Authorization: Bearer $THRONE_PK:$THRONE_SK" \ -H "Accept: application/json" ``` Com o `200`, as cobranças param na hora, e a resposta traz a recorrência. Cancelar uma recorrência que já estava parada também responde `200`, sem efeito nenhum. Cancelar **não** estorna o que já foi pago e **não** mexe em venda nenhuma: para devolver dinheiro, fale com o suporte. Você também cancela pelo painel, na área Recorrências. Quando o cancelamento não é confirmado, a resposta é de erro: | Status | `error` | Significado | O que fazer | |---|---|---|---| | `422` | `cancel_failed` | Esta recorrência não é cancelada por aqui | Fale com o suporte | | `503` | `cancel_failed` | O cancelamento não foi confirmado agora | Repita a mesma chamada em instantes, até receber `200` | Enquanto a resposta não for `200`, trate a recorrência como ativa. ## Moeda Você manda o valor na moeda que quiser, como em qualquer venda de cartão, e a cotação é fixada **na criação**. Os ciclos seguintes cobram o mesmo valor fixado: a cotação não é refeita a cada mês. Se você precisa acompanhar a variação da moeda, cancele e crie uma recorrência nova com o valor atualizado. ## Perguntas que aparecem sempre **O comprador sabe que assinou?** Pelo checkout, não: a tela é a de uma venda única. Quem informa é você, na sua página, e é o certo a fazer: o comprador que não reconhece uma cobrança abre contestação. **Posso mudar o valor, o ciclo ou o teto de uma recorrência ativa?** Não. Cancele e crie outra. **Posso mandar `recurrence` e `one_click_from` na mesma chamada?** Mande um ou o outro. Os dois campos são aceitos juntos e nenhum erro é devolvido, mas a cobrança em um clique não abre assinatura: o checkout que vira a primeira cobrança de uma recorrência é o que pede o cartão ao comprador. **Dá para oferecer upsell em um clique depois de uma cobrança de recorrência?** Não. A cobrança de recorrência não deixa cartão guardado, então `one_click_from` com o id dela devolve `one_click: false` e o comprador digita o cartão. Veja [Upsell em um clique](/docs/cartao/oneclick). **Em quais adquirentes funciona?** Na **Apex** e na **Vortex**. Mandar `recurrence` em outra devolve `422 recurrence_not_supported`, dizendo qual aceita. Nada é criado. Na Vortex, a [integração direta por API](/docs/cartao/vortex-direto#recorrencia) também aceita. **Serve para Pix?** Não. Recorrência existe só no cartão. ## Referência dos campos | Campo | Onde | Tipo | Obrigatório | Descrição | |---|---|---|---|---| | `recurrence.cycle` | corpo do `POST /elements/checkout` | string | sim, dentro do objeto | um dos sete ciclos | | `recurrence.max_payments` | idem | inteiro de 2 a 120 | não | teto de cobranças, contando a primeira | | `recurrence_id` | resposta `201` | string | só quando `recurrence` foi enviado | identificador da assinatura (`rec_` e 24 caracteres) | | `cycle` | resposta `201` | string | idem | o ciclo gravado | | `next_charge_at` | resposta `201` | data ISO 8601, em UTC | idem | estimativa da próxima cobrança | --- # Cartão com a Sterling > Regras e comportamento do checkout quando a venda passa pela Sterling (acquirer sterling). Fonte: https://app.thronecorporate.com/docs/cartao/sterling ## O que é igual às outras adquirentes A integração é a mesma do [guia de cartão](/docs/cartao): `POST /elements/checkout` na moeda do comprador, Throne Elements na página, webhook `transaction.paid`. Campos, respostas, erros, moedas e formato do webhook não mudam. Esta página cobre só o que é próprio da Sterling. **Para usar a Sterling**, mande `"acquirer": "sterling"` na criação do checkout. Se a Sterling for a padrão da sua conta, o campo é opcional. Veja [Escolher a adquirente](/docs/cartao/escolher). ## Resumo | Item | Na Sterling | |---|---| | Verificação do banco | Não tem hoje: o resultado sai na hora | | Navegador de app (Instagram, Facebook, Google, TikTok) | Tudo dentro do elemento; os eventos chegam normalmente | | Valor máximo por venda | Varia com a moeda e com a cotação do dia. Veja [Limites](#limites) | | Tentativas por checkout | 5 | | Validade do checkout | 24 horas | | Fechamento por recusa | Na 5ª recusa, ou 10 minutos depois de uma recusa sem nova tentativa (`failed`) | | Parcelas | À vista | | Idioma do checkout | Pelo país do comprador; Brasil e Portugal veem em inglês | | Idioma das mensagens de recusa | Pelo header `Accept-Language` da chamada que criou o checkout | ## Limites | Limite | Na Sterling | |---|---| | Mínimo por venda | Varia com a moeda e com a cotação do dia, ou o mínimo próprio da Sterling, se houver | | Máximo por venda | Depende da adquirente e varia com a moeda e com a cotação do dia. Consulte `GET /elements/acquirers?currency=` (campo `limits.max_amount_cents`) | | Acima do máximo | `422 invalid_amount` com `max_amount_cents` na sua moeda e `alternatives`. Nada é criado | | Capacidade de criação | Em pico ou instabilidade, `503 temporarily_unavailable` com `Retry-After` | ## Como o comprador paga 1. Preenche número, validade, CVV e nome impresso no cartão. 2. Clica em pagar. O resultado aparece em poucos segundos, dentro do próprio elemento. 3. Aprovado: o elemento mostra a confirmação e dispara `throne-elements:paid`. 4. Recusado: o elemento mostra o motivo, quantas tentativas restam e um botão para tentar de novo. A venda continua `pending`. No computador, no celular e no navegador de app (Instagram, Facebook e outros), o pagamento acontece todo dentro do elemento, sem janela nova. Os eventos chegam à sua página nos três casos. Se a verificação do banco for ligada nesta adquirente no futuro, ela vai aparecer **dentro do próprio checkout**, sem janela nova. Por isso, não fixe a altura do container. ## Requisitos da página - Container sem altura fixa e sem `overflow` escondido. - Sem `sandbox` em volta do elemento. Se não der para tirar, use a lista mínima de [Throne Elements](/docs/cartao/elements#boas-praticas). - `Accept-Language` na criação, se o público não fala português (veja abaixo). ## Pagamento em análise Às vezes o pagamento entra em análise antifraude. O comprador vê "Pagamento em análise" e a venda fica `pending` até a decisão: - aprovado, vira `paid` e o webhook `transaction.paid` é enviado; - reprovado, vira `failed`. Não libere o pedido enquanto não chegar `transaction.paid`. ## Idioma das mensagens de recusa O texto de recusa que o comprador vê (e que chega em `e.detail.message`) segue o header `Accept-Language` da chamada do seu servidor que criou o checkout: português por padrão, inglês com `en` e espanhol com `es`. Para público fora do Brasil, envie o header na criação: ```bash curl -X POST https://app.thronecorporate.com/api/v1/elements/checkout \ -H "Authorization: Bearer $THRONE_PK:$THRONE_SK" \ -H "Content-Type: application/json" \ -H "Accept: application/json" \ -H "Accept-Language: en" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "amount_cents": 4990, "currency": "usd", "acquirer": "sterling" }' ``` O restante da tela segue o país do comprador, independentemente desse header. Para decidir o que fazer, use `e.detail.code`, que não muda com o idioma. ## Erros do elemento Além dos códigos comuns, a Sterling pode enviar `token` em `throne-elements:error`: o cartão não passou na validação inicial. O comprador pode corrigir os dados e tentar de novo, e essa falha não consome tentativa. A tabela completa está em [Throne Elements](/docs/cartao/elements#codigos-de-erro). Duas tentativas ao mesmo tempo no mesmo checkout (duas abas ou duplo clique) não geram cobrança dupla: a segunda recebe "Já existe um pagamento em andamento. Aguarde." ## Erros e o que fazer | Situação | Resposta | O que fazer | |---|---|---| | Valor acima do máximo | `422 invalid_amount`, com `max_amount_cents` e `alternatives` | Não repita. Crie com o `acquirer` de uma das `alternatives` ou divida o pedido | | Sterling em pausa, instável ou sem capacidade | `503 temporarily_unavailable`, com `Retry-After` | Espere o `Retry-After` e repita com uma `Idempotency-Key` nova, ou use outra adquirente habilitada | | Criação recusada | `502 checkout_failed`, com `"retryable": false` | Não repita igual. Se continuar, fale com o suporte | | Falha passageira de conexão | `502 checkout_failed`, com `"retryable": true` | Repita com uma `Idempotency-Key` nova | | Sem resposta (timeout do seu lado) | Nenhuma | Repita com a **mesma** `Idempotency-Key` ([regra](/docs/comecar#como-repetir-uma-chamada-com-seguranca)) | ## Quando chega o webhook Como o resultado sai na hora, o webhook `transaction.paid` costuma sair logo depois da aprovação. Numa venda que entra em análise, ele sai quando a análise aprova. Não cancele o pedido por falta de webhook antes da validade de 24 horas; na dúvida, consulte `GET /transactions/{id}`. ## Depois da venda - **Estorno**: não é feito pela API; fale com o suporte. O estorno total deixa a venda `refunded`. O parcial mantém `paid`, com os valores reduzidos. - **Contestação**: o Throne debita da sua carteira o valor líquido da venda, e ela vira `chargeback`. Não há etapa de defesa pela API. - Nenhum desses eventos gera webhook. Acompanhe pelo `GET /transactions/{id}`. ## Testar Use um cartão de verdade com valor baixo. Os erros que dá para provocar sem custo estão em [Como testar](/docs/testar). ## Checklist - [ ] Checkout criado no servidor, com `Idempotency-Key` nova por venda e timeout de 60 segundos. - [ ] Moeda do comprador em `currency`, sem ajustar o valor no seu site. - [ ] `Accept-Language` na criação, se o público não fala português. - [ ] Container sem altura fixa e sem `sandbox`. - [ ] Pedido liberado só com `transaction.paid` (assinatura validada). - [ ] Novo checkout para cada nova tentativa de compra depois de `failed` ou `expired`. --- # Cartão com a Imperium > Regras e comportamento do checkout quando a venda passa pela Imperium (acquirer imperium). Fonte: https://app.thronecorporate.com/docs/cartao/imperium ## O que é igual às outras adquirentes A integração é a mesma do [guia de cartão](/docs/cartao): `POST /elements/checkout` na moeda do comprador, Throne Elements na página, webhook `transaction.paid`. Campos, respostas, erros, moedas e formato do webhook não mudam. Esta página cobre só o que é próprio da Imperium. **Para usar a Imperium**, mande `"acquirer": "imperium"` na criação do checkout. Se a Imperium for a padrão da sua conta, o campo é opcional. Veja [Escolher a adquirente](/docs/cartao/escolher). ## Resumo | Item | Na Imperium | |---|---| | Verificação do banco | Não tem: o resultado sai na própria tentativa | | Navegador de app (Instagram, Facebook, Google, TikTok) | Tudo dentro do elemento; os eventos chegam normalmente | | Valor máximo por venda | Varia com a moeda e com a cotação do dia. Veja [Limites](#limites) | | Tentativas por checkout | 5 | | Validade do checkout | 24 horas | | Fechamento por recusa | Na 5ª recusa, ou 10 minutos depois de uma recusa sem nova tentativa (`failed`) | | Tempo de resposta | Aprovação em segundos; uma recusa pode levar até 25 segundos | | Parcelas | À vista | | Idioma do checkout | Pelo país do comprador; Brasil e Portugal veem em inglês | | Idioma das mensagens de recusa | Pelo header `Accept-Language` da chamada que criou o checkout: português por padrão, inglês com `en` e espanhol com `es` | ## Limites | Limite | Na Imperium | |---|---| | Mínimo por venda | Varia com a moeda e com a cotação do dia, ou o mínimo próprio da Imperium, se houver | | Máximo por venda | Depende da adquirente e varia com a moeda e com a cotação do dia. Consulte `GET /elements/acquirers?currency=` (campo `limits.max_amount_cents`) | | Acima do máximo | `422 invalid_amount` com `max_amount_cents` na sua moeda e `alternatives`. Nada é criado | | Capacidade de criação | Em pico ou instabilidade, `503 temporarily_unavailable` com `Retry-After` | ## Como o comprador paga 1. Preenche número, validade, CVV e nome impresso no cartão. 2. Clica em pagar. O elemento mostra "processando" até a resposta. 3. Aprovado: a confirmação aparece na hora e o elemento dispara `throne-elements:paid`. 4. Recusado: o elemento avisa que o pagamento não foi autorizado, mostra quantas tentativas restam e o botão para tentar de novo. A venda continua `pending`. > [!NOTE] > A resposta de uma recusa pode demorar até 25 segundos. Não recarregue a página nem crie outro checkout nesse intervalo: o elemento continua acompanhando a tentativa. Como não existe etapa de verificação do banco, não há janela nem tela extra: tudo acontece dentro do elemento, no computador, no celular e no navegador de app. Os eventos chegam à sua página nos três casos. ## Requisitos da página - Uma tela de pagamento que aguarda até 25 segundos sem recarregar. - Container sem altura fixa e sem `overflow` escondido. - Sem `sandbox` em volta do elemento. Se não der para tirar, use a lista mínima de [Throne Elements](/docs/cartao/elements#boas-praticas). ## Erros e o que fazer | Situação | Resposta | O que fazer | |---|---|---| | Valor acima do máximo | `422 invalid_amount`, com `max_amount_cents` e `alternatives` | Não repita. Crie com o `acquirer` de uma das `alternatives` ou divida o pedido | | Imperium em pausa, instável ou sem capacidade | `503 temporarily_unavailable`, com `Retry-After` | Espere o `Retry-After` e repita com uma `Idempotency-Key` nova, ou use outra adquirente habilitada | | Criação recusada | `502 checkout_failed`, com `"retryable": false` | Não repita igual. Se continuar, fale com o suporte | | Falha passageira de conexão | `502 checkout_failed`, com `"retryable": true` | Repita com uma `Idempotency-Key` nova | | Sem resposta (timeout do seu lado) | Nenhuma | Repita com a **mesma** `Idempotency-Key` ([regra](/docs/comecar#como-repetir-uma-chamada-com-seguranca)) | Na página, a Imperium não envia `code` em `throne-elements:error`, só `e.detail.message`. Falha de conexão, validade e encerramento chegam como `network`, `expired` e `ended`. Numa recusa, inclusive a última, chega o texto mostrado ao comprador, que não diz se ainda há tentativa; `attempts_exhausted` só chega quando o elemento abre num checkout que já fechou. O próprio elemento mostra ao comprador se ele pode tentar de novo. Para saber se o checkout fechou, consulte `GET /transactions/{id}`. A tabela completa está em [Throne Elements](/docs/cartao/elements#codigos-de-erro). ## Quando chega o webhook O resultado sai na própria tentativa, então o webhook `transaction.paid` costuma sair logo depois da aprovação. Não cancele o pedido por falta de webhook antes da validade de 24 horas; na dúvida, consulte `GET /transactions/{id}`. ## Depois da venda - **Estorno**: não é feito pela API; fale com o suporte. A venda estornada fica `refunded`. - **Contestação**: a venda vira `chargeback`. Se a contestação for revertida a seu favor, ela volta para `paid`. - Nenhum desses eventos gera webhook. Acompanhe pelo `GET /transactions/{id}`. ## Testar Use um cartão de verdade com valor baixo. Os erros que dá para provocar sem custo estão em [Como testar](/docs/testar). ## Checklist - [ ] Checkout criado no servidor, com `Idempotency-Key` nova por venda e timeout de 60 segundos. - [ ] Moeda do comprador em `currency`, sem ajustar o valor no seu site. - [ ] Tela de pagamento que aguarda até 25 segundos sem recarregar. - [ ] Pedido liberado só com `transaction.paid` (assinatura validada). - [ ] Novo checkout para cada nova tentativa de compra depois de `failed` ou `expired`. --- # Cartão com a NovaCorp > Regras e comportamento do checkout quando a venda passa pela NovaCorp (acquirer novacorp). Fonte: https://app.thronecorporate.com/docs/cartao/novacorp ## O que é igual às outras adquirentes A integração é a mesma do [guia de cartão](/docs/cartao): `POST /elements/checkout` na moeda do comprador, Throne Elements na página, webhook `transaction.paid`. Campos, respostas, erros e formato do webhook não mudam. **Para usar a NovaCorp**, mande `"acquirer": "novacorp"` na criação do checkout. Se a NovaCorp for a padrão da sua conta, o campo é opcional. Veja [Escolher a adquirente](/docs/cartao/escolher). **A moeda funciona exatamente como nas outras adquirentes (Sterling, Imperium, Eminence, Apex e ShadowBank).** Você manda o valor na moeda do comprador (por exemplo, `"currency": "chf"`) e ele paga nessa moeda. A cotação que credita o seu saldo é fixada na criação, e o webhook traz `currency`, `native_amount_cents` e `fx_rate` no mesmo formato. Detalhes em [Moedas e limites](/docs/cartao/moeda). Esta página cobre só o que é próprio da NovaCorp. ## Resumo | Item | Na NovaCorp | |---|---| | Verificação do banco | Cada tentativa abre uma **janela de verificação**; a tela do banco aparece nela quando o banco pede | | Navegador de app (Instagram, Facebook, Google, TikTok) | A própria aba vai para a página segura de pagamento e termina lá, **sem evento na sua página** | | Valor máximo por venda | Varia com a moeda e com a cotação do dia. Hoje é o mais baixo das seis adquirentes. Veja [Limites](#limites) | | Tentativas por checkout | 6, contadas no servidor. Recarregar a página não devolve tentativas | | Validade do checkout | 24 horas | | Fechamento por recusa | Logo depois da 6ª recusa. Com tentativas sobrando, `failed` a partir de 10 minutos depois da última recusa, sem nova tentativa e com o comprador fora da página; pode levar alguns minutos a mais | | Parcelas | À vista | | Idioma do checkout | Pelo país do comprador; Brasil e Portugal veem em inglês | ## Limites | Limite | Na NovaCorp | |---|---| | Mínimo por venda | Varia com a moeda e com a cotação do dia, ou o mínimo próprio da NovaCorp, se houver | | Máximo por venda | Depende da adquirente e varia com a moeda e com a cotação do dia. Consulte `GET /elements/acquirers?currency=` (campo `limits.max_amount_cents`) | | Acima do máximo | `422 invalid_amount` com `max_amount_cents` na sua moeda e `alternatives`. Nada é criado | | Capacidade de criação | Dividida entre todas as contas. Em pico ou instabilidade, `503 temporarily_unavailable` com `Retry-After` | O máximo da NovaCorp é mais baixo que o das outras adquirentes. Para tíquetes altos, crie a venda com o `acquirer` de uma das `alternatives` ou peça ao suporte o [roteamento por valor](/docs/cartao/escolher#roteamento-por-valor). ## Como o comprador paga ### No computador 1. Preenche número, validade, CVV e nome impresso no cartão. 2. Clica em pagar. O Throne abre uma **janela de verificação** e o elemento mostra "Conclua a verificação na janela que abrimos". 3. Na janela, o comprador vê uma tela de espera no idioma do checkout e, quando o banco pede, a tela de verificação do próprio banco (código por SMS, aplicativo do banco, etc.). 4. Quando o resultado sai, a janela fecha e o elemento mostra o resultado. 5. Aprovado: o elemento dispara `throne-elements:paid`. Recusado: mostra quantas tentativas restam e o botão para tentar de novo; a venda continua `pending`. Se o comprador fechar a janela sem concluir, o elemento continua confirmando com o banco por um tempo, porque o pagamento pode ter sido aprovado no último instante. Sem tentativa nova, ele avisa que a verificação não foi concluída e oferece nova tentativa. Se o comprador não concluiu no banco, nada foi cobrado. A janela existe porque muitos bancos não deixam a tela de verificação aparecer dentro de outra página. ### No celular, em navegador comum Em Safari, Chrome e outros navegadores comuns, a janela de verificação abre como uma nova aba. Quando o resultado sai, ela é fechada e o comprador volta para a aba da sua loja, já com o resultado. Os eventos chegam normalmente. ### Em navegador de app ou com a janela bloqueada Instagram, Facebook, o app do Google, TikTok e outros apps abrem links num navegador próprio, que não abre uma segunda janela. O mesmo vale para um navegador comum que bloqueia a janela de verificação. Nesses casos, **a própria aba sai da sua página** e vai para a página segura de pagamento. O comprador passa pela tela do banco e termina nessa página, que mostra o resultado. Uma recusa ali oferece nova tentativa na mesma página. - A sua página **não recebe** `throne-elements:paid` nem outro evento. - O comprador não volta sozinho para a sua loja. - O pagamento é cobrado normalmente quando aprovado, e o webhook `transaction.paid` é enviado como em qualquer venda. Por isso, nesta adquirente: - libere o pedido pelo webhook ou pelo `GET`, nunca só pelo evento da página; - envie a conversão da campanha pelo seu servidor ao receber `transaction.paid` ([como](/docs/webhooks#conversao-de-campanha-pelo-servidor)), senão as vendas que vêm de anúncio em app não aparecem na campanha; - mande a confirmação do pedido por e-mail, pelo servidor. Se o comprador voltar à sua página pelo botão voltar, o elemento pode montar de novo já pago e disparar `throne-elements:paid`. Isso não é garantido. ### O que o comprador vê - **O endereço.** A janela de verificação e a página segura de pagamento mostram na barra o endereço da página de pagamento, não o da sua loja. - **A confirmação do banco.** Quando o banco pede, a tela dele aparece na janela de verificação (ou na própria aba, no navegador de app). A fatura do cartão mostra a compra sem o nome da sua loja. Avise ao lado do botão de pagar. Textos sugeridos: ```text Inglês Secure payment. Your bank may ask you to confirm this purchase on a separate secure page. ``` ```text Alemão Sichere Zahlung. Ihre Bank kann Sie bitten, diesen Kauf auf einer separaten sicheren Seite zu bestätigen. ``` ```text Espanhol Pago seguro. Tu banco puede pedirte que confirmes esta compra en una página segura aparte. ``` ## Requisitos da página - **O clique no botão de pagar precisa ser do comprador.** A janela só abre a partir de um clique de verdade; não dispare o pagamento por código. - **Não coloque o elemento dentro de um iframe com `sandbox`.** Se não der para tirar o `sandbox`, ele precisa incluir no mínimo `allow-scripts allow-same-origin allow-forms allow-popups allow-popups-to-escape-sandbox allow-top-navigation-by-user-activation`. Sem `allow-popups`, a janela não abre. Sem `allow-top-navigation-by-user-activation`, o pagamento em navegador de app não sai. - **Não fixe a altura do container.** O elemento ajusta a altura quando mostra a espera e o resultado. - Se a sua página envia o header `Cross-Origin-Opener-Policy: same-origin`, a janela pode perder o contato com o elemento. Com esse header, teste um pagamento completo no computador e no celular antes de publicar. ## Erros e o que fazer Na criação do checkout: | Situação | Resposta | O que fazer | |---|---|---| | Valor acima do máximo da NovaCorp | `422 invalid_amount`, com `max_amount_cents` e `alternatives` | Não repita. Crie com o `acquirer` de uma das `alternatives` ou divida o pedido | | NovaCorp em pausa, instável ou sem capacidade | `503 temporarily_unavailable`, com `Retry-After` e `"retryable": true` | Espere o `Retry-After` e repita com uma `Idempotency-Key` nova, ou use outra adquirente habilitada | | Criação recusada | `502 checkout_failed`, com `"retryable": false` | Não repita igual. Se continuar, fale com o suporte | | Falha passageira de conexão | `502 checkout_failed`, com `"retryable": true` | Repita com uma `Idempotency-Key` nova | | Sem resposta (timeout do seu lado) | Nenhuma | Repita com a **mesma** `Idempotency-Key` ([regra](/docs/comecar#como-repetir-uma-chamada-com-seguranca)) | Na página, em `throne-elements:error` (`e.detail.code`): | `code` | O que aconteceu | O que fazer | |---|---|---| | `declined` | O banco não autorizou | O comprador pode tentar de novo ou usar outro cartão. Vale sugerir que ele libere compras online e internacionais no app do banco | | `window_closed` | O comprador fechou a janela sem concluir | Nada; o elemento oferece nova tentativa | | `canceled` | O comprador cancelou a verificação | Nada; o elemento oferece nova tentativa | | `popup_blocked` | A janela de verificação fechou antes do envio do cartão. Nada foi cobrado | Nada; o elemento avisa o comprador, que pode tentar de novo. Não é janela bloqueada: com a janela bloqueada, o pagamento segue pela própria aba, sem evento | | `timeout` | A confirmação demorou mais que o esperado | Espere o webhook ou consulte o `GET`; não crie outro checkout | | `attempts_exhausted` | Acabaram as 6 tentativas | Ofereça outro meio de pagamento ou um checkout novo | | `closed` | O checkout fechou por recusa | Ofereça um checkout novo | | `network` | Falha de conexão do comprador | O comprador pode tentar de novo | | `expired`, `ended` | O checkout passou da validade ou foi encerrado | Crie um checkout novo | A tabela completa dos códigos, nas seis adquirentes, está em [Throne Elements](/docs/cartao/elements#codigos-de-erro). No caminho de navegador de app, nenhum desses eventos chega à sua página. ## Quando chega o webhook A confirmação da NovaCorp depende de o comprador voltar da tela do banco: - **Comprador volta da tela do banco** (o caminho normal): o webhook `transaction.paid` sai pouco depois da aprovação. - **Comprador fecha a tela do banco antes do fim**: a confirmação vem pela conferência automática do Throne, que pode levar alguns minutos. - **"Pagamento em análise"**: o comprador pode ver essa tela enquanto a confirmação sai. A venda continua `pending` e termina `paid` ou `failed`. Não cancele o pedido por falta de webhook antes da validade de 24 horas. Na dúvida, consulte `GET /transactions/{id}`. ## Depois da venda - **Estorno**: não é feito pela API; fale com o suporte. A venda estornada fica `refunded`. - **Contestação**: a venda vira `chargeback`. Se a contestação for revertida a seu favor, ela volta para `paid`. - Nenhum desses eventos gera webhook. Acompanhe pelo `GET /transactions/{id}`. ## Testar Use um cartão de verdade com valor baixo. Para ver como o comprador de outro país enxerga o checkout, abra o `checkout_url` da resposta com `?nl=` e o código do idioma (por exemplo, `?nl=de` para alemão): o formulário, os números e a tela de espera da verificação saem nesse idioma. Teste também dentro do navegador do Instagram ou do Facebook, no celular, e confira que o pedido é liberado pelo webhook. Roteiro completo em [Como testar](/docs/testar). ## Checklist - [ ] Checkout criado no servidor, com `Idempotency-Key` nova por venda e timeout de 60 segundos. - [ ] Moeda do comprador em `currency`, sem ajustar o valor no seu site. - [ ] Máximo da NovaCorp conferido na moeda do seu público, com plano para `422 invalid_amount` (outra adquirente ou pedido dividido). - [ ] Pagamento iniciado pelo clique do comprador, sem `sandbox` em volta do elemento (ou com a lista mínima acima). - [ ] Aviso ao lado do botão sobre a página segura em que o banco pode pedir a confirmação. - [ ] Tratamento de `e.detail.code` em `throne-elements:error`. - [ ] Pedido liberado só com `transaction.paid` (assinatura validada) ou com o `GET`. - [ ] Conversão de campanha enviada pelo servidor. - [ ] Teste no navegador do Instagram ou do Facebook, no celular. - [ ] Novo checkout para cada nova tentativa de compra depois de `failed` ou `expired`. --- # Cartão com a Eminence > Regras e comportamento do checkout quando a venda passa pela Eminence (acquirer eminence): verificação do banco dentro do elemento, navegador de app, pagamento em análise, códigos de erro e webhook. Fonte: https://app.thronecorporate.com/docs/cartao/eminence ## O que é igual às outras adquirentes A integração é a mesma do [guia de cartão](/docs/cartao): `POST /elements/checkout` na moeda do comprador, Throne Elements na página, webhook `transaction.paid`. Campos, respostas, erros, moedas e formato do webhook não mudam. Esta página cobre só o que é próprio da Eminence. **Para usar a Eminence**, mande `"acquirer": "eminence"` na criação do checkout. Se a Eminence for a padrão da sua conta, o campo é opcional. Veja [Escolher a adquirente](/docs/cartao/escolher). ```bash curl -X POST https://app.thronecorporate.com/api/v1/elements/checkout \ --max-time 60 \ -H "Authorization: Bearer $THRONE_PK:$THRONE_SK" \ -H "Content-Type: application/json" \ -H "Accept: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "amount_cents": 4990, "currency": "chf", "acquirer": "eminence", "customer": { "name": "Luca Meier", "email": "luca@example.ch" } }' ``` A resposta `201` traz `"acquirer": "eminence"`, e a venda fica na Eminence até o fim: pagamento, estorno e contestação. **A moeda funciona exatamente como nas outras adquirentes.** Você manda o valor na moeda do comprador (por exemplo, `"currency": "chf"`) e ele paga nessa moeda. A cotação que credita o seu saldo é fixada na criação, e o webhook traz `currency`, `native_amount_cents` e `fx_rate` no mesmo formato. Detalhes em [Moedas e limites](/docs/cartao/moeda). ## Resumo | Item | Na Eminence | |---|---| | Verificação do banco | Pode pedir; a tela do banco aparece **dentro do próprio elemento**, sem janela nova. Na maior parte das compras o banco não pede nada | | Navegador de app (Instagram, Facebook, Google, TikTok) | Tudo dentro do elemento; os eventos chegam normalmente | | Eventos na sua página | Em todos os caminhos: computador, celular e navegador de app | | Valor máximo por venda | Varia com a moeda e com a cotação do dia. Veja [Limites](#limites) | | Tentativas por checkout | 5, contadas no servidor. Recarregar a página não devolve tentativas | | Validade do checkout | 24 horas | | Fechamento por recusa | Na 5ª recusa, ou 10 minutos depois de uma recusa sem nova tentativa (`failed`). Uma recusa depois da análise antifraude ou de uma confirmação demorada fecha na hora | | Parcelas | À vista | | Idioma do checkout | Pelo país do comprador; Brasil e Portugal veem em inglês. `?nl=` no `checkout_url` mostra outro idioma, para teste | | Idioma da mensagem de recusa | O mesmo do checkout, pelo país do comprador (ou pelo `?nl=`), na tela e em `e.detail.message`. Não é preciso enviar `Accept-Language` na criação | | Códigos em `throne-elements:error` | `declined`, `attempts_exhausted`, `token`, `network`, `timeout`, `limit`, `expired`, `ended` | ## Limites | Limite | Na Eminence | |---|---| | Mínimo por venda | Varia com a moeda e com a cotação do dia, ou o mínimo próprio da Eminence, se houver | | Máximo por venda | Depende da adquirente e varia com a moeda e com a cotação do dia. Consulte `GET /elements/acquirers?currency=` (campo `limits.max_amount_cents`) | | Acima do máximo | `422 invalid_amount` com `max_amount_cents` na sua moeda e `alternatives`. Nada é criado | | Capacidade de criação | Em pico ou instabilidade, `503 temporarily_unavailable` com `Retry-After` | Se a Eminence não aceitar o valor da venda no cartão, o checkout fecha como `failed` na hora e a página recebe `limit` em `throne-elements:error`. ## Como o comprador paga ### Em qualquer aparelho 1. Preenche número, validade, CVV e nome impresso no cartão. 2. Clica em pagar. 3. Na maior parte das compras o banco não pede nada: o resultado sai em poucos segundos, dentro do próprio elemento. 4. Aprovado: o elemento mostra a confirmação e dispara `throne-elements:paid`. 5. Recusado: o elemento mostra o motivo, quantas tentativas restam e um botão para tentar de novo. A venda continua `pending`. ### Quando o banco pede verificação Alguns bancos pedem uma confirmação da compra (código por SMS, aplicativo do banco, pergunta de segurança). Na Eminence ela aparece **dentro do próprio elemento**, sem janela nova e sem sair da sua página. Quando o comprador conclui, o resultado aparece ali mesmo e os eventos chegam normalmente. - O elemento cresce para mostrar a tela do banco e depois o resultado. Por isso, **não fixe a altura do container**. - O comprador pode sair da verificação pelo botão **Cancelar**. Nada é cobrado por isso. A nova tentativa fica liberada quando o banco encerra a verificação pendente, o que pode levar alguns minutos; nesse intervalo o elemento avisa que está aguardando o banco. - A tela do banco segue o banco: idioma, desenho e tempo são dele. ### Em navegador de app Instagram, Facebook, o app do Google, TikTok e outros apps abrem links num navegador próprio, que não abre uma segunda janela. Na Eminence isso não faz diferença: a verificação acontece dentro do elemento, o comprador termina na sua página e `throne-elements:paid` chega como no computador. Mesmo assim, libere o pedido pelo webhook ou pelo `GET`, nunca só pelo evento da página. ### O que o comprador vê - **O endereço.** Nada muda: o comprador fica na sua página do começo ao fim, inclusive durante a verificação do banco. - **A confirmação do banco.** Quando o banco pede, a tela dele aparece dentro do elemento. Vale avisar ao lado do botão de pagar, para o comprador não estranhar. - **A fatura.** Na fatura do cartão, a compra aparece sem o nome da sua loja. Textos sugeridos para o aviso: ```text Inglês Secure payment. Your bank may ask you to confirm this purchase right here, on this page. ``` ```text Alemão Sichere Zahlung. Ihre Bank kann Sie bitten, diesen Kauf direkt hier auf dieser Seite zu bestätigen. ``` ```text Espanhol Pago seguro. Tu banco puede pedirte que confirmes esta compra aquí mismo, en esta página. ``` ## Requisitos da página - **Não fixe a altura do container** e não esconda o `overflow`: a verificação do banco e o resultado crescem o elemento. - Sem `sandbox` em volta do elemento. Se não der para tirar, use a lista mínima de [Throne Elements](/docs/cartao/elements#boas-praticas). - **O clique no botão de pagar precisa ser do comprador.** Não dispare o pagamento por código. - Se o seu site usa Content-Security-Policy, libere `https://app.thronecorporate.com` em `script-src` e em `img-src`, e em `frame-src` libere esse domínio e o domínio do `checkout_url`, como em [Throne Elements](/docs/cartao/elements#boas-praticas). ## Pagamento em análise Às vezes o pagamento entra em análise antifraude, ou a confirmação demora a sair. O comprador vê "Pagamento em análise", sem formulário, e a venda fica `pending` até a decisão: - aprovado, vira `paid` e o webhook `transaction.paid` é enviado; - reprovado na análise antifraude ou recusado depois de uma confirmação demorada, vira `failed` na hora, mesmo com tentativas sobrando. Se a decisão demorar, o elemento avisa que a confirmação pode levar alguns minutos e envia `timeout` em `throne-elements:error`. Isso não é recusa. Não libere o pedido enquanto não chegar `transaction.paid`, e não cancele o pedido só pelo `timeout`. ## Erros e o que fazer Na criação do checkout: | Situação | Resposta | O que fazer | |---|---|---| | Valor acima do máximo da Eminence | `422 invalid_amount`, com `max_amount_cents` e `alternatives` | Não repita. Crie com o `acquirer` de uma das `alternatives` ou divida o pedido | | Valor abaixo do mínimo | `422 invalid_amount`, com `min_amount_cents` | Aumente o valor | | Eminence não habilitada na conta | `422 acquirer_not_enabled`, com as habilitadas no `message` | Use uma das habilitadas, ou fale com o suporte para habilitar a Eminence | | Eminence em pausa, instável ou sem capacidade | `503 temporarily_unavailable`, com `Retry-After` e `"retryable": true` | Espere o `Retry-After` e repita com uma `Idempotency-Key` nova, ou use outra adquirente habilitada | | Criação recusada | `502 checkout_failed`, com `"retryable": false` | Não repita igual. Se continuar, fale com o suporte | | Falha passageira de conexão | `502 checkout_failed`, com `"retryable": true` | Repita com uma `Idempotency-Key` nova | | Sem resposta (timeout do seu lado) | Nenhuma | Repita com a **mesma** `Idempotency-Key` ([regra](/docs/comecar#como-repetir-uma-chamada-com-seguranca)) | Na página, em `throne-elements:error` (`e.detail.code`): | `code` | O que aconteceu | O que fazer | |---|---|---| | `declined` | O banco não autorizou | O comprador pode tentar de novo ou usar outro cartão. Vale sugerir que ele libere compras online e internacionais no app do banco | | `attempts_exhausted` | O checkout fechou: acabaram as 5 tentativas, ou a recusa veio depois da análise antifraude ou de uma confirmação demorada | Ofereça outro meio de pagamento ou um checkout novo | | `token` | Os dados do cartão não passaram na validação | O comprador confere os dados e tenta de novo. Quando a validação falha no próprio elemento, nenhuma tentativa é gasta; quando a Eminence recusa os dados depois do envio, a tentativa conta | | `network` | Falha de conexão do comprador, ou o formulário não carregou | O comprador pode tentar de novo | | `timeout` | A confirmação está demorando e o pagamento segue em análise, ou o comprador cancelou ou não concluiu a verificação do banco e ela ainda não foi encerrada | Espere o webhook ou consulte `GET /transactions/{id}`. Não é recusa; não crie outro checkout por isso | | `limit` | A Eminence não aceita o valor desta venda no cartão. O checkout fecha como `failed` | Crie um checkout novo com outra adquirente habilitada, ou ofereça outro meio de pagamento | | `expired`, `ended` | O checkout passou da validade ou foi encerrado | Crie um checkout novo | A tabela completa dos códigos, nas seis adquirentes, está em [Throne Elements](/docs/cartao/elements#codigos-de-erro). Duas tentativas ao mesmo tempo no mesmo checkout (duas abas ou duplo clique) não geram cobrança dupla: a segunda recebe "Já existe um pagamento em andamento. Aguarde." e o elemento acompanha o pagamento que já está em andamento. ### Tratar os eventos na página Na Eminence, dois códigos pedem um tratamento próprio: `timeout` é espera, não recusa; `limit` é troca de adquirente, não nova tentativa. ```js window.addEventListener('throne-elements:paid', (e) => { // e.detail.transaction_id: mostre o "obrigado". A liberação do pedido vem pelo webhook. }); window.addEventListener('throne-elements:error', (e) => { switch (e.detail.code) { case 'timeout': // o pagamento segue em análise: mostre "estamos confirmando" e espere o webhook break; case 'limit': case 'attempts_exhausted': case 'expired': case 'ended': // este checkout acabou: ofereça outro meio ou crie um checkout novo (no limit, com outra adquirente) break; default: // declined, token, network: o próprio elemento já oferece a nova tentativa break; } }); ``` ## Quando chega o webhook Quando o banco não pede verificação, o resultado sai na hora e o webhook `transaction.paid` costuma sair logo depois da aprovação. Quando pede, ele sai depois que o comprador conclui a verificação. Numa venda que entra em análise, ele sai quando a análise aprova. Se a confirmação da Eminence se perder no caminho, a conferência automática do Throne a recupera alguns minutos depois. Não cancele o pedido por falta de webhook antes da validade de 24 horas; na dúvida, consulte `GET /transactions/{id}`. O webhook é o mesmo das outras adquirentes: `currency` e `native_amount_cents` com a venda na moeda do comprador, `amount_cents` com o valor creditado no seu saldo e `fx_rate` com a cotação fixada na criação. Veja [Webhooks](/docs/webhooks#quando-o-webhook-chega). ## Depois da venda - **Estorno**: não é feito pela API; fale com o suporte. O estorno total deixa a venda `refunded`. O parcial mantém `paid`, com os valores reduzidos. - **Contestação**: o Throne debita da sua carteira o valor líquido da venda, e ela vira `chargeback`. Se a contestação for revertida a seu favor, ela volta para `paid`. - Nenhum desses eventos gera webhook. Acompanhe pelo `GET /transactions/{id}`. ## Testar Use um cartão de verdade com valor baixo. Para ver como o comprador de outro país enxerga o checkout, abra o `checkout_url` da resposta com `?nl=` e o código do idioma (por exemplo, `?nl=de` para alemão): o formulário, os números e as telas do elemento saem nesse idioma. A tela do próprio banco, na verificação, segue o banco. Teste também dentro do navegador do Instagram ou do Facebook, no celular: na Eminence o resultado aparece na sua página e o evento chega. Os erros que dá para provocar sem custo e o roteiro completo estão em [Como testar](/docs/testar). ## Checklist - [ ] Checkout criado no servidor, com `"acquirer": "eminence"` (ou a Eminence como padrão da conta), `Idempotency-Key` nova por venda e timeout de 60 segundos. - [ ] Moeda do comprador em `currency`, sem ajustar o valor no seu site. - [ ] Máximo da Eminence conferido na moeda do seu público, com plano para `422 invalid_amount` (outra adquirente ou pedido dividido). - [ ] Container sem altura fixa e sem `sandbox` em volta do elemento (ou com a lista mínima). - [ ] Aviso ao lado do botão de que o banco pode pedir a confirmação ali mesmo. - [ ] Tratamento de `e.detail.code` em `throne-elements:error`, com `timeout` tratado como espera e `limit` como troca de adquirente. - [ ] Pedido liberado só com `transaction.paid` (assinatura validada) ou com o `GET`. - [ ] Novo checkout para cada nova tentativa de compra depois de `failed` ou `expired`. --- # Cartão com a Apex > Regras e comportamento do checkout quando a venda passa pela Apex (acquirer apex): sem verificação do banco, tudo dentro do elemento, navegador de app, pagamento em análise, códigos de erro e webhook. Fonte: https://app.thronecorporate.com/docs/cartao/apex ## O que é igual às outras adquirentes A integração é a mesma do [guia de cartão](/docs/cartao): `POST /elements/checkout` na moeda do comprador, Throne Elements na página, webhook `transaction.paid`. Campos, respostas, erros, moedas e formato do webhook não mudam. Esta página cobre só o que é próprio da Apex. **Para usar a Apex**, mande `"acquirer": "apex"` na criação do checkout. Se a Apex for a padrão da sua conta, o campo é opcional. Veja [Escolher a adquirente](/docs/cartao/escolher). ```bash curl -X POST https://app.thronecorporate.com/api/v1/elements/checkout \ --max-time 60 \ -H "Authorization: Bearer $THRONE_PK:$THRONE_SK" \ -H "Content-Type: application/json" \ -H "Accept: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "amount_cents": 4990, "currency": "chf", "acquirer": "apex", "customer": { "name": "Luca Meier", "email": "luca@example.ch" } }' ``` A resposta `201` traz `"acquirer": "apex"`, e a venda fica na Apex até o fim: pagamento, estorno e contestação. **A moeda funciona exatamente como nas outras adquirentes.** Você manda o valor na moeda do comprador (por exemplo, `"currency": "chf"`) e ele paga nessa moeda. A cotação que credita o seu saldo é fixada na criação, e o webhook traz `currency`, `native_amount_cents` e `fx_rate` no mesmo formato. Detalhes em [Moedas e limites](/docs/cartao/moeda). A Apex é a única adquirente que aceita **recorrência** (cobrança que se repete sozinha no cartão) e é onde o **upsell em um clique** está disponível. As duas coisas são campos opcionais do mesmo `POST /elements/checkout`: veja [Recorrência](/docs/cartao/recorrencias) e [Upsell em um clique](/docs/cartao/oneclick). ## Resumo | Item | Na Apex | |---|---| | Verificação do banco | Não tem: o banco aprova ou recusa na hora, e o resultado aparece dentro do próprio elemento | | Navegador de app (Instagram, Facebook, Google, TikTok) | Tudo dentro do elemento; os eventos chegam normalmente | | Eventos na sua página | Em todos os caminhos: computador, celular e navegador de app | | Valor máximo por venda | Varia com a moeda e com a cotação do dia. Veja [Limites](#limites) | | Tentativas por checkout | 5, contadas no servidor. Recarregar a página não devolve tentativas | | Validade do checkout | 24 horas | | Fechamento por recusa | Na 5ª recusa, ou 10 minutos depois de uma recusa sem nova tentativa (`failed`). Um valor que a Apex não aceita fecha na hora | | Parcelas | À vista | | Recorrência | Aceita, nos sete ciclos de `weekly` a `yearly`. Veja [Recorrência](/docs/cartao/recorrencias) | | Upsell em um clique | Disponível, com habilitação por conta. Veja [Upsell em um clique](/docs/cartao/oneclick) | | Idioma do checkout | Pelo país do comprador; Brasil e Portugal veem em inglês. `?nl=` no `checkout_url` mostra outro idioma, para teste | | Idioma da mensagem de recusa | O mesmo do checkout, pelo país do comprador (ou pelo `?nl=`), na tela e em `e.detail.message`. Não é preciso enviar `Accept-Language` na criação | | Códigos em `throne-elements:error` | `declined`, `attempts_exhausted`, `token`, `network`, `timeout`, `limit`, `expired`, `ended` | ## Limites | Limite | Na Apex | |---|---| | Mínimo por venda | Varia com a moeda e com a cotação do dia, ou o mínimo próprio da Apex, se houver | | Máximo por venda | Depende da adquirente e varia com a moeda e com a cotação do dia. Consulte `GET /elements/acquirers?currency=` (campo `limits.max_amount_cents`) | | Acima do máximo | `422 invalid_amount` com `max_amount_cents` na sua moeda e `alternatives`. Nada é criado | | Capacidade de criação | Em pico ou instabilidade, `503 temporarily_unavailable` com `Retry-After` | Se a Apex não aceitar o valor da venda no cartão, o checkout fecha como `failed` na hora e a página recebe `limit` em `throne-elements:error`. ## Como o comprador paga ### Em qualquer aparelho 1. Preenche número, validade, CVV e nome impresso no cartão. 2. Clica em pagar. 3. O resultado sai em poucos segundos, dentro do próprio elemento. Não há etapa do banco nem janela nova. 4. Aprovado: o elemento mostra a confirmação e dispara `throne-elements:paid`. 5. Recusado: o elemento mostra o motivo, quantas tentativas restam e um botão para tentar de novo. A venda continua `pending`. ### Em navegador de app Instagram, Facebook, o app do Google, TikTok e outros apps abrem links num navegador próprio, que não abre uma segunda janela. Na Apex isso não faz diferença: o pagamento acontece todo dentro do elemento, o comprador termina na sua página e `throne-elements:paid` chega como no computador. Mesmo assim, libere o pedido pelo webhook ou pelo `GET`, nunca só pelo evento da página. ### O que o comprador vê - **O endereço.** Nada muda: o comprador fica na sua página do começo ao fim. - **Nenhuma confirmação do banco.** Não há código por mensagem, aplicativo do banco nem pergunta de segurança: o cartão é autorizado na hora. Um aviso ao lado do botão de pagar é opcional. - **A fatura.** Na fatura do cartão, a compra aparece sem o nome da sua loja. Textos sugeridos, se quiser avisar: ```text Inglês Secure payment. You will see the result right here, on this page. ``` ```text Alemão Sichere Zahlung. Das Ergebnis erscheint direkt hier auf dieser Seite. ``` ```text Espanhol Pago seguro. Verás el resultado aquí mismo, en esta página. ``` ## Requisitos da página - **Não fixe a altura do container** e não esconda o `overflow`: as telas de resultado e de análise mudam a altura do elemento. - Sem `sandbox` em volta do elemento. Se não der para tirar, use a lista mínima de [Throne Elements](/docs/cartao/elements#boas-praticas). - **O clique no botão de pagar precisa ser do comprador.** Não dispare o pagamento por código. - Se o seu site usa Content-Security-Policy, libere `https://app.thronecorporate.com` em `script-src` e em `img-src`, e em `frame-src` libere esse domínio e o domínio do `checkout_url`, como em [Throne Elements](/docs/cartao/elements#boas-praticas). ## Pagamento em análise Às vezes o pagamento entra em análise antifraude. O comprador vê "Pagamento em análise", sem formulário, e a venda fica `pending` até a decisão: - aprovado, vira `paid` e o webhook `transaction.paid` é enviado; - reprovado, conta como uma recusa: a tentativa é gasta, o elemento mostra o motivo e, com tentativas sobrando, o comprador pode tentar de novo. A venda continua `pending`; na 5ª recusa o checkout fecha como `failed`. Se a decisão demorar, o elemento avisa que a confirmação pode levar alguns minutos e envia `timeout` em `throne-elements:error`. Isso não é recusa. Não libere o pedido enquanto não chegar `transaction.paid`, e não cancele o pedido só pelo `timeout`. ## Erros e o que fazer Na criação do checkout: | Situação | Resposta | O que fazer | |---|---|---| | Valor acima do máximo da Apex | `422 invalid_amount`, com `max_amount_cents` e `alternatives` | Não repita. Crie com o `acquirer` de uma das `alternatives` ou divida o pedido | | Valor abaixo do mínimo | `422 invalid_amount`, com `min_amount_cents` | Aumente o valor | | Apex não habilitada na conta | `422 acquirer_not_enabled`, com as habilitadas no `message` | Use uma das habilitadas, ou fale com o suporte para habilitar a Apex | | Apex em pausa, instável ou sem capacidade | `503 temporarily_unavailable`, com `Retry-After` e `"retryable": true` | Espere o `Retry-After` e repita com uma `Idempotency-Key` nova, ou use outra adquirente habilitada | | Criação recusada | `502 checkout_failed`, com `"retryable": false` | Não repita igual. Se continuar, fale com o suporte | | Falha passageira de conexão | `502 checkout_failed`, com `"retryable": true` | Repita com uma `Idempotency-Key` nova | | Sem resposta (timeout do seu lado) | Nenhuma | Repita com a **mesma** `Idempotency-Key` ([regra](/docs/comecar#como-repetir-uma-chamada-com-seguranca)) | Na página, em `throne-elements:error` (`e.detail.code`): | `code` | O que aconteceu | O que fazer | |---|---|---| | `declined` | O banco não autorizou | O comprador pode tentar de novo ou usar outro cartão. Vale sugerir que ele libere compras online e internacionais no app do banco | | `attempts_exhausted` | O checkout fechou: acabaram as 5 tentativas | Ofereça outro meio de pagamento ou um checkout novo | | `token` | Os dados do cartão não passaram na validação, antes da cobrança | O comprador confere os dados e tenta de novo. Nenhuma tentativa é gasta | | `network` | Falha de conexão do comprador, ou o formulário não carregou | O comprador pode tentar de novo | | `timeout` | A análise antifraude está demorando e o pagamento segue em análise | Espere o webhook ou consulte `GET /transactions/{id}`. Não é recusa; não crie outro checkout por isso | | `limit` | A Apex não aceita o valor desta venda no cartão. O checkout fecha como `failed` | Crie um checkout novo com outra adquirente habilitada, ou ofereça outro meio de pagamento | | `expired`, `ended` | O checkout passou da validade ou foi encerrado | Crie um checkout novo | A tabela completa dos códigos, nas seis adquirentes, está em [Throne Elements](/docs/cartao/elements#codigos-de-erro). Duas tentativas ao mesmo tempo no mesmo checkout (duas abas ou duplo clique) não geram cobrança dupla: a segunda recebe "Já existe um pagamento em andamento. Aguarde." e o elemento acompanha o pagamento que já está em andamento. ### Tratar os eventos na página Na Apex, dois códigos pedem um tratamento próprio: `timeout` é espera, não recusa; `limit` é troca de adquirente, não nova tentativa. ```js window.addEventListener('throne-elements:paid', (e) => { // e.detail.transaction_id: mostre o "obrigado". A liberação do pedido vem pelo webhook. }); window.addEventListener('throne-elements:error', (e) => { switch (e.detail.code) { case 'timeout': // o pagamento segue em análise: mostre "estamos confirmando" e espere o webhook break; case 'limit': case 'attempts_exhausted': case 'expired': case 'ended': // este checkout acabou: ofereça outro meio ou crie um checkout novo (no limit, com outra adquirente) break; default: // declined, token, network: o próprio elemento já oferece a nova tentativa break; } }); ``` ## Quando chega o webhook O resultado sai na hora e o webhook `transaction.paid` costuma sair logo depois da aprovação. Numa venda que entra em análise, ele sai quando a análise aprova. Se a confirmação da Apex se perder no caminho, a conferência automática do Throne a recupera alguns minutos depois. Não cancele o pedido por falta de webhook antes da validade de 24 horas; na dúvida, consulte `GET /transactions/{id}`. O webhook é o mesmo das outras adquirentes: `currency` e `native_amount_cents` com a venda na moeda do comprador, `amount_cents` com o valor creditado no seu saldo e `fx_rate` com a cotação fixada na criação. Veja [Webhooks](/docs/webhooks#quando-o-webhook-chega). ## Depois da venda - **Estorno**: não é feito pela API; fale com o suporte. O estorno total deixa a venda `refunded`. O parcial mantém `paid`, com os valores reduzidos. - **Contestação**: o Throne debita da sua carteira o valor líquido da venda, e ela vira `chargeback`. Se a contestação for revertida a seu favor, ela volta para `paid`. - Nenhum desses eventos gera webhook. Acompanhe pelo `GET /transactions/{id}`. ## Testar Use um cartão de verdade com valor baixo. Para ver como o comprador de outro país enxerga o checkout, abra o `checkout_url` da resposta com `?nl=` e o código do idioma (por exemplo, `?nl=de` para alemão): o formulário, os números e as telas do elemento saem nesse idioma. Teste também dentro do navegador do Instagram ou do Facebook, no celular: na Apex o resultado aparece na sua página e o evento chega. Os erros que dá para provocar sem custo e o roteiro completo estão em [Como testar](/docs/testar). ## Checklist - [ ] Checkout criado no servidor, com `"acquirer": "apex"` (ou a Apex como padrão da conta), `Idempotency-Key` nova por venda e timeout de 60 segundos. - [ ] Moeda do comprador em `currency`, sem ajustar o valor no seu site. - [ ] Máximo da Apex conferido na moeda do seu público, com plano para `422 invalid_amount` (outra adquirente ou pedido dividido). - [ ] Container sem altura fixa e sem `sandbox` em volta do elemento (ou com a lista mínima). - [ ] Tratamento de `e.detail.code` em `throne-elements:error`, com `timeout` tratado como espera e `limit` como troca de adquirente. - [ ] Pedido liberado só com `transaction.paid` (assinatura validada) ou com o `GET`. - [ ] Novo checkout para cada nova tentativa de compra depois de `failed` ou `expired`. --- # Cartão com a ShadowBank > Regras e comportamento do checkout quando a venda passa pela ShadowBank (acquirer shadowbank): sem verificação do banco, tudo dentro do elemento, navegador de app, pagamento em análise, códigos de erro e webhook. Fonte: https://app.thronecorporate.com/docs/cartao/shadowbank ## O que é igual às outras adquirentes A integração é a mesma do [guia de cartão](/docs/cartao): `POST /elements/checkout` na moeda do comprador, Throne Elements na página, webhook `transaction.paid`. Campos, respostas, erros, moedas e formato do webhook não mudam. Esta página cobre só o que é próprio da ShadowBank. **Para usar a ShadowBank**, mande `"acquirer": "shadowbank"` na criação do checkout. Se a ShadowBank for a padrão da sua conta, o campo é opcional. A ShadowBank é habilitada pelo suporte, conta a conta. Veja [Escolher a adquirente](/docs/cartao/escolher). **Nome e e-mail do comprador são obrigatórios na criação.** Mande `customer.name` (até 120 caracteres) e `customer.email` (e-mail válido, até 180). Sem eles, ou fora desses tamanhos, a chamada volta `422 customer_required_for_shadowbank` e nada é criado. O elemento nunca pergunta nome nem e-mail ao comprador: colete os dois na sua página antes de chamar a API. A regra vale sempre que a venda fica com a ShadowBank, inclusive sem o campo `acquirer`, quando ela é a padrão da conta. Nas outras adquirentes, `customer` continua opcional. O exemplo abaixo já manda os dois campos. ```bash curl -X POST https://app.thronecorporate.com/api/v1/elements/checkout \ --max-time 60 \ -H "Authorization: Bearer $THRONE_PK:$THRONE_SK" \ -H "Content-Type: application/json" \ -H "Accept: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "amount_cents": 4990, "currency": "eur", "acquirer": "shadowbank", "customer": { "name": "Lena Fischer", "email": "lena@example.de" } }' ``` A resposta `201` traz `"acquirer": "shadowbank"`, e a venda fica na ShadowBank até o fim: pagamento, estorno e contestação. **A moeda funciona exatamente como nas outras adquirentes.** Você manda o valor na moeda do comprador (por exemplo, `"currency": "eur"`) e ele paga nessa moeda. A cotação que credita o seu saldo é fixada na criação, e o webhook traz `currency`, `native_amount_cents` e `fx_rate` no mesmo formato. Detalhes em [Moedas e limites](/docs/cartao/moeda). No **checkout comum** (Throne Elements), a ShadowBank não faz recorrência nem upsell em um clique e não atende o [Checkout Brasil](/docs/cartao/brasil). Com `"acquirer": "shadowbank"`, o campo `recurrence` volta `422 recurrence_not_supported`, o `one_click_from` volta `one_click: false`, sem erro, e o `POST /elements/checkout/br` volta `422 acquirer_not_enabled`. Na **[integração direta](/docs/cartao/shadowbank-direto)**, que é outra modalidade e precisa de liberação à parte, as duas coisas funcionam: cobrança recorrente e upsell em um clique, e o upsell também pode ser recorrente. O que não existe lá é o parcelamento — a cobrança é sempre à vista. ## Resumo | Item | Na ShadowBank | |---|---| | Verificação do banco | Não tem: o banco aprova ou recusa na hora, e o resultado aparece dentro do próprio elemento | | Navegador de app (Instagram, Facebook, Google, TikTok) | Tudo dentro do elemento; os eventos chegam normalmente | | Eventos na sua página | Em todos os caminhos: computador, celular e navegador de app | | Valor máximo por venda | Varia com a moeda e com a cotação do dia. Veja [Limites](#limites) | | Tentativas por checkout | 5, contadas no servidor. Recarregar a página não devolve tentativas | | Validade do checkout | 24 horas | | Fechamento por recusa | Na 5ª recusa, ou 10 minutos depois de uma recusa sem nova tentativa (`failed`) | | Parcelas | À vista | | Recorrência | Não | | Upsell em um clique | Não | | Dados do comprador | `customer.name` e `customer.email` obrigatórios na criação. O elemento não os pergunta | | Idioma do checkout | Pelo país do comprador; Brasil e Portugal veem em inglês. `?nl=` no `checkout_url` mostra outro idioma, para teste | | Idioma da mensagem de recusa | O mesmo do checkout, pelo país do comprador (ou pelo `?nl=`), na tela e em `e.detail.message`. Não é preciso enviar `Accept-Language` na criação | | Códigos em `throne-elements:error` | `declined`, `attempts_exhausted`, `token`, `network`, `timeout`, `expired`, `ended` | ## Limites | Limite | Na ShadowBank | |---|---| | Mínimo por venda | Varia com a moeda e com a cotação do dia, ou o mínimo próprio da ShadowBank, se houver | | Máximo por venda | Depende da adquirente e varia com a moeda e com a cotação do dia. Consulte `GET /elements/acquirers?currency=` (campo `limits.max_amount_cents`) | | Acima do máximo | `422 invalid_amount` com `max_amount_cents` na sua moeda e `alternatives`. Nada é criado | | Capacidade de criação | Em pico ou instabilidade, `503 temporarily_unavailable` com `Retry-After` | ## Como o comprador paga ### Em qualquer aparelho 1. Preenche número, validade, CVV e nome impresso no cartão. 2. Clica em pagar. 3. O resultado sai em poucos segundos, dentro do próprio elemento. Não há etapa do banco nem janela nova. 4. Aprovado: o elemento mostra a confirmação e dispara `throne-elements:paid`. 5. Recusado: o elemento mostra o motivo, quantas tentativas restam e um botão para tentar de novo. A venda continua `pending`. ### Em navegador de app Instagram, Facebook, o app do Google, TikTok e outros apps abrem links num navegador próprio, que não abre uma segunda janela. Na ShadowBank isso não faz diferença: o pagamento acontece todo dentro do elemento, o comprador termina na sua página e `throne-elements:paid` chega como no computador. Mesmo assim, libere o pedido pelo webhook ou pelo `GET`, nunca só pelo evento da página. ### O que o comprador vê - **O endereço.** Nada muda: o comprador fica na sua página do começo ao fim. - **Nenhuma confirmação do banco.** Não há código por mensagem, aplicativo do banco nem pergunta de segurança: o cartão é autorizado na hora. Um aviso ao lado do botão de pagar é opcional. - **A fatura.** Na fatura do cartão, a compra aparece sem o nome da sua loja. Textos sugeridos, se quiser avisar: ```text Inglês Secure payment. You will see the result right here, on this page. ``` ```text Alemão Sichere Zahlung. Das Ergebnis erscheint direkt hier auf dieser Seite. ``` ```text Espanhol Pago seguro. Verás el resultado aquí mismo, en esta página. ``` ## Requisitos da página - **Não fixe a altura do container** e não esconda o `overflow`: as telas de resultado e de análise mudam a altura do elemento. - Sem `sandbox` em volta do elemento. Se não der para tirar, use a lista mínima de [Throne Elements](/docs/cartao/elements#boas-praticas). - **O clique no botão de pagar precisa ser do comprador.** Não dispare o pagamento por código. - Se o seu site usa Content-Security-Policy, libere `https://app.thronecorporate.com` em `script-src` e em `img-src`, e em `frame-src` libere esse domínio e o domínio do `checkout_url`, como em [Throne Elements](/docs/cartao/elements#boas-praticas). ## Pagamento em análise Às vezes o pagamento entra em análise antifraude. O comprador vê "Pagamento em análise", sem formulário, e a venda fica `pending` até a decisão: - aprovado, vira `paid` e o webhook `transaction.paid` é enviado; - reprovado, conta como uma recusa: a tentativa é gasta, o elemento mostra o motivo e, com tentativas sobrando, o comprador pode tentar de novo. A venda continua `pending`; na 5ª recusa o checkout fecha como `failed`. Se a decisão demorar, o elemento avisa que a confirmação pode levar alguns minutos e envia `timeout` em `throne-elements:error`. Isso não é recusa. Não libere o pedido enquanto não chegar `transaction.paid`, e não cancele o pedido só pelo `timeout`. ## Erros e o que fazer Na criação do checkout: | Situação | Resposta | O que fazer | |---|---|---| | Valor acima do máximo da ShadowBank | `422 invalid_amount`, com `max_amount_cents` e `alternatives` | Não repita. Crie com o `acquirer` de uma das `alternatives` ou divida o pedido | | Valor abaixo do mínimo | `422 invalid_amount`, com `min_amount_cents` | Aumente o valor | | Sem `customer.name` ou `customer.email`, e-mail inválido ou fora dos tamanhos (nome até 120 caracteres, e-mail até 180) | `422 customer_required_for_shadowbank` | Mande os dois campos na criação, dentro dos tamanhos. Nada é criado | | ShadowBank não habilitada na conta | `422 acquirer_not_enabled`, com as habilitadas no `message` | Use uma das habilitadas, ou fale com o suporte para habilitar a ShadowBank | | ShadowBank em pausa, instável ou sem capacidade | `503 temporarily_unavailable`, com `Retry-After` e `"retryable": true` | Espere o `Retry-After` e repita com uma `Idempotency-Key` nova, ou use outra adquirente habilitada | | Criação recusada | `502 checkout_failed`, com `"retryable": false` | Não repita igual. Se continuar, fale com o suporte | | Falha passageira de conexão | `502 checkout_failed`, com `"retryable": true` | Repita com uma `Idempotency-Key` nova | | Sem resposta (timeout do seu lado) | Nenhuma | Repita com a **mesma** `Idempotency-Key` ([regra](/docs/comecar#como-repetir-uma-chamada-com-seguranca)) | Na página, em `throne-elements:error` (`e.detail.code`): | `code` | O que aconteceu | O que fazer | |---|---|---| | `declined` | O banco não autorizou | O comprador pode tentar de novo ou usar outro cartão. Vale sugerir que ele libere compras online e internacionais no app do banco | | `attempts_exhausted` | O checkout fechou: acabaram as 5 tentativas | Ofereça outro meio de pagamento ou um checkout novo | | `token` | Os dados do cartão não passaram na validação, antes da cobrança | O comprador confere os dados e tenta de novo. Nenhuma tentativa é gasta | | `network` | Falha de conexão, ou o formulário não carregou | O comprador pode tentar de novo em alguns instantes | | `timeout` | A análise antifraude está demorando e o pagamento segue em análise | Espere o webhook ou consulte `GET /transactions/{id}`. Não é recusa; não crie outro checkout por isso | | `expired`, `ended` | O checkout passou da validade ou foi encerrado | Crie um checkout novo | A tabela completa dos códigos, nas seis adquirentes, está em [Throne Elements](/docs/cartao/elements#codigos-de-erro). Duas tentativas ao mesmo tempo no mesmo checkout (duas abas ou duplo clique) não geram cobrança dupla: a segunda recebe "Já existe um pagamento em andamento. Aguarde." e o elemento acompanha o pagamento que já está em andamento. ### Tratar os eventos na página Na ShadowBank, um código pede tratamento próprio: `timeout` é espera, não recusa. ```js window.addEventListener('throne-elements:paid', (e) => { // e.detail.transaction_id: mostre o "obrigado". A liberação do pedido vem pelo webhook. }); window.addEventListener('throne-elements:error', (e) => { switch (e.detail.code) { case 'timeout': // o pagamento segue em análise: mostre "estamos confirmando" e espere o webhook break; case 'attempts_exhausted': case 'expired': case 'ended': // este checkout acabou: ofereça outro meio ou crie um checkout novo break; default: // declined, token, network: o próprio elemento já oferece a nova tentativa break; } }); ``` ## Quando chega o webhook O resultado sai na hora e o webhook `transaction.paid` costuma sair logo depois da aprovação. Numa venda que entra em análise, ele sai quando a análise aprova. Se a confirmação da ShadowBank se perder no caminho, a conferência automática do Throne a recupera alguns minutos depois. Não cancele o pedido por falta de webhook antes da validade de 24 horas; na dúvida, consulte `GET /transactions/{id}`. O webhook é o mesmo das outras adquirentes: `currency` e `native_amount_cents` com a venda na moeda do comprador, `amount_cents` com o valor creditado no seu saldo e `fx_rate` com a cotação fixada na criação. Veja [Webhooks](/docs/webhooks#quando-o-webhook-chega). ## Depois da venda - **Estorno**: não é feito pela API; fale com o suporte. O estorno total deixa a venda `refunded`. O parcial mantém `paid`, com os valores reduzidos. - **Contestação**: o Throne debita da sua carteira o valor líquido da venda, e ela vira `chargeback`. Se a contestação for revertida a seu favor, ela volta para `paid`. - Nenhum desses eventos gera webhook. Acompanhe pelo `GET /transactions/{id}`. ## Testar Use um cartão de verdade com valor baixo. Para ver como o comprador de outro país enxerga o checkout, abra o `checkout_url` da resposta com `?nl=` e o código do idioma (por exemplo, `?nl=de` para alemão): o formulário, os números e as telas do elemento saem nesse idioma. Teste também dentro do navegador do Instagram ou do Facebook, no celular: na ShadowBank o resultado aparece na sua página e o evento chega. Os erros que dá para provocar sem custo e o roteiro completo estão em [Como testar](/docs/testar). ## Checklist - [ ] Checkout criado no servidor, com `"acquirer": "shadowbank"` (ou a ShadowBank como padrão da conta), `Idempotency-Key` nova por venda e timeout de 60 segundos. - [ ] `customer.name` e `customer.email` (e-mail válido) enviados na criação, coletados na sua página antes de chamar a API. - [ ] Moeda do comprador em `currency`, sem ajustar o valor no seu site. - [ ] Máximo da ShadowBank conferido na moeda do seu público, com plano para `422 invalid_amount` (outra adquirente ou pedido dividido). - [ ] Container sem altura fixa e sem `sandbox` em volta do elemento (ou com a lista mínima). - [ ] Tratamento de `e.detail.code` em `throne-elements:error`, com `timeout` tratado como espera. - [ ] Pedido liberado só com `transaction.paid` (assinatura validada) ou com o `GET`. - [ ] Novo checkout para cada nova tentativa de compra depois de `failed` ou `expired`. --- # Cartão com a Vortex > Regras e comportamento do checkout quando a venda passa pela Vortex (acquirer vortex): sem verificação do banco, tudo dentro do elemento, navegador de app, pagamento em análise, códigos de erro e webhook. Fonte: https://app.thronecorporate.com/docs/cartao/vortex ## O que é igual às outras adquirentes A integração é a mesma do [guia de cartão](/docs/cartao): `POST /elements/checkout` na moeda do comprador, Throne Elements na página, webhook `transaction.paid`. Campos, respostas, erros, moedas e formato do webhook não mudam. Esta página cobre só o que é próprio da Vortex. **Para usar a Vortex**, mande `"acquirer": "vortex"` na criação do checkout. Se a Vortex for a padrão da sua conta, o campo é opcional. A Vortex é habilitada pelo suporte, conta a conta. Veja [Escolher a adquirente](/docs/cartao/escolher). **Nome e e-mail do comprador são obrigatórios na criação.** Mande `customer.name` (até 120 caracteres) e `customer.email` (e-mail válido, até 180). Sem eles, ou fora desses tamanhos, a chamada volta `422 customer_required_for_vortex` e nada é criado. O elemento nunca pergunta nome nem e-mail ao comprador: colete os dois na sua página antes de chamar a API. A regra vale sempre que a venda fica com a Vortex, inclusive sem o campo `acquirer`, quando ela é a padrão da conta. Nas outras adquirentes, `customer` continua opcional. O exemplo abaixo já manda os dois campos. ```bash curl -X POST https://app.thronecorporate.com/api/v1/elements/checkout \ --max-time 60 \ -H "Authorization: Bearer $THRONE_PK:$THRONE_SK" \ -H "Content-Type: application/json" \ -H "Accept: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "amount_cents": 4990, "currency": "eur", "acquirer": "vortex", "customer": { "name": "Lena Fischer", "email": "lena@example.de" } }' ``` A resposta `201` traz `"acquirer": "vortex"`, e a venda fica na Vortex até o fim: pagamento, estorno e contestação. **A aprovação nem sempre é imediata.** O banco do comprador autoriza na hora, mas a venda ainda passa por uma análise de risco antes de virar paga. Quando isso acontece, o elemento mostra o pagamento em análise, a venda fica `pending` e o `transaction.paid` chega quando a análise libera, em geral em segundos. Uma reprovação na análise fecha a venda como recusada e conta como tentativa. Não trate `pending` como falha: espere o webhook. **A moeda funciona exatamente como nas outras adquirentes.** Você manda o valor na moeda do comprador (por exemplo, `"currency": "eur"`) e ele paga nessa moeda. A cotação que credita o seu saldo é fixada na criação, e o webhook traz `currency`, `native_amount_cents` e `fx_rate` no mesmo formato. Detalhes em [Moedas e limites](/docs/cartao/moeda). Existe também a [integração direta por API](/docs/cartao/vortex-direto), sem o Throne Elements, para quem já tem checkout próprio. Ela é liberada caso a caso; sem a liberação, a Vortex habilitada na conta dá acesso ao checkout comum desta página. **A Vortex faz cobrança recorrente**, nos sete ciclos de `weekly` a `yearly`: mande `recurrence` na criação do checkout e as cobranças seguintes saem sozinhas, no mesmo cartão. Veja [Recorrência na Vortex](#recorrencia), mais abaixo, e o [guia de Recorrência](/docs/cartao/recorrencias). **A Vortex faz upsell em um clique na integração direta**, e só nela: o [`POST /api/v1/vortex/upsell`](/docs/cartao/vortex-direto#upsell-em-um-clique) cobra um segundo produto no mesmo cartão de uma venda paga, sem pedir o cartão de novo. No checkout do Throne, este, o `one_click_from` com `"acquirer": "vortex"` ainda volta `one_click: false`, sem erro: a página hospedada desta adquirente não sabe cobrar o cartão guardado. A Vortex não atende o [Checkout Brasil](/docs/cartao/brasil): o `POST /elements/checkout/br` volta `422 acquirer_not_enabled`. ## Resumo | Item | Na Vortex | |---|---| | Verificação do banco | Não tem. O banco autoriza ou recusa na hora, e o resultado aparece dentro do próprio elemento | | Pagamento em análise | Pode acontecer: autorizada pelo banco, a venda ainda passa por uma análise de risco. Nesse intervalo ela fica `pending`, e o `transaction.paid` chega quando a análise libera | | Navegador de app (Instagram, Facebook, Google, TikTok) | Tudo dentro do elemento; os eventos chegam normalmente | | Eventos na sua página | Em todos os caminhos: computador, celular e navegador de app | | Valor máximo por venda | Varia com a moeda e com a cotação do dia. Veja [Limites](#limites) | | Tentativas por checkout | 5, contadas no servidor. Recarregar a página não devolve tentativas | | Validade do checkout | 24 horas | | Fechamento por recusa | Na 5ª recusa, ou 10 minutos depois de uma recusa sem nova tentativa (`failed`) | | Parcelas | À vista | | Recorrência | Sim, nos sete ciclos de `weekly` a `yearly`. A assinatura só começa a valer quando a primeira cobrança aprova. Veja [Recorrência](#recorrencia) | | Upsell em um clique | Não | | Dados do comprador | `customer.name` e `customer.email` obrigatórios na criação. O elemento não os pergunta | | Idioma do checkout | Pelo país do comprador; Brasil e Portugal veem em inglês. `?nl=` no `checkout_url` mostra outro idioma, para teste | | Idioma da mensagem de recusa | O mesmo do checkout, pelo país do comprador (ou pelo `?nl=`), na tela e em `e.detail.message`. Não é preciso enviar `Accept-Language` na criação | | Códigos em `throne-elements:error` | `declined`, `attempts_exhausted`, `token`, `network`, `timeout`, `expired`, `ended` | ## Limites | Limite | Na Vortex | |---|---| | Mínimo por venda | Varia com a moeda e com a cotação do dia, ou o mínimo próprio da Vortex, se houver | | Máximo por venda | Depende da adquirente e varia com a moeda e com a cotação do dia. Consulte `GET /elements/acquirers?currency=` (campo `limits.max_amount_cents`) | | Acima do máximo | `422 invalid_amount` com `max_amount_cents` na sua moeda e `alternatives`. Nada é criado | | Capacidade de criação | Em pico ou instabilidade, `503 temporarily_unavailable` com `Retry-After` | ## Como o comprador paga ### Em qualquer aparelho 1. Preenche número, validade, CVV e nome impresso no cartão. 2. Clica em pagar. 3. O resultado sai em poucos segundos, dentro do próprio elemento. Não há etapa do banco nem janela nova. 4. Aprovado: o elemento mostra a confirmação e dispara `throne-elements:paid`. 5. Recusado: o elemento mostra o motivo, quantas tentativas restam e um botão para tentar de novo. A venda continua `pending`. ### Em navegador de app Instagram, Facebook, o app do Google, TikTok e outros apps abrem links num navegador próprio, que não abre uma segunda janela. Na Vortex isso não faz diferença: o pagamento acontece todo dentro do elemento, o comprador termina na sua página e `throne-elements:paid` chega como no computador. Mesmo assim, libere o pedido pelo webhook ou pelo `GET`, nunca só pelo evento da página. ### O que o comprador vê - **O endereço.** Nada muda: o comprador fica na sua página do começo ao fim. - **Nenhuma confirmação do banco.** Não há código por mensagem, aplicativo do banco nem pergunta de segurança: o cartão é autorizado na hora. Um aviso ao lado do botão de pagar é opcional. - **A fatura.** Na fatura do cartão, a compra aparece sem o nome da sua loja. Textos sugeridos, se quiser avisar: ```text Inglês Secure payment. You will see the result right here, on this page. ``` ```text Alemão Sichere Zahlung. Das Ergebnis erscheint direkt hier auf dieser Seite. ``` ```text Espanhol Pago seguro. Verás el resultado aquí mismo, en esta página. ``` ## Requisitos da página - **Não fixe a altura do container** e não esconda o `overflow`: as telas de resultado e de análise mudam a altura do elemento. - Sem `sandbox` em volta do elemento. Se não der para tirar, use a lista mínima de [Throne Elements](/docs/cartao/elements#boas-praticas). - **O clique no botão de pagar precisa ser do comprador.** Não dispare o pagamento por código. - Se o seu site usa Content-Security-Policy, libere `https://app.thronecorporate.com` em `script-src` e em `img-src`, e em `frame-src` libere esse domínio e o domínio do `checkout_url`, como em [Throne Elements](/docs/cartao/elements#boas-praticas). ## Pagamento em análise Às vezes o pagamento entra em análise antifraude. O comprador vê "Pagamento em análise", sem formulário, e a venda fica `pending` até a decisão: - aprovado, vira `paid` e o webhook `transaction.paid` é enviado; - reprovado, conta como uma recusa: a tentativa é gasta, o elemento mostra o motivo e, com tentativas sobrando, o comprador pode tentar de novo. A venda continua `pending`; na 5ª recusa o checkout fecha como `failed`. Se a decisão demorar, o elemento avisa que a confirmação pode levar alguns minutos e envia `timeout` em `throne-elements:error`. Isso não é recusa. Não libere o pedido enquanto não chegar `transaction.paid`, e não cancele o pedido só pelo `timeout`. ## Erros e o que fazer Na criação do checkout: | Situação | Resposta | O que fazer | |---|---|---| | Valor acima do máximo da Vortex | `422 invalid_amount`, com `max_amount_cents` e `alternatives` | Não repita. Crie com o `acquirer` de uma das `alternatives` ou divida o pedido | | Valor abaixo do mínimo | `422 invalid_amount`, com `min_amount_cents` | Aumente o valor | | Sem `customer.name` ou `customer.email`, e-mail inválido ou fora dos tamanhos (nome até 120 caracteres, e-mail até 180) | `422 customer_required_for_vortex` | Mande os dois campos na criação, dentro dos tamanhos. Nada é criado | | Vortex não habilitada na conta | `422 acquirer_not_enabled`, com as habilitadas no `message` | Use uma das habilitadas, ou fale com o suporte para habilitar a Vortex | | Vortex em pausa, instável ou sem capacidade | `503 temporarily_unavailable`, com `Retry-After` e `"retryable": true` | Espere o `Retry-After` e repita com uma `Idempotency-Key` nova, ou use outra adquirente habilitada | | Criação recusada | `502 checkout_failed`, com `"retryable": false` | Não repita igual. Se continuar, fale com o suporte | | Falha passageira de conexão | `502 checkout_failed`, com `"retryable": true` | Repita com uma `Idempotency-Key` nova | | Sem resposta (timeout do seu lado) | Nenhuma | Repita com a **mesma** `Idempotency-Key` ([regra](/docs/comecar#como-repetir-uma-chamada-com-seguranca)) | Na página, em `throne-elements:error` (`e.detail.code`): | `code` | O que aconteceu | O que fazer | |---|---|---| | `declined` | O banco não autorizou | O comprador pode tentar de novo ou usar outro cartão. Vale sugerir que ele libere compras online e internacionais no app do banco | | `attempts_exhausted` | O checkout fechou: acabaram as 5 tentativas | Ofereça outro meio de pagamento ou um checkout novo | | `token` | Os dados do cartão não passaram na validação, antes da cobrança | O comprador confere os dados e tenta de novo. Nenhuma tentativa é gasta | | `network` | Falha de conexão, ou o formulário não carregou | O comprador pode tentar de novo em alguns instantes | | `timeout` | A análise antifraude está demorando e o pagamento segue em análise | Espere o webhook ou consulte `GET /transactions/{id}`. Não é recusa; não crie outro checkout por isso | | `expired`, `ended` | O checkout passou da validade ou foi encerrado | Crie um checkout novo | A tabela completa dos códigos, nas seis adquirentes, está em [Throne Elements](/docs/cartao/elements#codigos-de-erro). Duas tentativas ao mesmo tempo no mesmo checkout (duas abas ou duplo clique) não geram cobrança dupla: a segunda recebe "Já existe um pagamento em andamento. Aguarde." e o elemento acompanha o pagamento que já está em andamento. ### Tratar os eventos na página Na Vortex, um código pede tratamento próprio: `timeout` é espera, não recusa. ```js window.addEventListener('throne-elements:paid', (e) => { // e.detail.transaction_id: mostre o "obrigado". A liberação do pedido vem pelo webhook. }); window.addEventListener('throne-elements:error', (e) => { switch (e.detail.code) { case 'timeout': // o pagamento segue em análise: mostre "estamos confirmando" e espere o webhook break; case 'attempts_exhausted': case 'expired': case 'ended': // este checkout acabou: ofereça outro meio ou crie um checkout novo break; default: // declined, token, network: o próprio elemento já oferece a nova tentativa break; } }); ``` ## Quando chega o webhook O resultado sai na hora e o webhook `transaction.paid` costuma sair logo depois da aprovação. Numa venda que entra em análise, ele sai quando a análise aprova. Se a confirmação da Vortex se perder no caminho, a conferência automática do Throne a recupera alguns minutos depois. Não cancele o pedido por falta de webhook antes da validade de 24 horas; na dúvida, consulte `GET /transactions/{id}`. O webhook é o mesmo das outras adquirentes: `currency` e `native_amount_cents` com a venda na moeda do comprador, `amount_cents` com o valor creditado no seu saldo e `fx_rate` com a cotação fixada na criação. Veja [Webhooks](/docs/webhooks#quando-o-webhook-chega). ## Recorrência A Vortex faz cobrança recorrente nos sete ciclos do Throne, com a integração do [guia de Recorrência](/docs/cartao/recorrencias): o mesmo `POST /elements/checkout`, com o campo `recurrence`. - **Estorno**: não é feito pela API; fale com o suporte. O estorno total deixa a venda `refunded`. O parcial mantém `paid`, com os valores reduzidos. - **Contestação**: o Throne debita da sua carteira o valor líquido da venda, e ela vira `chargeback`. Se a contestação for revertida a seu favor, ela volta para `paid`. - Nenhum desses eventos gera webhook. Acompanhe pelo `GET /transactions/{id}`. ## Testar Use um cartão de verdade com valor baixo. Para ver como o comprador de outro país enxerga o checkout, abra o `checkout_url` da resposta com `?nl=` e o código do idioma (por exemplo, `?nl=de` para alemão): o formulário, os números e as telas do elemento saem nesse idioma. Teste também dentro do navegador do Instagram ou do Facebook, no celular: na Vortex o resultado aparece na sua página e o evento chega. Os erros que dá para provocar sem custo e o roteiro completo estão em [Como testar](/docs/testar). ## Checklist - [ ] Checkout criado no servidor, com `"acquirer": "vortex"` (ou a Vortex como padrão da conta), `Idempotency-Key` nova por venda e timeout de 60 segundos. - [ ] `customer.name` e `customer.email` (e-mail válido) enviados na criação, coletados na sua página antes de chamar a API. - [ ] Moeda do comprador em `currency`, sem ajustar o valor no seu site. - [ ] Máximo da Vortex conferido na moeda do seu público, com plano para `422 invalid_amount` (outra adquirente ou pedido dividido). - [ ] Container sem altura fixa e sem `sandbox` em volta do elemento (ou com a lista mínima). - [ ] Tratamento de `e.detail.code` em `throne-elements:error`, com `timeout` tratado como espera. - [ ] Pedido liberado só com `transaction.paid` (assinatura validada) ou com o `GET`. - [ ] Novo checkout para cada nova tentativa de compra depois de `failed` ou `expired`. --- # Server-to-server (parceiros) > Integração por API, sem Throne Elements, na adquirente Eminence: para gateways, subadquirentes e agregadores que já têm checkout próprio e querem oferecer a Eminence aos seus sellers. Habilitação caso a caso. Fonte: https://app.thronecorporate.com/docs/cartao/server-to-server ## O que é esta integração A integração direta é uma alternativa ao Throne Elements para quem tem um checkout de cartão próprio e quer cobrar pela API, do próprio servidor, sem o iframe do Throne. Ela existe para a **Eminence**, nesta página, e para a **Vortex**, em [Server-to-server na Vortex](/docs/cartao/vortex-direto), que cobra na moeda do comprador. Nas outras adquirentes (Sterling, Imperium, NovaCorp e ShadowBank) a integração continua sendo o Throne Elements. Quando usar: - Você é um gateway, subadquirente ou agregador e já tem a própria tela de cartão. - Você quer oferecer a Eminence dentro da sua plataforma, sem mandar o comprador ao Throne Elements. Quando **não** usar: - Você quer um checkout pronto, sem desenvolver a tela do cartão: use o [Throne Elements](/docs/cartao). - Você quer cobrar em outras adquirentes: elas não aceitam a integração direta; use o [checkout comum](/docs/cartao). - Você quer a página sempre em português e com o CPF pedido na tela: use o [Checkout Brasil](/docs/cartao/brasil). > [!WARNING] > Na integração direta o número do cartão passa pelo **seu** servidor antes de chegar ao Throne. A guarda desses dados do seu lado (PCI DSS) é responsabilidade sua: nunca grave número, validade ou CVV em banco, arquivo ou log. ## Habilitação A integração direta é liberada **caso a caso**. [Fale com o suporte](https://app.thronecorporate.com/painel). São três requisitos: - A Eminence habilitada na sua conta (confira em `GET /api/v1/elements/acquirers`). - A integração direta liberada na sua conta. - Uma chave de API com o escopo `transactions:write` (gere no painel, em Chaves de API). Sem a liberação, as duas chamadas respondem `422 partner_api_not_enabled`. Sem a Eminence, `422 acquirer_not_enabled`. Sem o escopo, `403 scope_required`. > [!NOTE] > **Nesta modalidade você envia o valor na moeda do seu comprador.** **Parcelado funciona aqui**: veja [Parcelamento](#parcelamento). ## O caminho, em dois passos Toda cobrança tem duas chamadas, nesta ordem, as duas feitas **do seu servidor** e com a sua chave de API: 1. **Tokenizar o cartão.** Você manda o cartão e recebe um `card_token` de uso único. 2. **Cobrar** com esse `card_token`. A resposta já traz o resultado. > [!WARNING] > Nunca chame a API do navegador do comprador: a chave de API daria acesso à sua conta. A sua página de pagamento envia o cartão ao seu servidor, e o seu servidor chama o Throne. ### Passo 1: tokenizar o cartão ```bash cURL curl -X POST https://app.thronecorporate.com/api/v1/vault/eminence/cards \ --max-time 45 \ -H "Authorization: Bearer $THRONE_PK:$THRONE_SK" \ -H "Content-Type: application/json" \ -d '{ "number": "5555666677778884", "exp_month": 12, "exp_year": 2030, "cvv": "123", "holder_name": "LUCA MEIER" }' ``` Resposta `201`: ```json { "card_token": "9f0d4a1c7e2b48b1a3d0f6c89e2a4b7c", "brand": "mastercard", "last4": "8884", "exp_month": 12, "exp_year": 2030, "expires_at": "2026-11-02T15:12:00Z" } ``` | Campo | Tipo | Obrigatório | O que é | |---|---|---|---| | `number` | string | sim | Número do cartão, só dígitos (espaços e hífens são ignorados) | | `exp_month` | integer | sim | Mês da validade, de 1 a 12 | | `exp_year` | integer | sim | Ano da validade, com 4 dígitos (2030) ou 2 dígitos (30) | | `cvv` | string | sim | Código de segurança, 3 ou 4 dígitos | | `holder_name` | string | sim | Nome impresso no cartão | Observações: - O `card_token` é de **uso único**: cada cobrança pede um token novo. - Use o token em até **7 dias** (`expires_at`). O ideal é cobrar logo em seguida. - Aceitos: cartões de crédito Visa, Mastercard, Elo, American Express e Hipercard. - Esta chamada não leva `Idempotency-Key`. ### Passo 2: cobrar ```bash cURL curl -X POST https://app.thronecorporate.com/api/v1/eminence/payments \ --max-time 100 \ -H "Authorization: Bearer $THRONE_PK:$THRONE_SK" \ -H "Content-Type: application/json" \ -H "Accept: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "amount_cents": 4990, "card_token": "9f0d4a1c7e2b48b1a3d0f6c89e2a4b7c", "customer": { "name": "Ana Souza", "email": "ana@exemplo.com" }, "description": "Assinatura mensal", "metadata": { "order_id": "ORD-2026-00001" } }' ``` Resposta `201` (aprovado): ```json { "transaction_id": "ae3bf1da-0c9b-4818-9140-c6432152c23e", "status": "paid", "authorized": true, "acquirer": "eminence", "acquirer_message": "Pagamento autorizado.", "decline_code": null, "retryable": false, "card": { "brand": "mastercard", "last4": "8884" }, "amount_cents": 4990, "currency": "BRL", "created_at": "2026-10-26T15:12:00Z" } ``` Resposta `201` (recusado): ```json { "transaction_id": "0c2f9b77-7d0e-4c1b-9a44-2f61e8f1a0d3", "status": "failed", "authorized": false, "acquirer": "eminence", "acquirer_message": "Saldo ou limite insuficiente.", "decline_code": "51", "retryable": false, "card": { "brand": "mastercard", "last4": "8884" }, "amount_cents": 4990, "currency": "BRL", "created_at": "2026-10-26T15:13:00Z" } ``` ### Como ler a resposta Toda cobrança que chega a ser criada responde `201` com o `transaction_id`. O resultado está no campo `status`: | `status` | O que significa | O que fazer | |---|---|---| | `paid` | Aprovado. A venda já está na sua carteira, como qualquer venda de cartão | Libere o pedido | | `pending` | Em análise, ou o resultado ainda está em confirmação | Aguarde o webhook `transaction.paid` ou consulte `GET /api/v1/transactions/{id}`. **Não cobre de novo** | | `failed` | Recusado, ou não processado. Nada foi cobrado | Leia `acquirer_message` para o comprador. Com `retryable: true`, crie uma nova cobrança (token novo e `Idempotency-Key` nova) | Erros `4xx`, `429` e `503` **antes** da cobrança significam que nada foi criado: corrija e repita, inclusive com a mesma `Idempotency-Key`. Uma chamada com a mesma `Idempotency-Key` e o mesmo corpo de uma cobrança já feita devolve a resposta original (cabeçalho `Idempotent-Replay: true`), sem cobrar de novo, desde que a integração continue liberada na conta e a chamada caiba no limite por segundo. ### Campos do passo 2 | Campo | Tipo | Obrigatório | O que é | |---|---|---|---| | `amount_cents` | integer | sim | Valor em **centavos de real** (R$ 49,90 = 4990). Mínimo R$ 5,00 | | `currency` | string | não | Só `BRL` | | `card_token` | string | sim | O token do passo 1 | | `customer.name` | string | sim | Nome do comprador | | `customer.email` | string | não | E-mail do comprador (fica na venda, no Throne) | | `customer.document` | string | não | CPF do comprador, só dígitos. Mande quando tiver: um CPF válido entra na análise de risco da adquirente e aumenta a aprovação | | `customer.phone` | string | não | Telefone do comprador, com DDD, só dígitos. Entra na análise de risco da adquirente | | `installments` | integer | não | De `1` a `12`. Sem o campo, à vista. Veja [Parcelamento](#parcelamento) | | `description` | string | não | Até 255 caracteres. Volta em `metadata.description` na consulta e no webhook | | `metadata` | object | não | Até 20 chaves com valores simples (texto, número ou booleano). Volta na consulta e no webhook. Chaves de uso interno são recusadas com `422 metadata_reserved_key`: as que começam com `throne_`, `stripe_`, `eminence_`, `chargeback_`, `refund_`, `partial_` ou `original_`, e nomes como `description`, `currency`, `acquirer`, `items` e `seller_id`. Use nomes seus, como `order_id` ou `pedido` | | `Idempotency-Key` | header | sim | Uma chave nova por cobrança (UUID v4). Veja [Comece aqui](/docs/comecar) | **Dois campos do checkout comum não existem aqui.** `one_click_from` volta `422 one_click_not_supported` e `recurrence` volta `422 recurrence_not_supported`, e nada é criado. O upsell em um clique funciona no [checkout do Throne](/docs/cartao/checkout), e a cobrança recorrente nesta adquirente existe só no [Checkout Brasil](/docs/cartao/brasil). Veja [Upsell em um clique](/docs/cartao/oneclick) e [Recorrência](/docs/cartao/recorrencias). > [!NOTE] > Na **Vortex** a integração direta tem upsell em um clique, por uma rota própria: o [`POST /api/v1/vortex/upsell`](/docs/cartao/vortex-direto#upsell-em-um-clique), que cobra o cartão guardado da venda de origem e pode ser recorrente. ### Campos da resposta | Campo | O que é | |---|---| | `transaction_id` | Id público da venda. Use no `GET /api/v1/transactions/{id}` e para casar com o webhook | | `status` | `paid`, `pending` ou `failed` (veja a tabela acima) | | `authorized` | `true` só quando aprovado | | `acquirer` | Sempre `eminence` | | `acquirer_message` | Texto pronto para mostrar ao comprador. Pode mudar; decida pelo `status` | | `decline_code` | Código da recusa (padrão das bandeiras, como `51` saldo insuficiente) ou um destes: `invalid_card_token`, `card_not_supported`, `amount_limit`, `processing_unavailable`. `null` quando não se aplica | | `retryable` | `true` quando vale tentar de novo com uma nova cobrança (`processing_unavailable` e `invalid_card_token`) | | `card` | Bandeira e últimos 4 dígitos, quando conhecidos | | `amount_cents` | Valor cobrado, em centavos de real | | `currency` | Sempre `BRL` | | `created_at` | Data da criação, ISO 8601 em UTC | ## Consulta A venda da integração direta é uma transação comum do Throne. Use `GET /api/v1/transactions/{id}` com o `transaction_id`. ## Estorno Pelo suporte, como em qualquer venda de cartão. **Não existe endpoint de estorno.** Veja [Status e ciclo de vida](/docs/cartao/status). ## Parcelamento Mande `installments` de `1` a `12`. Sem o campo, a cobrança é à vista, igual a antes. ```json { "amount_cents": 50000, "currency": "BRL", "card_token": "ac1c3b53b22d47e2baa7b81662dc3a82", "customer": { "name": "Ana Lima", "email": "ana@exemplo.com", "ip": "189.0.157.190" }, "installments": 6 } ``` **Os juros são do comprador.** O `amount_cents` que você manda é o que você recebe: o Throne liquida pelo valor-base e os juros do parcelamento ficam com a adquirente. Numa venda de R$ 500,00 em 6x, o comprador paga cerca de R$ 571,60 no cartão dele e o seu líquido é o mesmo de uma venda de R$ 500,00 à vista. **O número é conferido na adquirente antes de cobrar.** A tabela de juros é do cartão do comprador, não sua: cartão que não parcela, ou parcela em menos vezes, volta `422 installments_unavailable` e **nada é cobrado**. Nunca acontece de você pedir 6x e a cobrança sair à vista sem avisar. | HTTP | `error` | O que aconteceu | O que fazer | |---|---|---|---| | 422 | `validation_failed` | `installments` fora de 1 a 12 | Corrija o número | | 422 | `installments_unavailable` | O cartão do comprador não oferece esse número de parcelas nesse valor. Nada foi cobrado | Cobre à vista, ou em menos parcelas | | 422 | `unavailable` | Não deu para consultar a tabela de juros agora. Nada foi cobrado | Repita com uma `Idempotency-Key` nova | O número de parcelas cobrado aparece na venda, e é o que a adquirente confirmou. Cobranças recorrentes são sempre à vista: `recurrence` e `installments` são coisas diferentes e não se misturam. ## Webhook Igual ao cartão pelo Throne Elements: `transaction.paid` quando a venda é aprovada, inclusive as que ficaram `pending` em análise e foram aprovadas depois. O payload é o mesmo de sempre. Veja [Webhooks](/docs/webhooks). ## Erros No passo 1 (tokenização): | HTTP | `error` | O que aconteceu | O que fazer | |---|---|---|---| | 401 | `unauthorized` | Chave de API ausente ou inválida | Confira o `Authorization` | | 403 | `scope_required` | A chave não tem `transactions:write` | Gere uma chave com esse escopo | | 403 | `seller_not_approved` | A conta não está aprovada (suspensa ou em análise) | Fale com o suporte | | 422 | `partner_api_not_enabled` | Integração direta não liberada | Fale com o suporte | | 422 | `acquirer_not_enabled` | Eminence não habilitada | Fale com o suporte | | 422 | `invalid_card` | Número, validade, CVV ou nome inválidos. O campo `field` diz qual | Peça ao comprador para conferir o cartão | | 422 | `card_not_supported` | Bandeira ou tipo de cartão não aceito (débito, por exemplo) | Peça outro cartão de crédito | | 429 | `rate_limit_exceeded` | Limite de requisições por segundo | Aguarde 1 segundo e repita | | 503 | `temporarily_unavailable` | Tokenização indisponível no momento | Repita em alguns segundos | No passo 2 (cobrança), antes de criar a venda (nada foi criado): | HTTP | `error` | O que aconteceu | O que fazer | |---|---|---|---| | 400 | `idempotency_key_required` | Faltou o cabeçalho `Idempotency-Key` | Envie uma chave por cobrança | | 401 | `unauthorized` | Chave de API ausente ou inválida | Confira o `Authorization` | | 403 | `scope_required` | A chave não tem `transactions:write` | Gere uma chave com esse escopo | | 403 | `seller_not_approved` | A conta não está aprovada (suspensa ou em análise) | Fale com o suporte | | 422 | `partner_api_not_enabled` | Integração direta não liberada | Fale com o suporte | | 422 | `acquirer_not_enabled` | Eminence não habilitada | Fale com o suporte | | 422 | `validation_failed` | Campo inválido; `errors` lista cada um | Corrija o corpo | | 422 | `invalid_card_token` | `card_token` fora do formato | Tokenize de novo | | 422 | `amount_out_of_range` | Valor fora da faixa; a resposta traz `min_amount_cents` e `max_amount_cents` | Ajuste o valor | | 422 | `metadata_reserved_key` | Chave de `metadata` reservada | Troque o nome da chave | | 422 | `idempotency_key_mismatch` | A mesma `Idempotency-Key` com outro corpo | Use uma chave nova | | 409 | `idempotency_key_processing` | A mesma `Idempotency-Key` ainda está em processamento | Aguarde a primeira resposta | | 429 | `rate_limit_exceeded` | Limite de requisições por segundo | Aguarde o `Retry-After` e repita | | 503 | `temporarily_unavailable` ou `busy` | Cobrança indisponível no momento | Aguarde o `Retry-After` e repita | Depois de criada, a venda responde sempre `201` com o `status` (veja [Como ler a resposta](#como-ler-a-resposta)). ## Dinheiro O fluxo financeiro é **igual** ao cartão pelo Throne Elements: a venda aprovada entra na sua carteira com a sua taxa de cartão, obedece à retenção e à maturação da sua conta e pode ser antecipada ou sacada do mesmo jeito. ## Limites e tempos - Timeout do seu cliente HTTP: **45 segundos** no passo 1 e **100 segundos** no passo 2. Na prática as respostas levam poucos segundos. - Limite por conta: **10 requisições por segundo** em cada passo, por padrão (o suporte ajusta). Acima disso, `429 rate_limit_exceeded`. - Tetos fixos, que não mudam com o ajuste da conta: **10 tokenizações por segundo** por IP no passo 1 e **8 cobranças em andamento ao mesmo tempo** no passo 2 (acima disso, `503 busy` com `Retry-After`, e nada é criado). - Valor mínimo: **R$ 5,00** por cobrança. - Se o seu cliente desistir antes da resposta do passo 2, consulte a venda pelo `GET` ou repita a **mesma** chamada com a **mesma** `Idempotency-Key`. Nunca crie uma cobrança nova para o mesmo pedido sem saber o resultado da anterior. ## Testar No Throne não existe ambiente de testes separado: use um cartão de verdade com valor baixo (R$ 5,00) e peça o estorno ao suporte. Cartão sem saldo, bloqueado para compra online ou com dados errados volta `failed` com o motivo em `acquirer_message`. Veja [Como testar](/docs/testar). ## Checklist - [ ] Eminence habilitada e integração direta liberada na conta (fale com o suporte). - [ ] Chave de API com `transactions:write`, guardada só no servidor. - [ ] Página de pagamento manda o cartão ao **seu** servidor; o seu servidor chama o Throne. - [ ] Número, validade e CVV nunca gravados em banco, arquivo ou log. - [ ] Um `card_token` novo por cobrança. - [ ] `Idempotency-Key` nova por cobrança e timeout de 100 segundos no passo 2. - [ ] Decisão pelo `status`: `paid` libera, `pending` espera o webhook, `failed` mostra o `acquirer_message`. - [ ] Webhook `transaction.paid` tratado como no cartão pelo Throne Elements. - [ ] Teste real de R$ 5,00 antes de abrir ao público. --- # Server-to-server na Vortex > Integração por API, sem Throne Elements, na adquirente Vortex: cobrança na moeda do comprador, para quem já tem checkout próprio. Habilitação caso a caso. Fonte: https://app.thronecorporate.com/docs/cartao/vortex-direto ## O que é esta integração A integração direta da Vortex é uma alternativa ao Throne Elements para quem tem um checkout de cartão próprio e quer cobrar pela API, do próprio servidor, sem o iframe do Throne. Ela existe para a **Eminence**, para a **Vortex** e para a **ShadowBank**, e esta página cobre a Vortex. Nas outras adquirentes a integração continua sendo o [Throne Elements](/docs/cartao). A diferença da Vortex para a [integração direta da Eminence](/docs/cartao/server-to-server) está na moeda: aqui você cobra na moeda do seu comprador, as mesmas 48 da [lista](/docs/cartao/moeda#moedas-aceitas), e a cotação que credita o seu saldo é fixada na criação da cobrança, como no checkout comum. Quando usar: - Você é um gateway, subadquirente ou agregador e já tem a própria tela de cartão. - Você vende para compradores de vários países e quer cobrar na moeda de cada um sem mandá-los ao Throne Elements. Quando **não** usar: - Você quer um checkout pronto, sem desenvolver a tela do cartão: use o [Throne Elements](/docs/cartao). - Você quer cobrar em outra adquirente: só a Eminence e a Vortex aceitam a integração direta. - Você precisa de parcelamento: a integração direta cobra sempre à vista. **Upsell em um clique e recorrência funcionam aqui.** O upsell tem rota própria, o [`POST /api/v1/vortex/upsell`](#upsell-em-um-clique), e ele mesmo pode ser recorrente. O campo `one_click_from` do checkout comum continua não existindo nesta modalidade: ele volta `422 one_click_not_supported` e a mensagem aponta a rota certa. > [!WARNING] > Na integração direta o número do cartão passa pelo **seu** servidor antes de chegar ao Throne. A guarda desses dados do seu lado (PCI DSS) é responsabilidade sua: nunca grave número, validade ou CVV em banco, arquivo ou log. ## Habilitação A integração direta é liberada **caso a caso**. [Fale com o suporte](https://app.thronecorporate.com/painel). São três requisitos: - A Vortex habilitada na sua conta (confira em `GET /api/v1/elements/acquirers`). - A integração direta da Vortex liberada na sua conta. - Uma chave de API com o escopo `transactions:write` (gere no painel, em Chaves de API). Sem a liberação, as duas chamadas respondem `422 partner_api_not_enabled`. Sem a Vortex, `422 acquirer_not_enabled`. Sem o escopo, `403 scope_required`. A liberação é por adquirente: quem já usa a integração direta da Eminence precisa pedir a da Vortex à parte. Habilitar só a Vortex na conta, sem essa liberação, dá acesso ao Throne Elements, não a esta API. ## O caminho, em dois passos Toda cobrança tem duas chamadas, nesta ordem, as duas feitas **do seu servidor** e com a sua chave de API: 1. **Tokenizar o cartão.** Você manda o cartão e recebe um `card_token` de uso único. 2. **Cobrar** com esse `card_token`. A resposta já traz o resultado. > [!WARNING] > Nunca chame a API do navegador do comprador: a chave de API daria acesso à sua conta. A sua página de pagamento envia o cartão ao seu servidor, e o seu servidor chama o Throne. ### Passo 1: tokenizar o cartão ```bash cURL curl -X POST https://app.thronecorporate.com/api/v1/vault/vortex/cards \ --max-time 45 \ -H "Authorization: Bearer $THRONE_PK:$THRONE_SK" \ -H "Content-Type: application/json" \ -d '{ "number": "4000000000000010", "exp_month": 12, "exp_year": 2030, "cvv": "123", "holder_name": "LENA FISCHER" }' ``` Resposta `201`: ```json { "card_token": "ac1c3b53b22d47e2baa7b81662dc3a82", "brand": "visa", "last4": "0010", "exp_month": 12, "exp_year": 2030, "expires_at": "2026-11-03T06:35:06Z" } ``` O token vale até o `expires_at` e serve para **uma** cobrança. Usado, recusado ou vencido, tokenize de novo. ### Passo 2: cobrar ```bash cURL curl -X POST https://app.thronecorporate.com/api/v1/vortex/payments \ --max-time 120 \ -H "Authorization: Bearer $THRONE_PK:$THRONE_SK" \ -H "Content-Type: application/json" \ -H "Accept: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "amount_cents": 4990, "currency": "eur", "card_token": "ac1c3b53b22d47e2baa7b81662dc3a82", "customer": { "name": "Lena Fischer", "email": "lena@exemplo.de", "phone": "+4915112345678", "ip": "91.64.12.33" }, "description": "Plano anual", "metadata": { "order_id": "ORD-2026-00001" } }' ``` Resposta `201` (aprovado): ```json { "transaction_id": "ae3bf1da-0c9b-4818-9140-c6432152c23e", "status": "paid", "authorized": true, "acquirer": "vortex", "acquirer_message": "Pagamento autorizado.", "decline_code": null, "retryable": false, "card": { "brand": "visa", "last4": "0010" }, "amount_cents": 4990, "currency": "EUR", "created_at": "2026-10-27T06:35:09Z" } ``` Resposta `201` (recusado): ```json { "transaction_id": "6ea30223-578f-4f3f-b9fb-24ff227c2f17", "status": "failed", "authorized": false, "acquirer": "vortex", "acquirer_message": "Cartão recusado.", "decline_code": "r800", "retryable": false, "card": { "last4": "0010" }, "amount_cents": 4990, "currency": "EUR", "created_at": "2026-10-27T06:35:09Z" } ``` ### Como ler a resposta Toda cobrança que chega a ser criada responde `201` com o `transaction_id`. O resultado está no campo `status`: | `status` | O que significa | O que fazer | |---|---|---| | `paid` | Aprovado. A venda já está na sua carteira, como qualquer venda de cartão | Libere o pedido | | `pending` | Autorizado pelo banco e em análise de risco, ou resultado ainda em confirmação | Aguarde o webhook `transaction.paid` ou consulte `GET /api/v1/transactions/{id}`. **Não cobre de novo** | | `failed` | Recusado, ou não processado. Nada foi cobrado | Leia `acquirer_message` para o comprador. Com `retryable: true`, crie uma nova cobrança (token novo e `Idempotency-Key` nova) | > [!NOTE] > **Na Vortex a aprovação nem sempre é imediata.** O banco do comprador autoriza na hora, mas a venda ainda passa por uma análise de risco antes de virar paga. Nesse intervalo o `status` é `pending`, e isso é normal: não trate como falha e não cobre de novo. O desfecho chega em segundos, pelo webhook. Uma reprovação na análise fecha a venda como recusada. Erros `4xx`, `429` e `503` **antes** da cobrança significam que nada foi criado: corrija e repita, inclusive com a mesma `Idempotency-Key`. Uma chamada com a mesma `Idempotency-Key` e o mesmo corpo de uma cobrança já feita devolve a resposta original (cabeçalho `Idempotent-Replay: true`), sem cobrar de novo. ### Campos do passo 2 | Campo | Tipo | Obrigatório | O que é | |---|---|---|---| | `amount_cents` | integer | sim | Valor na **menor unidade da moeda** (€ 49,90 = `4990`). Em moedas sem casa decimal, a unidade inteira (¥ 5000 = `5000`) | | `currency` | string | sim | Código de 3 letras, maiúsculas ou minúsculas. As 48 da [lista](/docs/cartao/moeda#moedas-aceitas) | | `card_token` | string | sim | O token do passo 1 | | `customer.name` | string | sim | Nome do comprador, até 120 caracteres | | `customer.email` | string | sim | E-mail válido do comprador, até 180 caracteres | | `customer.phone` | string | não | Telefone do comprador, até 30 caracteres | | `customer.ip` | string | **sim** | IP de onde o comprador está, nunca o do seu servidor. Sem ele a chamada volta `422 customer_ip_required_for_vortex` e nada é criado | | `customer.document` | string | não | CPF ou CNPJ do comprador, até 20 caracteres. Mande quando tiver: um documento válido entra na análise de risco da adquirente e aumenta a aprovação | | `description` | string | não | Até 255 caracteres. Volta em `metadata.description` na consulta e no webhook | | `metadata` | object | não | Até 20 chaves com valores simples (texto, número ou booleano). Volta na consulta e no webhook. Chaves de uso interno são recusadas com `422 metadata_reserved_key`: as que começam com `throne_`, `vortex_`, `stripe_`, `chargeback_`, `refund_`, `partial_` ou `original_`, e nomes como `description`, `currency`, `acquirer`, `items` e `seller_id`. Use nomes seus, como `order_id` ou `pedido` | | `recurrence` | object | não | Recorrência: `cycle` (um dos sete ciclos) e `max_payments` opcional, de 2 a 120. Esta cobrança é a primeira e as seguintes saem sozinhas, no mesmo cartão. Veja [Recorrência](#recorrencia) | | `Idempotency-Key` | header | sim | Uma chave nova por cobrança (UUID v4). Veja [Comece aqui](/docs/comecar) | Nome e e-mail do comprador são obrigatórios nesta modalidade, ao contrário da integração direta da Eminence. Sem eles, ou fora dos tamanhos, a chamada volta `422 customer_required_for_vortex` e nada é criado. Nesta adquirente a autorização do banco vem primeiro e a análise de risco vem depois, então o que você manda do comprador decide a venda. Quanto mais real for o cadastro, mais vendas passam. Em ordem de peso: - `customer.ip`: obrigatório. O IP de onde o comprador está, nunca o do seu servidor. - `customer.document`: CPF ou CNPJ, quando o comprador tem um. - `customer.phone`: com DDD, no formato brasileiro. Um número que não existe pesa contra. - `customer.email`: um endereço que recebe mensagem de verdade. Mandar um campo com valor inventado é pior do que não mandar: a adquirente confere o que pode conferir. ### Campos da resposta | Campo | O que é | |---|---| | `transaction_id` | Id público da venda. Use no `GET /api/v1/transactions/{id}` e para casar com o webhook | | `status` | `paid`, `pending` ou `failed` (veja a tabela acima) | | `authorized` | `true` só quando aprovado | | `acquirer` | Sempre `vortex` | | `acquirer_message` | Texto pronto para mostrar ao comprador. Pode mudar; decida pelo `status` | | `decline_code` | Código da recusa devolvido pela adquirente, ou um destes: `invalid_card_token`, `invalid_request`, `idempotency_mismatch`, `processing_unavailable`. `null` quando não se aplica | | `retryable` | `true` quando vale tentar de novo com uma nova cobrança | | `card` | Bandeira e últimos 4 dígitos, quando conhecidos | | `amount_cents` | O valor que você cobrou, na menor unidade da moeda enviada | | `currency` | A moeda que você enviou | | `created_at` | Data da criação, ISO 8601 em UTC | | `recurrence_id` | Só com `recurrence` na chamada: o id da recorrência, para consultar e cancelar. Nulo quer dizer que a assinatura não abriu | | `cycle` | Só com `recurrence`: o ciclo gravado | | `next_charge_at` | Só com `recurrence`: estimativa da próxima cobrança, ISO 8601 em UTC | ## Consulta A venda da integração direta é uma transação comum do Throne. Use `GET /api/v1/transactions/{id}` com o `transaction_id`. ## Estorno Pelo suporte, como em qualquer venda de cartão. **Não existe endpoint de estorno.** Veja [Status e ciclo de vida](/docs/cartao/status). ## Webhook Igual ao cartão pelo Throne Elements: `transaction.paid` quando a venda é aprovada, inclusive as que ficaram `pending` em análise e foram aprovadas depois. O payload é o mesmo de sempre, com `currency`, `native_amount_cents` e `fx_rate`. Veja [Webhooks](/docs/webhooks). ## Upsell em um clique Cobra um segundo produto no **mesmo cartão** de uma venda já paga, sem pedir o cartão outra vez ao comprador e sem `card_token` nenhum. É a oferta que aparece depois do obrigado: o comprador clica uma vez e pronto. **Habilitação à parte.** Ter a integração direta liberada não basta: o upsell em um clique é liberado conta por conta. Sem isso, a rota responde `403 one_click_not_enabled`. Fale com o suporte. ### A chamada ```http POST /api/v1/vortex/upsell Authorization: Bearer pk_live_xxx:sk_live_xxx Content-Type: application/json Idempotency-Key: upsell-pedido-9001 ``` ```json { "parent_transaction_id": "e1b9c0a4-7f3d-4e21-9a8b-3c5d6e7f8a90", "amount_cents": 4700, "currency": "USD", "description": "Plano anual", "metadata": { "pedido": "9001-up1" } } ``` O `parent_transaction_id` é o `transaction_id` que você recebeu na cobrança da venda de origem. Nunca um id do processador. **Não existe `card_token` nesta rota, e é esse o ponto.** Também não existem `customer` nem `installments`: o comprador é o da venda de origem e o upsell é sempre à vista. Mandar qualquer um desses campos volta `422 field_not_supported`, e nada é criado — é de propósito, para você não receber um `201` achando que mandou um cartão. ### O upsell pode ser recorrente A venda de origem é única; o upsell pode se repetir. É a assinatura vendida no pós-compra: o comprador pagou o produto à vista e assinou com um clique. Mande o mesmo campo `recurrence` da cobrança comum: ```json { "parent_transaction_id": "e1b9c0a4-7f3d-4e21-9a8b-3c5d6e7f8a90", "amount_cents": 4700, "currency": "USD", "recurrence": { "cycle": "monthly", "max_payments": 12 } } ``` `cycle` é um de `weekly`, `biweekly`, `monthly`, `bimonthly`, `quarterly`, `semiannually` ou `yearly`. `max_payments` é opcional, de 2 a 120, e **conta este upsell como a primeira cobrança**; sem ele, a recorrência segue até você cancelar em `/recurrences`. A resposta `201` ganha três campos: ```json { "transaction_id": "9f2c1d7e-4a8b-4c3d-8e9f-1a2b3c4d5e6f", "status": "pending", "recurrence_id": "rec_a1b2c3d4e5f60718293a4b5c", "cycle": "monthly", "next_charge_at": "2026-11-28T18:04:11Z" } ``` **A assinatura só passa a valer quando este upsell for aprovado**, e aqui a aprovação nunca é imediata (veja acima). Até lá a recorrência fica `pending`. `recurrence_id` nulo, com `cycle` e `next_charge_at` nulos, quer dizer que a cobrança foi criada mas a assinatura não abriu: não haverá cobranças seguintes. Ciclo fora dos sete volta `422 invalid_cycle` com a lista na mensagem, e **nada é criado nem cobrado**. `max_payments` sem `cycle` é pedido pela metade e volta `422 validation_failed`. ### A resposta Sempre `201` quando a venda é criada, com o `status` real: ```json { "transaction_id": "9f2c1d7e-4a8b-4c3d-8e9f-1a2b3c4d5e6f", "status": "pending", "authorized": false, "acquirer": "vortex", "acquirer_message": "Pagamento em análise. O resultado chega pelo webhook e pela consulta da venda.", "decline_code": null, "retryable": false, "card": { "brand": "visa", "last4": "4242" }, "amount_cents": 4700, "currency": "USD", "one_click": true, "created_at": "2026-10-28T18:04:11Z" } ``` **`pending` é o desfecho normal aqui, e ele já significa cobrado.** Nesta adquirente o cartão não aprova na hora, e no upsell há um motivo a mais: o processador confirma a cobrança sem devolver o número do pedido dela, que chega depois, no aviso. Então: - **espere o webhook** `transaction.paid`, como em qualquer venda `pending`; - **não repita a chamada.** Repetir cobra de novo. Se o seu cliente HTTP desistir antes da resposta, consulte a venda pelo `GET` ou repita a **mesma** chamada com a **mesma** `Idempotency-Key`. `failed` é recusa de verdade: o `decline_code` e o `acquirer_message` dizem o motivo, e `retryable` diz se vale repetir (com uma `Idempotency-Key` nova). ### O que a venda de origem precisa ser | Regra | Valor | |---|---| | Da sua conta | sempre; venda de outra conta responde como inexistente | | Paga | `paid`, sem estorno e sem contestação | | Dentro da janela | **120 minutos** desde o pagamento | | Dentro do teto | no máximo **5 upsells** por venda de origem | | Mesma adquirente | a venda de origem tem de ser da Vortex | | Com cartão guardado | a venda de origem tem de ter sido cobrada pela Vortex **depois de 28/10/2026**; antes disso o cartão não era guardado | Upsell de upsell funciona: o segundo aponta para o primeiro no `parent_transaction_id`, e os dois contam no teto da venda que abriu o funil. ### Erros Antes de criar a venda (nada foi criado, a `Idempotency-Key` fica livre): | HTTP | `error` | O que aconteceu | O que fazer | |---|---|---|---| | 403 | `one_click_not_enabled` | O upsell em um clique não está liberado na conta | Fale com o suporte | | 403 | `seller_not_approved` | A conta não está aprovada | Fale com o suporte | | 422 | `field_not_supported` | Mandou `card_token`, `customer`, `recurrence`, `installments` ou `one_click_from` | Tire o campo do corpo | | 422 | `validation_failed` | Campo inválido; `errors` lista cada um | Corrija o corpo | | 422 | `acquirer_not_enabled` | Vortex não habilitada | Fale com o suporte | | 422 | `currency_not_supported` | Moeda fora das aceitas | Use uma moeda da [lista](/docs/cartao/moeda#moedas-aceitas) | | 422 | `fx_unavailable` | Cotação momentaneamente indisponível | Espere o `Retry-After` (60 s) e repita | | 422 | `amount_out_of_range` | Valor fora da faixa. A resposta traz `min_amount_cents`, `max_amount_cents` e `currency`, na moeda que você enviou | Ajuste o valor | | 422 | `metadata_reserved_key` | Chave de `metadata` reservada | Troque o nome da chave | | 422 | `invalid_cycle` | O `recurrence.cycle` não é um dos sete. A resposta traz a lista | Mande um dos sete | | 422 | `recurrence_not_supported` | A cobrança recorrente não está disponível nesta adquirente no momento | Fale com o suporte, ou cobre sem `recurrence` | | 409 | `parent_not_found` | Não existe venda sua com esse `parent_transaction_id` | Confira o id | | 409 | `parent_not_paid` | A venda de origem não está paga | Espere o `transaction.paid` dela | | 409 | `parent_refunded` | A venda de origem foi estornada | Não serve de origem | | 409 | `parent_charged_back` | A venda de origem foi contestada | Não serve de origem | | 409 | `parent_too_old` | A venda de origem passou dos 120 minutos | Cobre pelo caminho normal, com `card_token` | | 409 | `upsell_limit_reached` | A venda de origem já tem 5 upsells | Cobre pelo caminho normal | | 409 | `parent_other_acquirer` | A venda de origem é de outra adquirente | O upsell cai na mesma adquirente dela | | 503 | `temporarily_unavailable` | Indisponível no momento | Aguarde o `Retry-After` e repita | Depois de criada, a venda responde `201` com o `status`, e aí o `error` do corpo dá lugar ao `decline_code`: | `decline_code` | O que aconteceu | O que fazer | |---|---|---| | `one_click_unavailable` | A venda de origem não tem cartão guardado (foi cobrada antes de 28/10/2026, ou pelo checkout da Vortex) | Cobre pelo caminho normal, com `card_token` | | `declined` | O processador recusou a cobrança | Mostre o `acquirer_message` ao comprador | | `unavailable` | Nada foi decidido no processador; nada foi cobrado | Repita com uma `Idempotency-Key` nova | | `invalid_request` | O corpo não passou na validação da ponte | Corrija e repita com uma chave nova | | `processing_unavailable` | Não deu para processar agora; nada foi cobrado | Repita com uma `Idempotency-Key` nova | ### Dinheiro e webhook Igual a qualquer venda de cartão: o upsell entra na sua carteira com a sua taxa, obedece à retenção e à maturação da conta, e o webhook é o `transaction.paid` de sempre, com `currency`, `native_amount_cents` e `fx_rate`. **Nenhum campo novo** entra no seu webhook: amarre o upsell ao funil pelo `transaction_id` que esta chamada devolveu. ### Checkout da Vortex (iframe) O upsell em um clique existe **só** na integração direta. No checkout hospedado da Vortex o campo `one_click_from` continua respondendo `one_click: false`. ## Recorrência A integração direta aceita o mesmo campo `recurrence` do checkout comum: esta cobrança é a primeira e as seguintes saem sozinhas, no mesmo cartão, sem novo `card_token` e sem nova chamada sua. No passo 1 (tokenização): | HTTP | `error` | O que aconteceu | O que fazer | |---|---|---|---| | 401 | `unauthorized` | Chave de API ausente ou inválida | Confira o `Authorization` | | 403 | `scope_required` | A chave não tem `transactions:write` | Gere uma chave com esse escopo | | 403 | `seller_not_approved` | A conta não está aprovada (suspensa ou em análise) | Fale com o suporte | | 422 | `partner_api_not_enabled` | Integração direta da Vortex não liberada | Fale com o suporte | | 422 | `acquirer_not_enabled` | Vortex não habilitada | Fale com o suporte | | 422 | `invalid_card` | Número, validade, CVV ou nome inválidos. O campo `field` diz qual | Peça ao comprador para conferir o cartão | | 429 | `rate_limit_exceeded` | Limite de requisições por segundo | Aguarde 1 segundo e repita | | 503 | `temporarily_unavailable` | Tokenização indisponível no momento | Repita em alguns segundos | No passo 2 (cobrança), antes de criar a venda (nada foi criado): | HTTP | `error` | O que aconteceu | O que fazer | |---|---|---|---| | 400 | `idempotency_key_required` | Faltou o cabeçalho `Idempotency-Key` | Envie uma chave por cobrança | | 401 | `unauthorized` | Chave de API ausente ou inválida | Confira o `Authorization` | | 403 | `scope_required` | A chave não tem `transactions:write` | Gere uma chave com esse escopo | | 403 | `seller_not_approved` | A conta não está aprovada (suspensa ou em análise) | Fale com o suporte | | 422 | `partner_api_not_enabled` | Integração direta da Vortex não liberada | Fale com o suporte | | 422 | `acquirer_not_enabled` | Vortex não habilitada | Fale com o suporte | | 422 | `validation_failed` | Campo inválido; `errors` lista cada um | Corrija o corpo | | 422 | `customer_required_for_vortex` | Faltou `customer.name` ou `customer.email`, ou um deles está fora do tamanho | Mande os dois campos | | 422 | `currency_not_supported` | Moeda fora das 48 aceitas | Use uma moeda da [lista](/docs/cartao/moeda#moedas-aceitas) | | 422 | `fx_unavailable` | Cotação momentaneamente indisponível | Espere o `Retry-After` (60 s) e repita | | 422 | `invalid_card_token` | `card_token` fora do formato | Tokenize de novo | | 422 | `invalid_cycle` | O `recurrence.cycle` não é um dos sete ciclos aceitos. A resposta traz a lista. Nada foi criado | Mande um de `weekly`, `biweekly`, `monthly`, `bimonthly`, `quarterly`, `semiannually` ou `yearly` | | 422 | `recurrence_not_supported` | A cobrança recorrente não está disponível nesta adquirente no momento. Nada foi criado | Fale com o suporte, ou cobre sem o campo `recurrence` | | 422 | `one_click_not_supported` | Mandou `one_click_from` nesta rota. O upsell em um clique tem rota própria. Nada foi criado | Use [`POST /api/v1/vortex/upsell`](#upsell-em-um-clique) | | 422 | `amount_out_of_range` | Valor fora da faixa aceita. A resposta traz `min_amount_cents`, `max_amount_cents` e `currency`, sempre na moeda que você enviou | Ajuste o valor | | 422 | `metadata_reserved_key` | Chave de `metadata` reservada | Troque o nome da chave | | 422 | `idempotency_key_mismatch` | A mesma `Idempotency-Key` com outro corpo | Use uma chave nova | | 409 | `idempotency_key_processing` | A mesma `Idempotency-Key` ainda está em processamento | Aguarde a primeira resposta | | 429 | `rate_limit_exceeded` | Limite de requisições por segundo | Aguarde o `Retry-After` e repita | | 503 | `temporarily_unavailable` ou `busy` | Cobrança indisponível no momento | Aguarde o `Retry-After` e repita | Depois de criada, a venda responde sempre `201` com o `status` (veja [Como ler a resposta](#como-ler-a-resposta)). ## Dinheiro O fluxo financeiro é **igual** ao cartão pelo Throne Elements: a venda aprovada entra na sua carteira com a sua taxa de cartão, obedece à retenção e à maturação da sua conta e pode ser antecipada ou sacada do mesmo jeito. A cotação que credita o seu saldo é fixada na criação da cobrança e volta no webhook em `fx_rate`. Veja [Moedas e limites](/docs/cartao/moeda). ## Limites e tempos - Timeout do seu cliente HTTP: **45 segundos** no passo 1 e **120 segundos** no passo 2. Na prática as respostas levam poucos segundos. - Limite por conta: **10 requisições por segundo** em cada passo, por padrão (o suporte ajusta). Acima disso, `429 rate_limit_exceeded`. - Tetos fixos, que não mudam com o ajuste da conta: **10 tokenizações por segundo** por IP no passo 1 e **6 cobranças em andamento ao mesmo tempo** no passo 2 (acima disso, `503 busy` com `Retry-After`, e nada é criado). - Mínimo e máximo por cobrança variam com a moeda e com a cotação do dia. Fora da faixa, `422 amount_out_of_range` com os limites na sua moeda. - Um `card_token` por cobrança: ele é de uso único. - Se o seu cliente desistir antes da resposta do passo 2, consulte a venda pelo `GET` ou repita a **mesma** chamada com a **mesma** `Idempotency-Key`. Nunca crie uma cobrança nova para o mesmo pedido sem saber o resultado da anterior. ## Testar No Throne não existe ambiente de testes separado: use um cartão de verdade com valor baixo e peça o estorno ao suporte. Cartão sem saldo, bloqueado para compra online ou com dados errados volta `failed` com o motivo em `acquirer_message`. Veja [Como testar](/docs/testar). ## Checklist - [ ] Vortex habilitada e integração direta da Vortex liberada na conta (fale com o suporte). - [ ] Chave de API com `transactions:write`, guardada só no servidor. - [ ] Página de pagamento manda o cartão ao **seu** servidor; o seu servidor chama o Throne. - [ ] Número, validade e CVV nunca gravados em banco, arquivo ou log. - [ ] Um `card_token` novo por cobrança. - [ ] `customer.name` e `customer.email` em toda cobrança. - [ ] `Idempotency-Key` nova por cobrança e timeout de 120 segundos no passo 2. - [ ] Decisão pelo `status`: `paid` libera, `pending` espera o webhook, `failed` mostra o `acquirer_message`. - [ ] Webhook `transaction.paid` tratado como no cartão pelo Throne Elements. - [ ] Teste real de valor baixo antes de abrir ao público. Se você vai usar o upsell em um clique, mais três: - [ ] Upsell em um clique liberado na conta (é à parte da integração direta). - [ ] Guardado o `transaction_id` de cada venda paga, para usar em `parent_transaction_id` dentro dos 120 minutos. - [ ] O upsell `pending` **nunca** é repetido: você espera o webhook. - [ ] Se o upsell for recorrente: `recurrence_id` conferido na resposta, e o acesso liberado pelo `transaction.paid` de cada cobrança, não pelo `next_charge_at` (que é estimativa). --- # Server-to-server na ShadowBank > Integração por API, sem Throne Elements, na adquirente ShadowBank: cobrança na moeda do comprador, com recorrência e upsell em um clique, para quem já tem checkout próprio. Habilitação caso a caso. Fonte: https://app.thronecorporate.com/docs/cartao/shadowbank-direto ## O que é esta integração A integração direta da ShadowBank é uma alternativa ao Throne Elements para quem tem um checkout de cartão próprio e quer cobrar pela API, do próprio servidor, sem o iframe do Throne. Ela existe para a **Eminence**, para a **Vortex** e para a **ShadowBank**, e esta página cobre a ShadowBank. Nas outras adquirentes a integração continua sendo o [Throne Elements](/docs/cartao). Você cobra na moeda do seu comprador, as mesmas 48 da [lista](/docs/cartao/moeda#moedas-aceitas), e a cotação que credita o seu saldo é fixada na criação da cobrança, como no checkout comum. Quando usar: - Você é um gateway, subadquirente ou agregador e já tem a própria tela de cartão. - Você quer cobrança recorrente e upsell em um clique sem mandar o comprador a uma tela do Throne. Quando **não** usar: - Você quer um checkout pronto, sem desenvolver a tela do cartão: use o [Throne Elements](/docs/cartao). - Você precisa de parcelamento: nesta modalidade a cobrança é sempre à vista. **Upsell em um clique e recorrência funcionam aqui, e o upsell também pode ser recorrente.** O upsell tem rota própria, o [`POST /api/v1/shadowbank/upsell`](#upsell-em-um-clique). O campo `one_click_from` do checkout comum não existe nesta modalidade: ele volta `422 field_not_supported` e a mensagem aponta a rota certa. > [!WARNING] > Na integração direta o número do cartão passa pelo **seu** servidor antes de chegar ao Throne. A guarda desses dados do seu lado (PCI DSS) é responsabilidade sua: nunca grave número, validade ou CVV em banco, arquivo ou log. ## Habilitação A integração direta é liberada **caso a caso**. [Fale com o suporte](https://app.thronecorporate.com/painel). São três requisitos: - A ShadowBank habilitada na sua conta (confira em `GET /api/v1/elements/acquirers`). - A integração direta da ShadowBank liberada na sua conta. - Uma chave de API com o escopo `transactions:write` (gere no painel, em Chaves de API). Sem a liberação, as chamadas respondem `422 partner_api_not_enabled`. Sem a ShadowBank, `422 acquirer_not_enabled`. Sem o escopo, `403 scope_required`. A liberação é por adquirente: quem já usa a integração direta da Eminence ou da Vortex precisa pedir a da ShadowBank à parte. Para o **upsell em um clique** há um quarto requisito, que é por conta e não por adquirente: o upsell em um clique habilitado. Sem ele a rota do upsell responde `403 one_click_not_enabled`, e a cobrança comum continua funcionando. ## O caminho, em dois passos Toda cobrança tem duas chamadas, nesta ordem, as duas feitas **do seu servidor** e com a sua chave de API: 1. **Tokenizar o cartão.** Você manda o cartão e recebe um `card_token`. 2. **Cobrar** com esse `card_token`. A resposta já traz o resultado. O cartão nunca é enviado ao endpoint de cobrança. Se você mandar qualquer campo de cartão nele, a resposta é `400 card_data_not_allowed` e nada é cobrado. ### Passo 1: tokenizar o cartão ``` POST /api/v1/vault/shadowbank/cards Authorization: Bearer pk_live_...:sk_live_... Content-Type: application/json ``` ```json { "number": "5555555555554444", "exp_month": 11, "exp_year": 2031, "cvv": "737", "holder_name": "MARIA S SOUZA", "customer": { "name": "Maria Souza", "email": "maria@exemplo.com" } } ``` Resposta `201`: ```json { "card_token": "sbt_7b58f4a1600bd9419cb20b7f111bc831", "brand": "mastercard", "last4": "4444", "exp_month": 11, "exp_year": 2031, "expires_at": "2026-11-09T03:00:00Z" } ``` O `card_token` vale até `expires_at` — no máximo sete dias, e nunca depois do fim do mês de validade do cartão. O bloco `customer` neste passo é **opcional e recomendado**. Ele não é repassado à adquirente além do nome: serve para que o mesmo comprador caia sempre no mesmo cadastro do lado dela quando voltar a comprar. Mande o mesmo `customer` que você vai mandar na cobrança. Erros deste passo: | HTTP | `error` | Quando | |---|---|---| | `422` | `invalid_card` | Número, validade, CVV ou titular fora do formato, ou o cartão recusado na tokenização. O campo problemático vem em `field`. | | `401` | `unauthorized` | Chave de API ausente ou inválida. | | `403` | `scope_required` | A chave não tem `transactions:write`. | | `422` | `partner_api_not_enabled` | A integração direta não está liberada na conta. | | `422` | `acquirer_not_enabled` | A ShadowBank não está habilitada na conta. | | `429` | `rate_limit_exceeded` | Limite por segundo da conta. Aguarde 1 segundo. | | `503` | `temporarily_unavailable` | Tokenização indisponível. Tente de novo em instantes. | ### Passo 2: cobrar ``` POST /api/v1/shadowbank/payments Authorization: Bearer pk_live_...:sk_live_... Idempotency-Key: pedido-9823 Content-Type: application/json ``` ```json { "amount_cents": 2500, "currency": "BRL", "card_token": "sbt_7b58f4a1600bd9419cb20b7f111bc831", "description": "Plano Pro", "customer": { "name": "Maria Souza", "email": "maria@exemplo.com" }, "metadata": { "pedido": "9823" } } ``` Resposta `201`: ```json { "transaction_id": "a960cd5a-a166-4e16-8e28-72a64b4013ed", "status": "paid", "authorized": true, "amount_cents": 2500, "currency": "BRL", "installments": 1, "message": "Pagamento aprovado.", "created_at": "2026-11-02T01:15:35-03:00", "card": { "brand": "mastercard", "last4": "4444" } } ``` ### Como ler a resposta Depois que a venda é criada, **a resposta é sempre `201`**, e o que muda é o `status`: | `status` | O que fazer | |---|---| | `paid` | Aprovado e liberado. Entregue o pedido. | | `pending` | Em análise da adquirente, ou resultado indefinido. **Não repita a cobrança**: espere o webhook `transaction.paid` ou consulte a venda. | | `failed` | Recusado, ou nada processado. Veja `retryable`. | Em `failed`, o campo `retryable` diz o que fazer: - `retryable: true` — **nada foi cobrado**. Você pode tentar de novo, com um `card_token` novo. - `retryable: false` — a adquirente recusou. Não insista no mesmo cartão; `decline_code` traz o código dela. `pending` é desfecho normal nesta adquirente, não erro: a cobrança pode cair em análise e fechar depois, pelo webhook. Antes de a venda ser criada (erro de validação, conta sem liberação, valor fora da faixa), a resposta é `4xx` ou `503` e a `Idempotency-Key` fica livre para ser repetida. ### Campos do passo 2 | Campo | Obrigatório | Regra | |---|---|---| | `amount_cents` | sim | Inteiro, na menor unidade da moeda. | | `currency` | sim | Três letras, uma das [48 aceitas](/docs/cartao/moeda#moedas-aceitas). | | `card_token` | sim | O token do passo 1. | | `customer.name` | sim | Nome do comprador, até 120 caracteres. | | `customer.email` | não | E-mail do comprador. Opcional nesta adquirente. | | `description` | não | Até 255 caracteres. Vira o nome do produto no extrato interno. | | `metadata` | não | Até 20 chaves de texto, número ou booleano. | | `installments` | não | Só `1`. Qualquer outro valor é `422`. | | `recurrence` | não | Veja [Recorrência](#recorrência). | Campos recusados nesta rota, com o motivo na resposta: `one_click_from` (use a rota do upsell). ### Campos da resposta | Campo | Sempre | O que é | |---|---|---| | `transaction_id` | sim | O identificador da venda no Throne. Guarde este. | | `status` | sim | `paid`, `pending` ou `failed`. | | `authorized` | sim | `true` só em `paid`. | | `amount_cents`, `currency` | sim | Ecoam **o seu pedido**, não o valor interno. | | `installments` | sim | Sempre `1`. | | `message` | sim | Frase pronta para registro. | | `card` | quase sempre | `brand` e `last4`. | | `decline_code` | em `failed` | O código da adquirente. | | `retryable` | em `failed` | Se pode tentar de novo. | | `recurrence_id`, `cycle`, `next_charge_at` | com recorrência | Veja [Recorrência](#recorrência). | ## Consulta `GET /api/v1/transactions/{transaction_id}`, igual a qualquer venda. Veja [Transações](/docs/transacoes). ## Estorno O estorno é feito pelo Throne, não pela API do parceiro. Fale com o suporte. ## Webhook O webhook não muda por causa desta modalidade: `transaction.paid`, `transaction.failed` e os demais chegam no mesmo formato de sempre, no mesmo endpoint. Veja [Webhooks](/docs/webhooks). Libere o pedido pelo webhook ou pela consulta, nunca só pela resposta da cobrança. ## Upsell em um clique Cobra o **mesmo cartão** de uma venda que já foi paga, sem pedir o cartão ao comprador de novo e sem `card_token` nenhum. Você manda só o `transaction_id` da venda de origem. ``` POST /api/v1/shadowbank/upsell Authorization: Bearer pk_live_...:sk_live_... Idempotency-Key: upsell-9823-1 Content-Type: application/json ``` ```json { "parent_transaction_id": "a960cd5a-a166-4e16-8e28-72a64b4013ed", "amount_cents": 1500, "currency": "BRL", "description": "Garantia estendida" } ``` Resposta `201`: ```json { "transaction_id": "7fb09848-911d-45ee-97ee-2d0847c2d3eb", "status": "paid", "authorized": true, "amount_cents": 1500, "currency": "BRL", "installments": 1, "one_click": true, "message": "Pagamento aprovado.", "created_at": "2026-11-02T01:24:35-03:00", "card": { "brand": "visa", "last4": "1881" } } ``` O `status` se lê igual ao da cobrança comum. ### O upsell pode ser recorrente Mande `recurrence` no corpo do upsell e ele vira a primeira cobrança de uma assinatura: ```json { "parent_transaction_id": "a960cd5a-a166-4e16-8e28-72a64b4013ed", "amount_cents": 2000, "currency": "BRL", "description": "Clube mensal", "recurrence": { "cycle": "monthly", "max_payments": 6 } } ``` A resposta traz, além dos campos de sempre, os três da recorrência: ```json { "transaction_id": "45f577d8-e706-4198-91b0-323f64697b93", "status": "paid", "one_click": true, "recurrence_id": "rec_a8158e00a2a6e23bc68f1532", "cycle": "monthly", "next_charge_at": "2026-12-02T15:00:00Z" } ``` ### O que a venda de origem precisa ser | Requisito | Por quê | |---|---| | Sua | Venda de outra conta responde como se não existisse. | | Paga | O upsell cobra o cartão de uma venda que deu certo. | | Na ShadowBank | O upsell cai na mesma adquirente da venda de origem. | | Dentro da janela | A janela padrão é de 2 horas desde o pagamento. | | Com cartão guardado | Vendas pagas antes desta versão não têm o cartão guardado. | | Abaixo do limite de upsells | Até 5 upsells por venda de origem. | O valor do upsell tem um teto próprio: o menor entre um limite absoluto e vinte vezes o valor da venda de origem. Ele existe porque o comprador não digita nada nesta cobrança — um erro de valor do seu lado debitaria algo que ele nunca viu. ### Erros do upsell | HTTP | `error` | Quando | |---|---|---| | `409` | `parent_not_found` | Não existe venda sua com esse `parent_transaction_id`. | | `409` | `parent_not_paid` | A venda de origem não está paga. | | `409` | `parent_refunded` | A venda de origem foi estornada. | | `409` | `parent_charged_back` | A venda de origem foi contestada. | | `409` | `parent_too_old` | Fora da janela do um clique. | | `409` | `upsell_limit_reached` | A venda de origem já atingiu o limite de upsells. | | `409` | `parent_other_acquirer` | A venda de origem é de outra adquirente. | | `409` | `one_click_unavailable` | O um clique não está disponível para essa venda. | | `403` | `one_click_not_enabled` | O upsell em um clique não está habilitado na conta. | | `422` | `field_not_supported` | Você mandou `card_token`, `customer`, `installments` ou `one_click_from`. O campo vem em `field`. | | `422` | `amount_out_of_range` | Acima do teto do upsell ou fora da faixa da adquirente. | | `503` | `busy` | Outro upsell desta venda de origem está em andamento. Tente em 3 segundos. | O upsell não recebe comprador: ele é o da venda de origem, e mandar `customer` é `422`. ## Recorrência Mande `recurrence` na cobrança e ela se torna a primeira de uma assinatura. A adquirente guarda o cartão e cobra os ciclos seguintes sozinha. ```json { "amount_cents": 2500, "currency": "BRL", "card_token": "sbt_7b58f4a1600bd9419cb20b7f111bc831", "customer": { "name": "Maria Souza" }, "recurrence": { "cycle": "monthly", "max_payments": 12 } } ``` | Campo | Obrigatório | Regra | |---|---|---| | `recurrence.cycle` | sim, se mandar `recurrence` | Um dos sete: `weekly`, `biweekly`, `monthly`, `bimonthly`, `quarterly`, `semiannually`, `yearly`. | | `recurrence.max_payments` | não | De 2 a 120, contando a primeira cobrança. Sem ele, a assinatura não tem fim. | A resposta ganha três campos: | Campo | O que é | |---|---| | `recurrence_id` | O identificador da recorrência no Throne. | | `cycle` | O ciclo, de volta. | | `next_charge_at` | Quando a próxima cobrança cai. | Pontos que importam: - **A primeira cobrança é a desta chamada.** A assinatura na adquirente começa a valer a partir do ciclo seguinte, então o comprador nunca é cobrado duas vezes no mesmo dia. - **A assinatura só abre se a primeira cobrança for aprovada.** Recusada, a resposta traz `recurrence_id` e a recorrência fica pendente, sem nada cobrando. - **Os ciclos seguintes chegam pelo webhook**, como vendas novas ligadas à mesma recorrência. - Ciclo fora dos sete é `422 invalid_cycle`, com a lista na mensagem. - Recorrência e parcelamento não se combinam: esta modalidade é sempre à vista. Para listar, consultar e cancelar recorrências, veja [Recorrências](/docs/cartao/recorrencias). ## Dinheiro Taxa, cautela, maturação, antecipação e saque seguem as regras da sua conta, iguais às de qualquer venda de cartão. Veja [Como o dinheiro entra](/docs/dinheiro). ## Limites e tempos - Limite por segundo: o da sua conta, compartilhado entre a tokenização e a cobrança. - A cobrança pode levar até 85 segundos. Configure o seu cliente HTTP com no mínimo 90 segundos de leitura. - Repetir a mesma `Idempotency-Key` devolve a resposta da primeira chamada, sem cobrar de novo. > [!WARNING] > Nunca repita uma cobrança que respondeu `pending`. Ela pode ter sido aprovada do outro lado, e uma segunda chamada cobraria o comprador duas vezes. ## Testar Não há ambiente de testes nesta modalidade: as chamadas são reais. Comece com um valor baixo e um cartão seu. ## Checklist - [ ] A ShadowBank habilitada na conta. - [ ] A integração direta da ShadowBank liberada. - [ ] Chave de API com `transactions:write`. - [ ] Para o upsell: o um clique habilitado na conta. - [ ] O cartão nunca vai ao endpoint de cobrança. - [ ] Número, validade e CVV não são gravados em banco, arquivo nem log do seu lado. - [ ] `Idempotency-Key` em toda cobrança e todo upsell. - [ ] Cliente HTTP com 90 segundos ou mais de leitura. - [ ] `pending` nunca é repetido. - [ ] O pedido é liberado pelo webhook ou pela consulta, nunca só pela resposta. --- # Internacional (Paycore) > Botão de pagamento internacional embutido na sua página. Fonte: https://app.thronecorporate.com/docs/internacional ## Visão geral O Internacional (Paycore) é um botão de pagamento embutido na sua página. O comprador conclui o pagamento com carteira digital internacional ou cartão internacional, dentro do checkout do Throne. É um trilho separado do [cartão](/docs/cartao), e os dois podem ficar habilitados ao mesmo tempo. Diferenças importantes em relação ao cartão: | Item | Internacional (Paycore) | Cartão (Sterling, Imperium, NovaCorp, Eminence, Apex, ShadowBank, Vortex) | |---|---|---| | Endpoint | `POST /global/checkout` | `POST /elements/checkout` | | Como embutir | `iframe` com o `checkout_url` | Script `throne-elements.js` | | Moedas | 22 | 48 | | Cotação que credita o seu saldo | Fixada na aprovação | Fixada na criação | | Recusa | A venda continua `pending` e o comprador tenta de novo | A venda continua `pending`; o checkout fecha 10 minutos depois de uma recusa sem nova tentativa | O `transaction_id` de um trilho não funciona no outro. ## 1. Criar o checkout ```bash cURL curl -X POST https://app.thronecorporate.com/api/v1/global/checkout \ -H "Authorization: Bearer $THRONE_PK:$THRONE_SK" \ -H "Content-Type: application/json" \ -H "Accept: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "amount_cents": 2000, "currency": "usd", "customer": { "name": "John Carter", "email": "john@example.com" } }' ``` ```js Node.js import { randomUUID } from 'node:crypto'; const res = await fetch('https://app.thronecorporate.com/api/v1/global/checkout', { method: 'POST', headers: { Authorization: `Bearer ${process.env.THRONE_PK}:${process.env.THRONE_SK}`, 'Content-Type': 'application/json', Accept: 'application/json', 'Idempotency-Key': randomUUID(), }, body: JSON.stringify({ amount_cents: 2000, currency: 'usd', customer: { name: 'John Carter', email: 'john@example.com' } }), }); const { transaction_id, checkout_url } = await res.json(); // res.status === 201 ``` ```python Python import os, uuid, requests r = requests.post( "https://app.thronecorporate.com/api/v1/global/checkout", headers={ "Authorization": f"Bearer {os.environ['THRONE_PK']}:{os.environ['THRONE_SK']}", "Accept": "application/json", "Idempotency-Key": str(uuid.uuid4()), }, json={"amount_cents": 2000, "currency": "usd", "customer": {"name": "John Carter", "email": "john@example.com"}}, timeout=20, ) checkout = r.json() # r.status_code == 201 ``` | Campo | Obrigatório | Regra | |---|---|---| | `amount_cents` | Sim | Na menor unidade da moeda (US$ 20,00 = `2000`). Mínimo `50` (US$ 0,50). Em JPY, HUF e TWD é a unidade inteira, com mínimo 1 | | `currency` | Sim | Uma destas 22: AUD, BRL, CAD, CHF, CZK, DKK, EUR, GBP, HKD, HUF, ILS, JPY, MXN, NOK, NZD, PHP, PLN, SEK, SGD, THB, TWD, USD | | `customer.name` | Não | Nome do comprador | | `customer.email` | Não | E-mail do comprador | Outros campos (metadata, itens, documento, endereço) são ignorados neste endpoint. Guarde o `transaction_id` para ligar a venda ao seu pedido. Resposta `201 Created`: ```json { "transaction_id": "5f0a2c1e-8d3b-4c7a-9e21-6b4d8f2a1c90", "status": "pending", "checkout_url": "https://app.thronecorporate.com/global/5f0a2c1e-8d3b-4c7a-9e21-6b4d8f2a1c90" } ``` ## 2. Embutir na sua página Coloque o `checkout_url` num `iframe`, com `embed=1` e, se quiser, a aparência: ```html ``` - `color`: cor no formato `#rrggbb` (na URL, `#` vira `%23`). - `theme`: `light` ou `dark`. - `radius`: cantos arredondados, de 0 a 32. - Mantenha `allow="payment"`: sem ele, a carteira digital pode não funcionar dentro do iframe. O checkout avisa a sua página por `postMessage`. Escute as mensagens para ajustar a altura e reagir ao pagamento: ```js const frame = document.getElementById('pagamento-internacional'); const origemThrone = new URL(frame.src).origin; window.addEventListener('message', (ev) => { if (ev.origin !== origemThrone || !ev.data || typeof ev.data.type !== 'string') return; switch (ev.data.type) { case 'throne-elements:resize': frame.style.height = ev.data.height + 'px'; break; case 'throne-elements:paid': // o comprador concluiu: mostre o "obrigado" break; case 'throne-elements:error': // ev.data.message: o comprador pode tentar de novo break; } }); ``` Como no cartão, esses eventos são sinais de tela. A confirmação que vale é o webhook `transaction.paid` ou a consulta. ## 3. Confirmar a venda A venda vira `paid` quando o pagamento é capturado. Isso pode levar alguns instantes depois de o comprador concluir. Você recebe o webhook `transaction.paid`, no mesmo formato do cartão: - `payment_method` vem sempre `credit_card`, inclusive quando o comprador pagou com carteira digital. `card_brand` e `card_last4` vêm `null`. - `currency` e `native_amount_cents` são a moeda e o valor que você mandou. - `amount_cents` é o valor bruto creditado no seu saldo, em reais (a moeda da sua conta), e `net_amount_cents` o líquido, já descontada a taxa de cartão da sua conta. - `fx_rate` é a cotação usada para creditar o seu saldo (reais por 1 unidade da moeda da venda), fixada na aprovação. Em venda criada em BRL, `fx_rate` vale 1. Antes do pagamento, a consulta mostra `amount_cents` igual a 0 e `fx_rate` nulo. ## Recusas, estornos e contestações - **Recusa** não encerra a venda: ela continua `pending` e o comprador pode tentar de novo no mesmo checkout. - **Estorno** total deixa a venda `refunded`; o parcial mantém `paid` com o valor reduzido. - **Contestação** deixa a venda `chargeback`. - Nenhum desses eventos gera webhook. Acompanhe pelo `GET /transactions/{id}`. ## Erros | Status | `error` | O que fazer | |---|---|---| | `422` | `global_not_enabled` | O Internacional não está habilitado na sua conta. Fale com o suporte | | `422` | `unsupported_currency` | A moeda não é aceita. O campo `supported` traz a lista | | `422` | `currency_not_enabled` | A sua conta aceita só algumas moedas. O campo `enabled` traz a lista | | `422` | `amount_too_small` | Abaixo do mínimo. O campo `minimum` traz o valor | | `400` | `idempotency_key_required` | Envie o header `Idempotency-Key` | | `422` | `idempotency_key_mismatch` | Mesma `Idempotency-Key` com corpo diferente. Use uma nova | Trate pelo campo `error`; o texto de `message` pode mudar. --- # Saldo e saques > Consultar saldo e pedir saque por Pix ou cripto pela API. Fonte: https://app.thronecorporate.com/docs/saques > [!WARNING] > Não existe ambiente de testes para saque. Qualquer chave com a permissão de saque, inclusive `pk_test_`, transfere dinheiro de verdade. ## Saldo `GET /balance` (permissão `balance:read`): ```bash curl https://app.thronecorporate.com/api/v1/balance \ -H "Authorization: Bearer $THRONE_PK:$THRONE_SK" \ -H "Accept: application/json" ``` ```json { "balance_cents": 543210, "pending_balance_cents": 0, "reserved_cents": 50000, "available_cents": 543210, "currency": "BRL" } ``` | Campo | O que é | |---|---| | `available_cents` | Saldo livre para saque pela API (igual a `balance_cents`) | | `reserved_cents` | Valor preso em saques em andamento | | `pending_balance_cents` | Saldo ainda pendente | | `currency` | Sempre `BRL` | O saldo da API é o **saldo Pix** da conta. O dinheiro de vendas no cartão (Sterling, Imperium, NovaCorp, Eminence, Apex, ShadowBank, Vortex) e no Internacional (Paycore) fica numa carteira separada, acompanhada pelo painel, e não é sacado pela API. O saque por Pix independe do recebimento por Pix: a conta saca mesmo quando a criação de cobrança Pix responde `pix_not_enabled`. ## Antes do primeiro saque Três exigências, todas obrigatórias: 1. **Permissão `withdrawals:write` na chave.** Ela não vem ligada; habilite em [Chaves de API](https://app.thronecorporate.com/painel/api-keys). 2. **IPs de saque.** Cadastre na chave o IP (ou faixa CIDR) do servidor que vai pedir o saque. Sem nenhum IP cadastrado, todo pedido volta `403 withdrawal_ip_not_authorized`. Uma chave de produção com saque também exige a lista geral de IPs da chave, que vale para todas as chamadas dela; por isso, use **uma chave só para saques**. Veja [as duas listas de IP](/docs/comecar#as-duas-listas-de-ip-da-chave). 3. **Código do autenticador.** Cada pedido leva no corpo o `totp_code`: os 6 dígitos atuais do aplicativo autenticador do dono da conta (o mesmo 2FA do login). Sem 2FA ativado nesse usuário, nenhum saque é aceito. ### Saque automático Para o seu servidor pedir saque sem uma pessoa digitando o código, ele precisa gerar o `totp_code` sozinho, a partir da chave do autenticador do dono da conta (a chave mostrada, em texto ou QR Code, quando o 2FA é ativado). É o TOTP padrão: 6 dígitos, renovados a cada 30 segundos; o Throne aceita também o código anterior e o seguinte. Qualquer biblioteca de TOTP gera o código (por exemplo, `otplib` no Node.js, `pyotp` no Python, `spomky-labs/otphp` no PHP). > [!WARNING] > Essa chave dá acesso ao 2FA do login do dono da conta. Guarde-a como a secret key: só no servidor, fora do código e do repositório. ## Pedir saque `POST /withdrawals`: ```bash cURL curl -X POST https://app.thronecorporate.com/api/v1/withdrawals \ -H "Authorization: Bearer $THRONE_PK:$THRONE_SK" \ -H "Content-Type: application/json" \ -H "Accept: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "amount_cents": 50000, "totp_code": "123456", "destination": { "key": "12345678909", "key_type": "cpf" } }' ``` ```js Node.js import { randomUUID } from 'node:crypto'; const res = await fetch('https://app.thronecorporate.com/api/v1/withdrawals', { method: 'POST', headers: { Authorization: `Bearer ${process.env.THRONE_PK}:${process.env.THRONE_SK}`, 'Content-Type': 'application/json', Accept: 'application/json', 'Idempotency-Key': randomUUID(), }, body: JSON.stringify({ amount_cents: 50000, // R$ 500,00 totp_code: codigoDoAutenticador, destination: { key: '12345678909', key_type: 'cpf' }, }), }); const saque = await res.json(); // 201, status "requested" ``` ```python Python import os, uuid, requests r = requests.post( "https://app.thronecorporate.com/api/v1/withdrawals", headers={ "Authorization": f"Bearer {os.environ['THRONE_PK']}:{os.environ['THRONE_SK']}", "Accept": "application/json", "Idempotency-Key": str(uuid.uuid4()), }, json={ "amount_cents": 50000, "totp_code": codigo_do_autenticador, "destination": {"key": "12345678909", "key_type": "cpf"}, }, timeout=20, ) saque = r.json() # r.status_code == 201 ``` | Campo | Obrigatório | Regra | |---|---|---| | `amount_cents` | Sim | Em centavos. Mínimo padrão R$ 50,00 (`5000`); a sua conta pode ter outro mínimo, nunca abaixo de R$ 10,00 (`1000`). Máximo R$ 500.000,00 por saque | | `totp_code` | Sim | **Texto** com os 6 dígitos atuais do autenticador do dono da conta, entre aspas (`"012345"`). Enviado como número, volta erro de campo | | `destination.key` e `destination.key_type` | Para Pix | `key_type`: `cpf` ou `cnpj` (só números), `email`, `phone` (formato internacional, `+5511999998888`) ou `evp` (a chave aleatória, com hífens) | | `destination.address` e `destination.network` | Para cripto | `network`: `BTC`, `ETH`, `USDT_TRC20` ou `USDT_ERC20` | Mande Pix **ou** cripto: com `address` presente, o saque é tratado como cripto. O saque cripto só é pago se a sua conta estiver configurada para pagar saques em cripto; se não estiver, termina `failed` e o valor volta. ### Taxa, limites e ritmo - A taxa sai **de dentro** do valor pedido: o saldo é debitado em `amount_cents`, a taxa aparece em `fee_cents` e o destino recebe `net_amount_cents`. O valor pedido precisa ser maior que a taxa. - Existe um limite diário por conta, que soma os saques do dia (os recusados não contam). - O mínimo e a taxa da sua conta aparecem no painel, na tela [Saque](https://app.thronecorporate.com/painel/withdraw). - No máximo **1 pedido de saque por minuto**. A janela conta mesmo quando o pedido é recusado por saldo ou por limite. ## Acompanhar o saque A resposta `201` traz o saque com `status: "requested"`: ```json { "id": "0b8f3a52-6c1d-4e2f-9a7b-3c4d5e6f7a8b", "type": "pix", "status": "requested", "amount_cents": 50000, "fee_cents": 500, "net_amount_cents": 49500, "requested_at": "2026-10-03T14:40:00Z", "completed_at": null, "failure_reason": null } ``` | Status | O que significa | |---|---| | `requested` | Pedido aceito, na fila de pagamento | | `processing` | Enviado ao banco. Pode voltar para `requested` enquanto espera a vez na fila | | `completed` | Pago | | `failed` | Não foi pago. O valor inteiro, taxa incluída, volta para o saldo | Consulte com `GET /withdrawals/{id}` (permissão `withdrawals:read`). O campo `failure_reason` vem em linguagem pronta para mostrar ao seu cliente. A API não tem listagem de saques: guarde o `id`. Os webhooks `withdrawal.completed` e `withdrawal.failed` avisam mais cedo, mas **nem todo saque gera webhook**. Consulte o `GET` até chegar em `completed` ou `failed`. Detalhes em [Webhooks](/docs/webhooks). ## Repetir um pedido de saque Um saque repetido por engano é dinheiro saindo duas vezes. Siga esta ordem: 1. **A chamada falhou sem resposta (falha de conexão, timeout ou resposta sem o JSON da API).** Repita com a **mesma** `Idempotency-Key` e **exatamente o mesmo corpo, inclusive o mesmo `totp_code`**, mesmo que ele já tenha vencido. Se o primeiro pedido chegou, a API devolve o saque original sem conferir o código de novo e sem contar no limite de 1 por minuto. 2. **Veio `409 idempotency_key_processing`.** O primeiro pedido ainda está sendo registrado. Espere alguns segundos e repita igual ao passo 1. Se continuar por mais de 2 minutos, siga o passo 4. 3. **Veio um erro `4xx`, ou um `503` com o JSON da API.** O saque não foi criado. Corrija o motivo e peça de novo com uma `Idempotency-Key` nova e um código novo. 4. **Veio `500` (erro inesperado).** O saque pode ter sido criado. Não peça de novo antes de conferir o saldo (`GET /balance`) e a lista de saques no painel. Repetir com a mesma chave devolve `409 idempotency_key_processing` por até 24 horas, sem criar outro saque. Nunca gere uma `Idempotency-Key` nova para um saque cujo resultado você não conhece. Na dúvida, confira o saldo (`GET /balance`) e a lista de saques no painel. ## Erros | Status | `error` | O que fazer | |---|---|---| | `403` | `scope_required` | A chave precisa da permissão `withdrawals:write` | | `403` | `withdrawal_ip_not_authorized` | Cadastre o IP do servidor nos IPs de saque da chave | | `401` | `invalid_two_factor` | Código do autenticador errado ou vencido, ou o dono da conta não tem 2FA ativado. Gere um código novo; se continuar, confira o 2FA no painel | | `422` | `insufficient_balance` | Saldo menor que `amount_cents` | | `422` | `invalid_request` | Valor abaixo do mínimo da conta, acima do máximo ou menor que a taxa. Leia o `message` (abaixo de R$ 10,00 o erro vem como erro de campo) | | `429` | `daily_limit_exceeded` | Passou do limite diário da conta | | `429` | `rate_limited` | Mais de 1 pedido por minuto. Espere 60 segundos | | `403` | `seller_not_approved` | A conta ainda não está liberada para operar | Depois de um erro desta tabela, use uma `Idempotency-Key` nova. Depois de um `500`, ou sem resposta, siga [repetir um pedido de saque](#repetir-um-pedido-de-saque). --- # Webhooks > Eventos enviados ao seu servidor, assinatura HMAC, retentativas, quando chegam e conversão de campanha pelo servidor. Fonte: https://app.thronecorporate.com/docs/webhooks ## Cadastrar o endereço No painel, em [Webhooks](https://app.thronecorporate.com/painel/webhook-settings), adicione a URL do seu servidor. Você pode ter até 20 endereços, cada um com: - a própria URL, sempre em **HTTPS**; - a lista de eventos que quer receber (vazia = todos); - o próprio **secret de assinatura**, que você copia no painel e guarda no servidor. O botão de teste envia um evento `test.ping` na hora, assinado como os outros: ```json { "event_id": "...", "type": "test.ping", "version": "2026-05-01", "created_at": "2026-10-03T14:00:00Z", "data": { "message": "Webhook de teste do Throne, seu endpoint está funcionando!", "sent_at": "2026-10-03T14:00:00Z" } } ``` O histórico de entregas fica em [Entregas de webhook](https://app.thronecorporate.com/painel/webhook-deliveries). ## Eventos | Evento | Quando | |---|---| | `transaction.paid` | Pagamento confirmado: Pix, cartão (Sterling, Imperium, NovaCorp, Eminence, Apex, ShadowBank, Vortex) e Internacional (Paycore) | | `transaction.in_review` | **Só na Vortex.** O banco autorizou e a adquirente está analisando. O cartão já foi debitado, e a venda continua `pending` | | `withdrawal.completed` | Saque pago | | `withdrawal.failed` | Saque não pago (o valor volta para o saldo) | | `test.ping` | Botão de teste do painel | Só esses eventos existem. Recusa de cartão, expiração, estorno e contestação **não geram webhook**: acompanhe pelo `GET /transactions/{id}`. O `transaction.in_review` **vem desmarcado**: marque-o no endpoint em [Webhooks](https://app.thronecorporate.com/painel/webhooks) para começar a receber. Marcar ou desmarcar não muda nada nos outros eventos. Recorrência não muda nada aqui: cada cobrança de ciclo chega como um `transaction.paid` normal, com o mesmo formato e com `transaction_id` próprio, como qualquer venda de cartão. Para ligar a cobrança à assinatura, consulte a recorrência, como em [Recorrência](/docs/cartao/recorrencias). ### `transaction.in_review` Só na **Vortex**. Nessa adquirente o banco autoriza na hora e a análise de risco da adquirente libera depois. Nesse intervalo **o cartão do comprador já foi debitado** e a venda continua `pending` para você. O corpo tem os mesmos campos do `transaction.paid`, com os valores que uma venda pendente tem: `amount_cents`, `fee_cents` e `net_amount_cents` só nascem na liquidação, então chegam em zero, e `status` vem `pending`. **Não existe status novo**: quem diz "em análise" é o nome do evento. ```json { "type": "transaction.in_review", "data": { "transaction_id": "9f2c1d7e-4a8b-4c3d-8e9f-1a2b3c4d5e6f", "status": "pending", "payment_method": "credit_card", "amount_cents": 0, "fee_cents": 0, "net_amount_cents": 0, "card_brand": "visa", "card_last4": "4242", "currency": "BRL", "native_amount_cents": 49900, "customer": { "name": "Ana Lima", "email": "ana@exemplo.com" }, "metadata": { "order_id": "ORD-2026-00001" }, "created_at": "2026-10-29T18:04:11Z" } } ``` **Não libere acesso neste evento.** Ele diz que o cartão foi debitado, não que a venda foi aprovada: a análise pode recusar, e aí a venda vira `failed` e o valor volta ao comprador. Quem libera é o `transaction.paid`. Cada venda avisa **uma vez**. Reenvio da adquirente e releitura do seu lado não repetem o aviso. Depois dele, três caminhos: a análise aprova e chega o `transaction.paid`; a análise recusa e a venda vira `failed` (sem webhook, como as outras recusas); ou a análise demora, e a venda segue `pending` até resolver. ## Quando o webhook chega O `transaction.paid` sai quando o pagamento é confirmado. O tempo até ele chegar depende do meio de pagamento: | Meio | Quando chega | |---|---| | Pix | Logo depois da confirmação do pagamento | | Cartão na Sterling e na Imperium | Logo depois da aprovação, na maior parte das vendas. Uma venda em análise antifraude só gera o webhook quando a análise aprova | | Cartão na NovaCorp | Logo depois da aprovação quando o comprador volta da tela do banco. Se ele sair antes, a confirmação vem pela conferência automática, alguns minutos depois | | Cartão na Eminence | Logo depois da aprovação. Quando o banco pede verificação, depois que o comprador a conclui. Uma venda em análise antifraude só gera o webhook quando a análise aprova | | Cartão na Apex | Logo depois da aprovação. Uma venda em análise antifraude só gera o webhook quando a análise aprova | | Cartão na ShadowBank | Logo depois da aprovação. Uma venda em análise antifraude só gera o webhook quando a análise aprova | | Cartão na Vortex | Logo depois da aprovação. Uma venda em análise antifraude só gera o webhook quando a análise aprova | | Internacional (Paycore) | Logo depois da aprovação | Não cancele um pedido por falta de webhook antes da validade da cobrança (24 horas no cartão). Na dúvida, consulte `GET /transactions/{id}`. ## Conversão de campanha pelo servidor Se você mede vendas de anúncio (pixel de compra), envie a conversão **pelo seu servidor**, ao receber `transaction.paid`, pela API de conversões da plataforma de anúncios. Não dependa só do evento `throne-elements:paid` da página: - na NovaCorp, quando o comprador vem de um navegador de app (Instagram, Facebook, Google, TikTok), a aba sai da sua página para pagar e nenhum evento de tela chega; - o comprador pode fechar a página antes do evento; - bloqueadores de anúncio derrubam pixels no navegador. Para não contar a mesma compra duas vezes quando o pixel da página também disparar, use o `transaction_id` como identificador do evento nos dois lados. Assim a plataforma de anúncios junta os dois envios num só. ## Formato Todo evento chega como `POST` com corpo JSON: ```json { "event_id": "8c1f3c5e-2c1a-4d1e-9a2b-1f0c9d7e6b5a", "type": "transaction.paid", "version": "2026-05-01", "created_at": "2026-10-03T14:21:07Z", "data": { } } ``` ### `transaction.paid` Pix: ```json "data": { "transaction_id": "72394240-db89-4ec5-b370-4a3e7b998409", "status": "paid", "payment_method": "pix", "amount_cents": 4990, "fee_cents": 250, "net_amount_cents": 4740, "paid_at": "2026-10-03T14:21:05Z", "customer": { "name": "Maria Souza", "document": "12345678909", "email": "maria@exemplo.com" }, "shipping_address": null, "items": null, "metadata": { "loja_pedido_id": "PED-1042" } } ``` Cartão e Internacional trazem também `card_brand`, `card_last4`, `currency`, `native_amount_cents` e `fx_rate`: ```json "data": { "transaction_id": "ae3bf1da-0c9b-4818-9140-c6432152c23e", "status": "paid", "payment_method": "credit_card", "amount_cents": 31237, "fee_cents": 4148, "net_amount_cents": 27089, "paid_at": "2026-10-03T14:30:58Z", "card_brand": "visa", "card_last4": "4242", "currency": "CHF", "native_amount_cents": 4990, "fx_rate": "6.26000000", "customer": { "name": "Luca Meier", "email": "luca@example.ch" }, "shipping_address": null, "items": null, "metadata": {} } ``` `fx_rate` chega como texto com 8 casas. Trate os campos que não usa (ou que vierem `null`) como opcionais: qualquer `transaction.paid` pode trazer o conjunto completo. ### `withdrawal.completed` e `withdrawal.failed` ```json "data": { "withdrawal_id": "0b8f3a52-6c1d-4e2f-9a7b-3c4d5e6f7a8b", "amount_cents": 50000, "completed_at": "2026-10-03T14:45:12Z" } ``` ```json "data": { "withdrawal_id": "0b8f3a52-6c1d-4e2f-9a7b-3c4d5e6f7a8b", "amount_cents": 50000, "reason": "Chave Pix inexistente" } ``` O `reason` de `withdrawal.failed` pode vir técnico. Para mostrar ao seu cliente, use o `failure_reason` do `GET /withdrawals/{id}`, que já vem em linguagem amigável. ## Validar a assinatura Cada entrega traz o header `X-Throne-Signature`: o HMAC-SHA256, em hexadecimal, do **corpo cru** da requisição, com o secret do endereço. Calcule sobre os bytes exatamente como chegaram, antes de interpretar o JSON (reformatar o JSON muda a assinatura). ```php PHP $body = file_get_contents('php://input'); $recebida = $_SERVER['HTTP_X_THRONE_SIGNATURE'] ?? ''; $esperada = hash_hmac('sha256', $body, getenv('THRONE_WEBHOOK_SECRET')); if (! hash_equals($esperada, $recebida)) { http_response_code(401); exit; } $evento = json_decode($body, true); http_response_code(200); // responda rápido; processe em segundo plano ``` ```js Node.js import crypto from 'node:crypto'; import express from 'express'; const app = express(); // corpo cru: não use express.json() nesta rota app.post('/throne/webhook', express.raw({ type: '*/*' }), (req, res) => { const recebida = req.get('X-Throne-Signature') || ''; const esperada = crypto .createHmac('sha256', process.env.THRONE_WEBHOOK_SECRET) .update(req.body) .digest('hex'); const ok = recebida.length === esperada.length && crypto.timingSafeEqual(Buffer.from(recebida), Buffer.from(esperada)); if (!ok) return res.sendStatus(401); const evento = JSON.parse(req.body.toString('utf8')); res.sendStatus(200); // responda rápido; processe em segundo plano }); ``` ```python Python import hashlib, hmac, json, os from flask import Flask, request, abort app = Flask(__name__) @app.post("/throne/webhook") def throne_webhook(): body = request.get_data() # bytes crus esperada = hmac.new(os.environ["THRONE_WEBHOOK_SECRET"].encode(), body, hashlib.sha256).hexdigest() if not hmac.compare_digest(esperada, request.headers.get("X-Throne-Signature", "")): abort(401) evento = json.loads(body) return "", 200 # responda rápido; processe em segundo plano ``` ```go Go func throneWebhook(w http.ResponseWriter, r *http.Request) { body, err := io.ReadAll(r.Body) if err != nil { w.WriteHeader(http.StatusBadRequest) return } mac := hmac.New(sha256.New, []byte(os.Getenv("THRONE_WEBHOOK_SECRET"))) mac.Write(body) esperada := hex.EncodeToString(mac.Sum(nil)) if !hmac.Equal([]byte(esperada), []byte(r.Header.Get("X-Throne-Signature"))) { w.WriteHeader(http.StatusUnauthorized) return } w.WriteHeader(http.StatusOK) // responda rápido; processe em segundo plano } ``` ## Entrega e retentativas - **Responda 2xx em até 3 segundos.** Grave o evento e processe depois; processamento pesado dentro da requisição faz a entrega falhar por tempo. - Sem 2xx, o Throne tenta de novo: **até 10 tentativas no total** (a primeira e mais 9), com intervalos crescentes, de segundos até algumas horas. - Responda 2xx também aos tipos que você não trata (como `test.ping`), para não gerar retentativas. - Use a URL final. A entrega não segue redirecionamento como POST: uma URL que redireciona (por exemplo, de `http` para `https`, ou com e sem `www`) faz a entrega falhar. - Endereços internos (como `localhost` ou `192.168.x.x`) não recebem entregas. ## Não processe duas vezes O mesmo pagamento pode chegar mais de uma vez (retentativas, ou mais de um endereço cadastrado). O `event_id` é diferente para cada endereço, então **deduplique pelo conteúdo**: - `transaction.paid`: pela dupla `type` + `data.transaction_id`; - saques: por `type` + `data.withdrawal_id`. Se o evento já foi processado, responda `200` de novo sem refazer nada. ## Quando não chega webhook Recusa e expiração de cartão, expiração de Pix, estornos e contestações não geram evento, e alguns saques terminam sem webhook. Para esses casos, consulte: - `GET /transactions/{id}` para cobranças e checkouts; - `GET /withdrawals/{id}` para saques. --- # Erros > Cada código de erro da API: status, causa, o que fazer e se pode repetir. Fonte: https://app.thronecorporate.com/docs/errors ## Como ler um erro Há dois formatos: ```json { "error": "invalid_amount", "message": "Valor abaixo do mínimo aceito: CHF 0,80." } ``` ```json { "message": "O campo customer.email é obrigatório.", "errors": { "customer.email": ["O campo customer.email é obrigatório."] } } ``` O primeiro é erro de regra: decida pelo campo `error`, que é fixo. O segundo é erro de campo (validação), com a lista em `errors`. O texto de `message` serve para log e apoio e pode mudar. Alguns erros trazem campos extras, sempre como acréscimo: | Campo | Onde aparece | Para que serve | |---|---|---| | `retryable` | `502` e `503` do cartão | `true`: vale repetir com uma `Idempotency-Key` nova. `false`: não repita igual | | `retry_same_key` | `502 acquirer_error` do Pix em `POST /transactions` | Só vem com `true`: a cobrança pode ter sido criada. Repita a mesma chamada, com a mesma `Idempotency-Key` e o mesmo corpo, e nunca com uma chave nova | | Header `Retry-After` | `429`, `503` e `fx_unavailable` | Segundos que você deve esperar antes de repetir | | `max_amount_cents`, `min_amount_cents`, `currency` | `422 invalid_amount` do cartão | O limite da adquirente, na moeda que você mandou | | `alternatives` | `422 invalid_amount` acima do máximo | Outras adquirentes habilitadas na conta que aceitam o valor | | `field` | `422 invalid_appearance` do Checkout Brasil | O caminho da chave de `appearance` recusada, por exemplo `appearance.button.text` | ## Repetir ou não A regra única de repetição, com o que a API devolve em cada caso, está em [como repetir com segurança](/docs/comecar#como-repetir-uma-chamada-com-seguranca). Em resumo: - **`4xx`**: nada foi criado. Corrija o que for preciso e, se for repetir, use uma `Idempotency-Key` **nova**. Repetir a mesma chave devolve `409`. - **`502` ou `503` com o JSON da API**: nada foi criado. Repita com uma `Idempotency-Key` **nova**, depois do `Retry-After` quando ele vier. No `502` do cartão, só com `"retryable": true`. - **`502 acquirer_error` com `"retry_same_key": true`** (só no Pix de `POST /transactions`): é a exceção da linha acima. A cobrança pode ter sido criada na adquirente. Repita a **mesma** chamada, com a mesma `Idempotency-Key` e o mesmo corpo da primeira tentativa: a chave fica livre para essa repetição, e só para ela. Com outro corpo, a resposta é `422 idempotency_key_mismatch`. Nunca use uma chave nova para esse pedido, porque ela pode gerar uma segunda cobrança. - **`500`**: algo pode ter sido criado. Não use uma `Idempotency-Key` nova antes de conferir no painel. Em `POST /elements/checkout`, repita antes a mesma chamada, igual: se nada foi criado, ela roda de novo. - **Sem resposta (falha de conexão, timeout ou resposta sem o JSON da API)**: repita a **mesma** chamada, com a mesma `Idempotency-Key` e o mesmo corpo. - **`409 idempotency_key_processing`**: a primeira chamada ainda está em andamento. Espere alguns segundos e repita igual. Se continuar por mais de 2 minutos, confira no painel se algo foi criado antes de usar uma chave nova. A coluna **Repetir** das tabelas abaixo diz o que fazer em cada código. ## Autenticação e permissões | Status | `error` | Significado | O que fazer | Repetir | |---|---|---|---|---| | `401` | `unauthorized`, `detail: credentials_required` | Faltou o header `Authorization` ou ele não começa com `Bearer ` | Envie `Authorization: Bearer pk:sk` | Depois de corrigir | | `401` | `unauthorized`, `detail: malformed_credentials` | Formato errado (por exemplo, base64) | Envie `pk:sk` em texto | Depois de corrigir | | `401` | `unauthorized`, `detail: invalid_credentials` | Chave de API inexistente, revogada ou conta bloqueada | Gere outra chave de API no painel | Depois de corrigir | | `401` | `unauthorized`, `detail: ip_not_allowed` | IP fora da lista de IPs da chave de API | Inclua o IP na lista da chave | Depois de corrigir | | `403` | `scope_required` | A chave não tem a permissão do endpoint | Veja `required_scope` e ligue a permissão | Com chave nova | | `403` | `withdrawal_ip_not_authorized` | IP fora da lista de IPs de saque | Cadastre o IP do servidor nos IPs de saque | Com chave nova | | `403` | `seller_not_approved` | A conta ainda não está liberada para operar | Conclua o cadastro no painel | Com chave nova | ## Idempotência | Status | `error` | Significado | O que fazer | Repetir | |---|---|---|---|---| | `400` | `idempotency_key_required` | Faltou o header `Idempotency-Key` ou ele não tem de 8 a 128 caracteres | Envie um UUID v4 por operação | Depois de corrigir | | `422` | `idempotency_key_mismatch` | Mesma `Idempotency-Key` com corpo diferente | Use uma `Idempotency-Key` nova | Com chave nova | | `422` | `idempotency_key_other_endpoint` | Mesma `Idempotency-Key` usada em outro endpoint (o `POST /elements/checkout` e o `POST /elements/checkout/br` não compartilham chave) | Use uma `Idempotency-Key` nova | Com chave nova | | `409` | `idempotency_key_processing` | A primeira chamada ainda está em andamento, ou terminou em erro de servidor (`5xx`) e a chave ficou presa por até 24 horas (em `POST /elements/checkout`, um `502` ou `503` libera a chave; um `500` também, a não ser que a venda tenha chegado a ser criada ou que a chamada tenha sido interrompida no servidor; em `POST /transactions`, o `502 acquirer_error` também libera a chave, e com `"retry_same_key": true` só para a repetição do mesmo corpo) | Espere alguns segundos e repita a mesma chamada, igual. Se continuar por mais de 2 minutos, siga a [regra de repetição](/docs/comecar#como-repetir-uma-chamada-com-seguranca) | A mesma chamada, igual | | `409` | `idempotency_key_race` | Chamadas simultâneas com a mesma `Idempotency-Key`, ou uma chave cuja primeira chamada recebeu erro `4xx` | Depois de um `4xx`, nada foi criado: use uma `Idempotency-Key` nova. Se foram duas chamadas ao mesmo tempo, espere alguns segundos e repita a mesma chamada, igual: ela devolve a resposta da primeira | Com chave nova depois de um `4xx`. A mesma chamada, igual, depois de chamadas simultâneas | | `409` | `idempotency_key_in_use` | Essa `Idempotency-Key` não pode ser usada pela sua conta | Use uma `Idempotency-Key` nova | Com chave nova | ## Validação e regras gerais | Status | `error` | Significado | O que fazer | Repetir | |---|---|---|---|---| | `422` | (lista em `errors`) | Campo faltando ou fora do formato | Corrija o campo indicado | Com chave nova | | `422` | `invalid_request` | Regra de valor, método ou destino (por exemplo, Pix abaixo de R$ 1,00 ou saque abaixo do mínimo) | Leia o `message` e ajuste | Com chave nova | | `404` | | O recurso não existe ou não é da sua conta | Confira o id (UUID em minúsculas) | Depois de corrigir | | `429` | `rate_limit_exceeded` | Mais de 10 cobranças Pix por segundo | Respeite o `Retry-After` | Com chave nova, depois do `Retry-After` | | `429` | | Mais de 10 chamadas por segundo sem credencial válida, do mesmo IP | Corrija a credencial e respeite o `Retry-After` | Depois do `Retry-After` | | `503` | `temporarily_unavailable` | Falha temporária. Nada foi criado | Respeite o `Retry-After`, com recuo ([como](/docs/limites#quando-a-resposta-e-429-ou-503)) | Com chave nova, depois do `Retry-After` | ## Pix | Status | `error` | Significado | O que fazer | Repetir | |---|---|---|---|---| | `422` | `pix_not_enabled` | O recebimento por Pix não está habilitado nesta conta. Nada foi criado. Vale para `POST /transactions` com `pix` e para `POST /payment-links` | Fale com o suporte para habilitar. Cartão e saques por Pix continuam normais | Não, até habilitar | | `422` | `items_total_mismatch` | A soma dos itens não bate com `amount_cents` | Corrija os itens ou o valor | Com chave nova | | `422` | `split_invalid` | Divisão para a própria conta | Remova essa divisão | Com chave nova | | `502` | `acquirer_error`, com `"retry_same_key": true` | A adquirente não confirmou o Pix, e a cobrança pode ter sido criada. Nada fica gravado na sua conta e a chave é liberada para a repetição do mesmo corpo | Repita a mesma chamada, com a mesma `Idempotency-Key` e o mesmo corpo da primeira tentativa. Se o erro continuar, fale com o suporte informando a chave, antes de criar outra cobrança para o pedido | A mesma chamada, igual | | `502` | `acquirer_error`, sem `retry_same_key` | A adquirente recusou o Pix antes de criar. Nada foi criado | Tente de novo em instantes | Com chave nova | | `409` | `already_revoked` | O link de pagamento já estava desativado | Nada a fazer | Não | ## Cartão, integração direta na Vortex Só em `POST /api/v1/vault/vortex/cards` e `POST /api/v1/vortex/payments`. Veja [Server-to-server na Vortex](/docs/cartao/vortex-direto). | Status | `error` | Significado | O que fazer | Repetir | |---|---|---|---|---| | `422` | `partner_api_not_enabled` | A integração direta da Vortex não está liberada na conta. A liberação é por adquirente: a da Eminence não vale aqui | Fale com o suporte | Com a mesma chave, depois da liberação | | `422` | `invalid_card` | Número, validade, CVV ou nome do titular inválidos. O campo `field` diz qual | Peça ao comprador para conferir o cartão | Com chave nova, depois de corrigir | | `422` | `invalid_card_token` | O `card_token` está fora do formato, venceu ou já foi usado. Ele serve para uma cobrança só | Tokenize de novo | Com chave nova | | `422` | `fx_unavailable` | Cotação momentaneamente indisponível. Nada foi criado | Espere o `Retry-After` (60 s) | Com a mesma chave | | `422` | `amount_out_of_range` | Valor fora da faixa da adquirente. A resposta traz `min_amount_cents`, `max_amount_cents` e `currency`, na moeda que você enviou | Ajuste o valor | Com chave nova | | `503` | `busy` | Cobranças diretas demais em andamento ao mesmo tempo. Nada foi criado | Espere o `Retry-After` | Com a mesma chave | | `422` | `invalid_cycle` | Só na integração direta da Vortex: o `recurrence.cycle` não é um dos sete ciclos aceitos. A resposta traz a lista. Nada foi criado | Mande um de `weekly`, `biweekly`, `monthly`, `bimonthly`, `quarterly`, `semiannually` ou `yearly` | Com a mesma chave, depois de corrigir | | `422` | `recurrence_not_supported` | Só na integração direta da Vortex: o corpo trouxe `recurrence` e a cobrança recorrente não está disponível nesta adquirente no momento. Nada foi criado | Fale com o suporte, ou cobre sem o campo | Com a mesma chave, depois da liberação | | `422` | `one_click_not_supported` | O corpo trouxe `one_click_from` numa rota que não tem esse campo. Nada foi criado | Na Vortex, o upsell em um clique tem rota própria: [`POST /api/v1/vortex/upsell`](/docs/cartao/vortex-direto#upsell-em-um-clique). Nas outras, cobre normalmente | Com a mesma chave, sem o campo | ## Cartão (Sterling, Imperium, NovaCorp, Eminence, Apex, ShadowBank, Vortex) | Status | `error` | Significado | O que fazer | Repetir | |---|---|---|---|---| | `422` | `card_not_enabled` | A conta não tem cartão habilitado | Fale com o suporte | Não | | `422` | `invalid_acquirer` | `acquirer` fora da lista | Use `sterling`, `imperium`, `novacorp`, `eminence`, `apex`, `shadowbank` ou `vortex` | Com chave nova | | `422` | `acquirer_not_enabled` | A adquirente pedida em `acquirer` não está habilitada na conta. Em `POST /elements/checkout/br`, também quando a conta não tem adquirente habilitada para o Checkout Brasil, ou quando `acquirer` pede uma que não atende o Checkout Brasil | Escolha uma das listadas no `message` (ou em `GET /elements/acquirers`). No Checkout Brasil, fale com o suporte para habilitar | Com chave nova | | `422` | `customer_required_for_shadowbank` | Só na ShadowBank: a criação veio sem `customer.name` ou `customer.email`, com e-mail inválido ou fora dos tamanhos (`name` até 120 caracteres, `email` até 180). Nada foi criado | Mande os dois campos na criação, dentro dos tamanhos. O elemento não pergunta nome nem e-mail ao comprador; colete os dois na sua página | Com chave nova, depois de corrigir | | `422` | `customer_required_for_vortex` | Só na Vortex: a criação veio sem `customer.name` ou `customer.email`, com e-mail inválido ou fora dos tamanhos (`name` até 120 caracteres, `email` até 180). Nada foi criado | Mande os dois campos na criação, dentro dos tamanhos. O elemento não pergunta nome nem e-mail ao comprador; colete os dois na sua página | Com chave nova, depois de corrigir | | `422` | `currency_not_supported` | Moeda fora das 48 aceitas | Use uma moeda da [lista](/docs/cartao/moeda#moedas-aceitas) | Com chave nova | | `422` | `fx_unavailable` | Cotação momentaneamente indisponível | Espere o `Retry-After` (60 s) | Com chave nova | | `422` | `invalid_amount` | Valor abaixo do mínimo ou acima do máximo da adquirente, na moeda enviada e pela cotação do dia. A resposta traz o limite na sua moeda (`min_amount_cents` ou `max_amount_cents`) e, acima do máximo, `alternatives` | Abaixo do mínimo, aumente o valor. Acima do máximo, crie com o `acquirer` de uma das `alternatives` ou divida o pedido | Não com o mesmo valor na mesma adquirente | | `422` | `invalid_cycle` | O `recurrence.cycle` não é um dos sete ciclos aceitos. A resposta traz a lista. Nada foi criado | Mande um de `weekly`, `biweekly`, `monthly`, `bimonthly`, `quarterly`, `semiannually` ou `yearly` | Com chave nova, depois de corrigir | | `422` | `recurrence_not_supported` | A adquirente da chamada não faz cobrança recorrente. Nada foi criado | Mande `acquirer` com uma das que o `message` lista | Com chave nova, depois de corrigir | | `422` | `field_not_supported` | Só em `POST /elements/checkout/br`: o corpo trouxe `one_click_from` ou `recurrence`, que não existem no Checkout Brasil. Nada foi criado | Tire o campo do corpo. Para upsell em um clique e recorrência, use o `POST /elements/checkout` | Com chave nova, depois de corrigir | | `422` | `invalid_appearance` | Só em `POST /elements/checkout/br`: o objeto `appearance` trouxe chave que não existe, valor fora da regra ou combinação impossível. O campo `field` diz qual chave. Nada foi criado | Corrija a chave indicada em `field` | Com chave nova, depois de corrigir | | `503` | `temporarily_unavailable` | A adquirente está em pausa, instável ou sem capacidade. Vem com `Retry-After` e `"retryable": true`. Nada foi criado | Espere o `Retry-After`, ou crie com outra adquirente habilitada | Com chave nova, depois do `Retry-After` | | `502` | `checkout_failed` | O checkout não foi criado. `"retryable": false` quando a adquirente recusou a criação; `true` numa falha passageira de conexão | Com `false`, não repita igual e fale com o suporte se continuar. Com `true`, repita | Só com `"retryable": true`, com chave nova | O máximo por venda depende da adquirente e varia com a moeda e com a cotação do dia. `GET /elements/acquirers?currency=` mostra o limite atual na moeda pedida. Veja [Escolher a adquirente](/docs/cartao/escolher#quando-o-valor-passa-do-maximo). > [!NOTE] > O campo `one_click_from` não tem erro próprio. Venda de origem inexistente, de outra conta, não paga, fora da janela ou acima do teto não devolve `4xx`: a resposta vem `201` com `one_click: false` e o comprador digita o cartão. Veja [Upsell em um clique](/docs/cartao/oneclick). Os endpoints de recorrência (`GET /recurrences`, `GET /recurrences/{id}` e `POST /recurrences/{id}/cancel`) usam o mesmo formato de erro e têm um código próprio, no cancelamento: | Status | `error` | Significado | O que fazer | Repetir | |---|---|---|---|---| | `422` | `cancel_failed` | Esta recorrência não é cancelada pela API. Ela continua valendo | Fale com o suporte | Não | | `503` | `cancel_failed` | O cancelamento não foi confirmado agora | Repita a mesma chamada em instantes, até receber `200`. Enquanto a resposta não for `200`, trate a recorrência como ativa | A mesma chamada, igual | Uma recorrência que não é da sua conta responde `404`, como qualquer outro recurso. Os detalhes de cada situação estão em [Recorrência](/docs/cartao/recorrencias). ## Internacional (Paycore) | Status | `error` | Significado | O que fazer | Repetir | |---|---|---|---|---| | `422` | `global_not_enabled` | O Internacional não está habilitado | Fale com o suporte | Não | | `422` | `unsupported_currency` | Moeda fora das 22 aceitas | Veja o campo `supported` | Com chave nova | | `422` | `currency_not_enabled` | Moeda não liberada na sua conta | Veja o campo `enabled` | Com chave nova | | `422` | `amount_too_small` | Abaixo do mínimo | Veja o campo `minimum` | Com chave nova | ## Saques O saque por Pix não depende do recebimento por Pix: uma conta com `pix_not_enabled` na criação de cobrança continua sacando normalmente. | Status | `error` | Significado | O que fazer | Repetir | |---|---|---|---|---| | `401` | `invalid_two_factor` | Código do autenticador errado ou vencido, ou o dono da conta não tem 2FA ativado | Gere um código novo; se continuar, confira o 2FA no painel | Com chave nova e código novo | | `422` | `insufficient_balance` | Saldo menor que `amount_cents` | Consulte `GET /balance` | Com chave nova | | `429` | `daily_limit_exceeded` | Passou do limite diário da conta | Tente no dia seguinte | Com chave nova, no dia seguinte | | `429` | `rate_limited` | Mais de 1 pedido de saque por minuto | Espere 60 segundos | Com chave nova | ## Eventos de erro do checkout embutido Os códigos que chegam em `throne-elements:error` (`e.detail.code`, como `declined` e `popup_blocked`) estão em [Throne Elements](/docs/cartao/elements#codigos-de-erro). Eles são sinais de tela e nunca substituem o webhook. --- # Exemplos por linguagem > Cliente HTTP mínimo pronto para copiar em PHP, Node.js e Python, com timeout e repetição segura. Fonte: https://app.thronecorporate.com/docs/sdks ## Não existe pacote para instalar A API é REST com JSON: qualquer cliente HTTP serve. O Throne **não publica pacote** no npm, Packagist, PyPI ou outro repositório. Pacotes com nomes parecidos com "throne" não são nossos: não instale. Abaixo há um cliente mínimo, pronto para copiar, em cada linguagem. Ele já: - monta a autenticação e manda `Accept: application/json`; - usa timeout (20 segundos por padrão; passe 60 segundos na criação de checkout de cartão); - aceita uma `Idempotency-Key` sua nos `POST`, para você repetir com a mesma chave depois de um timeout; - transforma erros em exceção com o código `error`, o status, o `retryable` e o `Retry-After`. Sem resposta (falha de conexão ou timeout), o status é `0`. ```php PHP final class ThroneError extends RuntimeException { public function __construct( public readonly int $status, // 0 = sem resposta (conexão caiu ou timeout) public readonly string $error, string $message, public readonly ?bool $retryable = null, public readonly ?int $retryAfter = null, ) { parent::__construct("{$error}: {$message}", $status); } } final class Throne { public function __construct( private string $pk, private string $sk, private string $base = 'https://app.thronecorporate.com/api/v1', ) {} public function get(string $path, int $timeout = 20): array { return $this->request('GET', $path, null, null, $timeout); } public function post(string $path, array $body, ?string $idempotencyKey = null, int $timeout = 20): array { return $this->request('POST', $path, $body, $idempotencyKey ?? bin2hex(random_bytes(16)), $timeout); } private function request(string $method, string $path, ?array $body, ?string $key, int $timeout): array { $headers = [ "Authorization: Bearer {$this->pk}:{$this->sk}", 'Accept: application/json', 'Content-Type: application/json', ]; if ($key !== null) { $headers[] = "Idempotency-Key: {$key}"; } $retryAfter = null; $ch = curl_init($this->base . $path); curl_setopt_array($ch, [ CURLOPT_CUSTOMREQUEST => $method, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => $headers, CURLOPT_CONNECTTIMEOUT => 10, CURLOPT_TIMEOUT => $timeout, CURLOPT_HEADERFUNCTION => function ($ch, string $line) use (&$retryAfter): int { if (stripos($line, 'Retry-After:') === 0) { $retryAfter = (int) trim(substr($line, 12)); } return strlen($line); }, ]); if ($body !== null) { curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($body)); } $raw = curl_exec($ch); if ($raw === false) { // Sem resposta: repita com a MESMA Idempotency-Key. throw new ThroneError(0, 'network', curl_error($ch)); } $status = curl_getinfo($ch, CURLINFO_HTTP_CODE); $json = json_decode((string) $raw, true) ?? []; if ($status >= 400) { throw new ThroneError( $status, $json['error'] ?? 'http_' . $status, $json['message'] ?? '', $json['retryable'] ?? null, $retryAfter, ); } return $json; } } $throne = new Throne(getenv('THRONE_PK'), getenv('THRONE_SK')); // Pix $pix = $throne->post('/transactions', [ 'payment_method' => 'pix', 'amount_cents' => 4990, 'customer' => ['name' => 'Maria Souza', 'document' => '12345678909', 'email' => 'maria@exemplo.com'], ]); // Cartão: guarde a chave junto do pedido antes de chamar; 60 segundos de timeout. $chave = bin2hex(random_bytes(16)); $checkout = $throne->post('/elements/checkout', ['amount_cents' => 4990, 'currency' => 'usd'], $chave, 60); ``` ```js Node.js import { randomUUID } from 'node:crypto'; export class ThroneError extends Error { constructor(status, code, message, retryable = null, retryAfter = null) { super(`${code}: ${message}`); this.status = status; // 0 = sem resposta (conexão caiu ou timeout) this.code = code; this.retryable = retryable; this.retryAfter = retryAfter; } } export function throneClient({ pk, sk, base = 'https://app.thronecorporate.com/api/v1' }) { async function request(method, path, body, idempotencyKey, timeoutMs = 20000) { const headers = { Authorization: `Bearer ${pk}:${sk}`, Accept: 'application/json', 'Content-Type': 'application/json', }; if (method === 'POST') headers['Idempotency-Key'] = idempotencyKey ?? randomUUID(); let res; try { res = await fetch(base + path, { method, headers, body: body ? JSON.stringify(body) : undefined, signal: AbortSignal.timeout(timeoutMs), }); } catch (cause) { // sem resposta: repita com a MESMA Idempotency-Key throw new ThroneError(0, 'network', cause.message); } const json = await res.json().catch(() => ({})); if (!res.ok) { throw new ThroneError( res.status, json.error ?? `http_${res.status}`, json.message ?? '', json.retryable ?? null, Number(res.headers.get('Retry-After')) || null, ); } return json; } return { get: (path, timeoutMs) => request('GET', path, undefined, undefined, timeoutMs), post: (path, body, idempotencyKey, timeoutMs) => request('POST', path, body, idempotencyKey, timeoutMs), }; } const throne = throneClient({ pk: process.env.THRONE_PK, sk: process.env.THRONE_SK }); // Cartão: guarde a chave junto do pedido antes de chamar; 60 segundos de timeout. const chave = randomUUID(); const checkout = await throne.post('/elements/checkout', { amount_cents: 4990, currency: 'usd' }, chave, 60000); ``` ```python Python import uuid import requests class ThroneError(Exception): def __init__(self, status, code, message, retryable=None, retry_after=None): super().__init__(f"{code}: {message}") self.status = status # 0 = sem resposta (conexão caiu ou timeout) self.code = code self.retryable = retryable self.retry_after = retry_after class Throne: def __init__(self, pk, sk, base="https://app.thronecorporate.com/api/v1"): self.base = base self.session = requests.Session() self.session.headers.update({ "Authorization": f"Bearer {pk}:{sk}", "Accept": "application/json", }) def _request(self, method, path, body=None, idempotency_key=None, timeout=20): headers = {} if method == "POST": headers["Idempotency-Key"] = idempotency_key or str(uuid.uuid4()) try: r = self.session.request(method, self.base + path, json=body, headers=headers, timeout=timeout) except requests.RequestException as e: # sem resposta: repita com a MESMA Idempotency-Key raise ThroneError(0, "network", str(e)) try: data = r.json() except ValueError: data = {} if r.status_code >= 400: retry_after = r.headers.get("Retry-After") raise ThroneError( r.status_code, data.get("error", f"http_{r.status_code}"), data.get("message", ""), data.get("retryable"), int(retry_after) if retry_after and retry_after.isdigit() else None, ) return data def get(self, path, timeout=20): return self._request("GET", path, timeout=timeout) def post(self, path, body, idempotency_key=None, timeout=20): return self._request("POST", path, body, idempotency_key, timeout) throne = Throne(pk="pk_live_...", sk="sk_live_...") saldo = throne.get("/balance") # Cartão: guarde a chave junto do pedido antes de chamar; 60 segundos de timeout. chave = str(uuid.uuid4()) checkout = throne.post("/elements/checkout", {"amount_cents": 4990, "currency": "usd"}, chave, timeout=60) ``` ## Repetir com segurança Com os clientes acima, a decisão fica assim: | A exceção traz | O que fazer | |---|---| | `status` 0 (sem resposta) | Repita **com a mesma** `Idempotency-Key` e o mesmo corpo. Se a primeira chegou, você recebe a resposta original, sem duplicar nada | | `502`, `503` ou `504` com código começando em `http_` | Página de erro do servidor, sem o JSON da API. Trate como sem resposta: repita com a mesma chave | | `409 idempotency_key_processing` | Espere alguns segundos e repita com a mesma chave. Se continuar por mais de 2 minutos, confira no painel se algo foi criado antes de usar uma chave nova | | `429`, ou `503` com o código da API | Espere o `retryAfter` e repita com uma chave **nova**, poucas vezes, aumentando a espera ([recuo](/docs/limites#quando-a-resposta-e-429-ou-503)) | | `502` com o código da API e `retryable` verdadeiro ou ausente (o `502` do Pix não traz `retryable`) | Repita com uma chave **nova** | | `502` com `retryable` falso | Não repita igual | | `500` | Algo pode ter sido criado. Não use chave nova antes de conferir no painel. Em `POST /elements/checkout`, repita antes com a mesma chave: se nada foi criado, a chamada roda de novo | | Qualquer outro `4xx` | Corrija o que for preciso e repita com uma chave **nova** | Por isso os clientes aceitam uma `Idempotency-Key` sua: gere a chave antes, guarde junto do pedido e reuse na repetição. A tabela completa está em [como repetir com segurança](/docs/comecar#como-repetir-uma-chamada-com-seguranca). ## Validar webhooks Os exemplos de validação da assinatura `X-Throne-Signature` em PHP, Node.js, Python e Go estão em [Webhooks](/docs/webhooks#validar-a-assinatura). --- # Changelog > O que mudou na API e na documentação, com data. Fonte: https://app.thronecorporate.com/docs/changelog ## Como ler Toda mudança na API é um **acréscimo**: campos novos nas respostas, códigos novos ou páginas novas. Nada que já funciona deixa de funcionar sem aviso aqui. Ignore os campos que você não conhece. O formato do webhook `transaction.paid` (versão `2026-05-01`) não mudou. ## 2026-11-03 **Pix** - **Campo novo `retry_same_key` no `502 acquirer_error` de `POST /transactions`, só no Pix.** Ele vem com `true` quando a emissão não se confirmou e a cobrança pode existir na adquirente. Nesse caso, repita a mesma chamada, com a mesma `Idempotency-Key` e o mesmo corpo, e nunca com uma chave nova: ela pode gerar um segundo código pagável para o mesmo pedido. Sem o campo, nada foi criado e a chave nova continua sendo o certo. A exceção é o `502` sem o campo que chega na repetição de um `502` com o campo: ele não descarta a cobrança da primeira tentativa, então fale antes com o suporte. - Com `"retry_same_key": true`, a chave fica livre só para a repetição do mesmo corpo. Com outro corpo (outro valor, outro comprador), a resposta é `422 idempotency_key_mismatch` e a chamada não chega à adquirente. - Depois de um `502 acquirer_error` do Pix, a mesma chave não fica mais presa em `409 idempotency_key_processing` por 24 horas: a repetição da mesma chamada roda de novo. - **No cartão nada muda.** O `retry_same_key` não vem em nenhuma resposta de cartão, e o checkout (`POST /elements/checkout`) segue a regra de repetição de sempre. **Pix com a Shadow Pix** - Página nova: [Pix com a Shadow Pix](/docs/pix/shadowpix), para a conta atendida por essa adquirente. A integração é a mesma do [guia de Pix](/docs/pix): mesmos endpoints, mesmo corpo, mesmo webhook. O que muda: cobrança só de R$ 10,00 a R$ 5.000,00, timeout de 60 segundos na criação, código pagável no banco por cerca de 2 dias e saque com mínimo de R$ 10,00 na adquirente. - O `502` sem `retry_same_key` também sai quando a adquirente mostra que a chave já está ligada a uma cobrança de outro pedido. Nenhuma cobrança do seu pedido existe lá, e a mesma chave devolve sempre este erro: use uma chave nova. Veja [o que fazer num `502`](/docs/pix/shadowpix#o-que-fazer-num-502). - Na repetição de um `502` com `retry_same_key`, se o comprador já tinha pago aquele código, a resposta é `201` com `status: "pending"` e `payment_data.qr_code_payload` vazio. A venda foi registrada: não mostre QR Code e libere o pedido pelo `transaction.paid` ou por um `GET` com `status: "paid"`. **Documentação** - [Erros](/docs/errors), [Comece aqui](/docs/comecar#como-repetir-uma-chamada-com-seguranca) e [Cobrar com Pix](/docs/pix) separam o `502` com e sem `retry_same_key`. A referência da API (OpenAPI e coleção do Postman) ganha o campo, os dois exemplos da resposta `502` de `POST /transactions` e o `qr_code_payload` vazio da cobrança já paga. - Versão da referência (`info.version` do OpenAPI): 1.13.0. ## 2026-10-27 **Cartão, recorrência na Vortex** - **Integração direta na ShadowBank**: modalidade nova, sem o Throne Elements. O seu servidor tokeniza o cartão em `POST /api/v1/vault/shadowbank/cards` e cobra em `POST /api/v1/shadowbank/payments`, na moeda do comprador. Aceita **cobrança recorrente** e **upsell em um clique** (`POST /api/v1/shadowbank/upsell`), e o upsell também pode ser recorrente. Sempre à vista. Liberada caso a caso, à parte das integrações diretas da Eminence e da Vortex. O checkout iframe da ShadowBank não muda em nada, e o webhook continua o mesmo. Guia: [Server-to-server na ShadowBank](/docs/cartao/shadowbank-direto). - **A Vortex passa a fazer cobrança recorrente.** No `POST /elements/checkout` com `"acquirer": "vortex"`, mande `recurrence` com `cycle` (um dos sete, de `weekly` a `yearly`) e `max_payments` opcional: a resposta `201` traz `recurrence_id`, `cycle` e `next_charge_at`, e as cobranças seguintes saem sozinhas, no mesmo cartão. O `422 recurrence_not_supported` não vale mais para a Vortex. - Cada cobrança de ciclo é uma venda, com `transaction_id` próprio e o `transaction.paid` no formato de sempre. O webhook não ganha campo novo. Cobrança de ciclo recusada não cria venda: a recorrência fica `past_due`. - **A assinatura só começa a valer quando a primeira cobrança aprova.** Na Vortex a aprovação nem sempre é imediata: enquanto a venda está em análise, a recorrência fica em `pending` e nenhuma cobrança seguinte sai. Se a análise reprovar, nada é assinado. - Consulta e cancelamento continuam em `GET /recurrences`, `GET /recurrences/{id}` e `POST /recurrences/{id}/cancel`, com as permissões que já existem. - A moeda segue a regra de sempre: você cobra na moeda do seu comprador e a cotação é fixada na criação. Os ciclos seguintes cobram o mesmo valor fixado. - **A integração direta por API também aceita `recurrence`** (`POST /api/v1/vortex/payments`), com `recurrence_id`, `cycle` e `next_charge_at` na resposta. Ciclo fora dos sete: `422 invalid_cycle`. O `422 recurrence_not_supported` continua existindo nesse endpoint, mas agora só quando a cobrança recorrente não está disponível na adquirente. **Documentação** - [Cartão com a Vortex](/docs/cartao/vortex) e [Server-to-server na Vortex](/docs/cartao/vortex-direto) ganham a seção Recorrência. [Recorrência](/docs/cartao/recorrencias), [Comparar as adquirentes](/docs/cartao/comparar), [Cartão](/docs/cartao) e [Erros](/docs/errors) acompanham. - Versão da referência (`info.version` do OpenAPI): 1.12.0. **Cartão** - **Vortex: adquirente nova.** Mande `"acquirer": "vortex"` na criação do checkout para usá-la, ou peça ao suporte para deixá-la como padrão da sua conta. A integração é a mesma de sempre: `POST /elements/checkout` na moeda do comprador, Throne Elements na página, webhook `transaction.paid`. Campos, respostas, moedas e formato do webhook não mudam. - Na Vortex, `customer.name` (até 120 caracteres) e `customer.email` (e-mail válido, até 180) são **obrigatórios** na criação. Sem eles, `422 customer_required_for_vortex` e nada é criado. O elemento não pergunta nome nem e-mail ao comprador: colete os dois na sua página antes de chamar a API. - A aprovação na Vortex nem sempre é imediata. O banco autoriza na hora, mas a venda ainda passa por uma análise de risco antes de virar paga. Nesse intervalo ela fica `pending` e o `transaction.paid` chega quando a análise libera. Não trate `pending` como falha. - A Vortex é à vista e não atende o [Checkout Brasil](/docs/cartao/brasil). O upsell em um clique dela chegou depois, na integração direta: veja a entrada de 2026-10-29. **Documentação** - Página nova: [Cartão com a Vortex](/docs/cartao/vortex). - `vortex` entra nos campos `acquirer` de `POST /elements/checkout`, na resposta da consulta e em `GET /elements/acquirers`. - Versão da referência (`info.version` do OpenAPI): 1.10.0. **Cartão, integração direta na Vortex** - **A integração direta por API agora existe também na Vortex.** Duas chamadas: `POST /api/v1/vault/vortex/cards` tokeniza o cartão no seu servidor e `POST /api/v1/vortex/payments` cobra com esse token, sem o Throne Elements. Guia novo: [Server-to-server na Vortex](/docs/cartao/vortex-direto). - Ao contrário da integração direta da Eminence, aqui você cobra **na moeda do seu comprador**, as mesmas 48 do checkout, e `currency` é obrigatório. - `customer.name` e `customer.email` são obrigatórios também nesta modalidade. Sem eles, `422 customer_required_for_vortex`. - Códigos novos no passo 2: `currency_not_supported`, `fx_unavailable` (com `Retry-After`) e `amount_out_of_range`, que traz `min_amount_cents`, `max_amount_cents` e `currency` sempre na moeda que você enviou. - A liberação é **por adquirente**: quem já usa a integração direta da Eminence precisa pedir a da Vortex à parte. Com a Vortex habilitada na conta e sem essa liberação, continua valendo o checkout comum. - Versão da referência (`info.version` do OpenAPI): 1.11.0. ## 2026-10-26 **Cartão** - **Integração direta (server-to-server, só Eminence) corrigida.** Até hoje as duas chamadas não completavam uma cobrança: nenhuma venda chegou a ser processada por esta modalidade. Quem testou antes deve seguir o contrato abaixo, que é o definitivo. - `POST /api/v1/vault/eminence/cards` passa a exigir `Authorization: Bearer` com a permissão `transactions:write` e deve ser chamado do seu servidor. O corpo continua `number`, `exp_month`, `exp_year`, `cvv` e `holder_name` (agora obrigatório). A resposta é `card_token`, `brand`, `last4`, `exp_month`, `exp_year` e `expires_at` (token de uso único, até 7 dias). Erros: `invalid_card` (com `field`), `card_not_supported`, `scope_required`, `partner_api_not_enabled`, `acquirer_not_enabled`, `rate_limit_exceeded` e `temporarily_unavailable`. - `POST /api/v1/eminence/payments` responde `201` sempre que a venda é criada, com `status` `paid`, `pending` ou `failed`, além de `decline_code`, `retryable` e `card`. Recusa não é mais `422 declined`: vem `201` com `status: "failed"` e o motivo em `acquirer_message`. `customer.name` passa a ser obrigatório, o mínimo passa a R$ 5,00 (`422 amount_out_of_range`), só à vista, e chaves internas em `metadata` são recusadas (`422 metadata_reserved_key`). Erros antes da criação da venda não prendem mais a `Idempotency-Key`. - Estorno da integração direta é pelo suporte, como no cartão comum. A descrição da página antiga dizia "pelo painel"; o correto é pelo suporte. **Documentação** - [Integração direta (parceiros)](/docs/cartao/server-to-server) reescrita com o contrato acima, a tabela de como ler a resposta, os erros reais, os limites e os tempos. A referência da API (OpenAPI e coleção do Postman) acompanha. - Versão da referência (`info.version` do OpenAPI): 1.9.0. ## 2026-10-22 **Cartão** - **ShadowBank**: nome e e-mail do comprador passam a ser obrigatórios na criação do checkout (`customer.name` e `customer.email`, com e-mail válido). Sem eles, `422 customer_required_for_shadowbank` e nada é criado. O elemento continua sem perguntar nome nem e-mail: colete os dois na sua página antes de chamar a API. Nas outras adquirentes, `customer` segue opcional. Guia: [Cartão com a ShadowBank](/docs/cartao/shadowbank). - A adquirente passa a se chamar ShadowBank. O valor `shadowbank` do campo `acquirer`, os endpoints e o webhook não mudam; só o nome exibido (`name` em `GET /elements/acquirers`) passa a vir `ShadowBank`. **Documentação** - [Cartão com a ShadowBank](/docs/cartao/shadowbank) ganha a regra de nome e e-mail, a linha do erro novo e o item no checklist. [Erros](/docs/errors) ganha `customer_required_for_shadowbank`. A referência da API (OpenAPI e coleção do Postman) ganha o código na lista de erros do `POST /elements/checkout` e a regra na descrição do campo `customer`. - Versão da referência (`info.version` do OpenAPI): 1.8.1. ## 2026-10-21 **Cartão** - **Server-to-server, só Eminence**: alternativa ao Throne Elements, para gateway, subadquirente ou agregador que já tem o próprio checkout de cartão. Dois endpoints novos, embaixo da mesma URL base da API: `POST /vault/eminence/cards`, sem `Authorization`, tokeniza o cartão em TLS e devolve um `card_token` de uso único (5 minutos); `POST /eminence/payments`, com `Authorization: Bearer`, cobra na Eminence com o `card_token` e devolve `authorized` em tempo real. Consulta pelo `GET /transactions/{id}` que já existe; estorno pelo painel, como no cartão comum. Habilitação caso a caso: fale com o suporte. Nas outras cinco adquirentes a integração continua sendo o Throne Elements. Guia: [Server-to-server (parceiros)](/docs/cartao/server-to-server). - Erros novos: `partner_api_not_enabled`, `invalid_card_token`, `expired_card_token`, `invalid_card` (vault), `vault_unavailable`. `acquirer_not_enabled`, `invalid_amount`, `declined` e `rate_limited` seguem o mesmo contrato do cartão comum. - **ShadowBank**: adquirente nova, com o valor `shadowbank` no campo `acquirer` do `POST /elements/checkout`. Mesmo endpoint, mesmo Throne Elements, mesmo webhook `transaction.paid`. Sem verificação do banco, tudo dentro do elemento, 5 tentativas, à vista, nas 48 moedas. Não faz recorrência nem upsell em um clique e não atende o Checkout Brasil. Habilitada pelo suporte, conta a conta. Guia: [Cartão com a ShadowBank](/docs/cartao/shadowbank). - `shadowbank` entra nos valores aceitos de `acquirer`, na lista do `422 invalid_acquirer` e na resposta de `GET /elements/acquirers` das contas em que estiver habilitada. Integrações que não mandam `shadowbank` não mudam. **Documentação** - Página nova [Server-to-server (parceiros)](/docs/cartao/server-to-server), com os dois endpoints, exemplos cURL, tabela de erros e checklist. [Cartão: como funciona](/docs/cartao) e [Cartão com a Eminence](/docs/cartao/eminence) ganham um aviso curto apontando para ela. A referência da API (OpenAPI e coleção do Postman) ganha os paths `POST /vault/eminence/cards` e `POST /eminence/payments`, com request e response schemas e a tabela de erros. - Página nova [Cartão com a ShadowBank](/docs/cartao/shadowbank) e coluna nova em [Comparar adquirentes](/docs/cartao/comparar). A referência da API (OpenAPI e coleção do Postman) ganha `shadowbank` no campo `acquirer`. - Versão da referência (`info.version` do OpenAPI): 1.8.0. ## 2026-10-20 **Cartão** - **Checkout Brasil, aparência pela API**: campo opcional `appearance` no `POST /elements/checkout/br`, com o texto do botão, o valor no botão, as cores, o arredondamento, a altura e a largura do botão e uma moldura em volta dos campos. Fora da regra, `422 invalid_appearance`, com o caminho da chave em `field`. Aviso novo `low_contrast` em `warnings`. Sem o campo, nada muda. Se você já mandava um campo `appearance` no Checkout Brasil, ele era ignorado e passa a valer. Veja [Aparência](/docs/cartao/brasil#aparencia). - O `POST /elements/checkout` não muda: `appearance` continua ignorado nele. **Documentação** - A referência da API (OpenAPI e coleção do Postman) passa a trazer o que já funcionava desde 2026-10-18: os endpoints `GET /recurrences`, `GET /recurrences/{id}` e `POST /recurrences/{id}/cancel`, os campos `one_click_from` e `recurrence` no corpo do `POST /elements/checkout`, os campos `one_click`, `one_click_card`, `recurrence_id`, `cycle` e `next_charge_at` na resposta `201` e os códigos `invalid_cycle`, `recurrence_not_supported` e `cancel_failed`. Nada mudou na API por isso. - O timeout de 60 segundos vale também para o `POST /elements/checkout/br`, como o guia do Checkout Brasil já dizia. [Limites e tempos](/docs/limites), [Comece aqui](/docs/comecar) e o [Checklist](/docs/checklist) passam a dizer o mesmo. - Versão da referência (`info.version` do OpenAPI): 1.6.0. ## 2026-10-19 **Cartão** - **Checkout Brasil**: endpoint novo `POST /elements/checkout/br`, para o comprador brasileiro. Mesmo corpo do `POST /elements/checkout` (`amount_cents`, `currency` e `customer` opcional), mesma resposta `201`, mesmo Throne Elements, mesmos eventos e mesmo webhook `transaction.paid`. A página sai sempre em português, pede o CPF do comprador junto com o cartão e abre para comprador de qualquer país. Funciona nas adquirentes habilitadas para o Checkout Brasil na conta; peça ao suporte para habilitar. Os campos `one_click_from` e `recurrence` não existem neste endpoint: se vierem, `422 field_not_supported`. Sem adquirente habilitada para o Checkout Brasil, ou com `acquirer` pedindo uma que não atende, `422 acquirer_not_enabled`. Página nova: [Checkout Brasil](/docs/cartao/brasil). - O `POST /elements/checkout` não muda: as integrações que já estão no ar continuam com a mesma requisição, a mesma resposta e o mesmo webhook. **Documentação** - Página nova na seção Cartão: [Checkout Brasil](/docs/cartao/brasil). A referência da API ganha o caminho `POST /elements/checkout/br`, e [Erros](/docs/errors) ganha o código `field_not_supported`. - Versão da referência (`info.version` do OpenAPI): 1.5.0. ## 2026-10-18 **Cartão** - **Recorrência na Apex**: campo opcional `recurrence` no `POST /elements/checkout`, com os ciclos `weekly`, `biweekly`, `monthly`, `bimonthly`, `quarterly`, `semiannually` e `yearly`, e `max_payments` opcional de 2 a 120. A resposta `201` ganha `recurrence_id`, `cycle` e `next_charge_at`. Cada cobrança de ciclo é uma venda, com `transaction_id` próprio e o mesmo webhook `transaction.paid`. Consulta e cancelamento em `GET /recurrences`, `GET /recurrences/{id}` e `POST /recurrences/{id}/cancel`, com as permissões que já existem (`transactions:read` e `transactions:write`): nenhuma chave de API precisa ser gerada de novo. Códigos novos: `invalid_cycle` e `recurrence_not_supported` na criação, `invalid_status` e `cancel_failed` nos endpoints de recorrência. Página nova: [Recorrência](/docs/cartao/recorrencias). - **Upsell em um clique**: campo opcional `one_click_from` no `POST /elements/checkout`, e os campos `one_click` e `one_click_card` na resposta `201`. Nenhum código de erro novo: qualquer recusa vira `one_click: false` e o comprador digita o cartão. Habilitação por conta, pelo suporte. Página nova: [Upsell em um clique](/docs/cartao/oneclick). - Sem os campos novos, nada muda: as integrações que já estão no ar continuam com a mesma requisição, a mesma resposta e o mesmo webhook. **Documentação** - Duas páginas novas na seção Cartão: [Upsell em um clique](/docs/cartao/oneclick) e [Recorrência](/docs/cartao/recorrencias). As páginas de cartão, comparação, Apex, erros, webhooks e o mapa da documentação passam a citar as duas. ## 2026-10-16 **Cartão** - Nova adquirente de cartão **Apex** (`acquirer: "apex"`). Mesmo endpoint, mesmo Throne Elements, mesmas moedas e mesmo webhook das outras. Sem verificação do banco: o comprador paga dentro do elemento, em qualquer aparelho e também em navegador de app, e os eventos chegam à sua página em todos os caminhos. Guia em [Cartão com a Apex](/docs/cartao/apex); comparação em [Comparar adquirentes](/docs/cartao/comparar). - `GET /elements/acquirers`, o campo `acquirer` do `POST /elements/checkout` e o `acquirer` do `GET /transactions/{id}` passam a aceitar e devolver `apex` nas contas em que ela estiver habilitada. Peça ao suporte para habilitar. - Throne Elements: na Apex, `throne-elements:error` traz `code` com `declined`, `attempts_exhausted`, `token`, `network`, `timeout`, `limit`, `expired` e `ended`. `timeout` só acontece quando a análise antifraude demora. A tabela está em [Throne Elements](/docs/cartao/elements#codigos-de-erro). - Guias de cartão, erros, status, moeda, webhooks, testes e checklist passam a incluir a Apex. **Documentação** - Versão da referência (`info.version` do OpenAPI): 1.4.0. ## 2026-10-12 **Pix** - Novo código `422 pix_not_enabled` em `POST /transactions` (Pix) e `POST /payment-links`: o recebimento por Pix não está habilitado na conta. Nada é criado; cartão e saques por Pix continuam. A conta sem adquirente Pix, que antes respondia `500`, responde este mesmo código. Um link de pagamento já criado mostra "Link indisponível" enquanto o recebimento estiver desabilitado. Linhas novas em [Erros](/docs/errors) e em [Cobrar com Pix](/docs/pix). **Cartão** - **Eminence** disponível para qualquer conta: peça ao suporte para habilitar. O guia [Cartão com a Eminence](/docs/cartao/eminence) foi refeito por inteiro: exemplo de criação com `"acquirer": "eminence"`, os três caminhos de pagamento (computador, celular e navegador de app, todos dentro do elemento), a verificação do banco dentro do elemento, o pagamento em análise, as tabelas de erros na criação e dos códigos em `throne-elements:error` (com `timeout` e `limit`), um exemplo de tratamento dos eventos na página, quando chega o webhook e o checklist. - `POST /elements/checkout`: a referência ganha um exemplo de corpo com `"acquirer": "eminence"`, e `GET /elements/acquirers` traz a Eminence no exemplo de resposta. **Documentação** - A página de moedas do cartão passa a se chamar [Moedas e limites](/docs/cartao/moeda) (mesmo endereço): as 48 moedas, o mínimo e o máximo por moeda e como a cotação do dia muda os limites. - Textos sugeridos para o aviso ao lado do botão de pagar (NovaCorp e Eminence) refeitos, agora também em espanhol. - Versão da referência (`info.version` do OpenAPI): 1.3.0. ## 2026-10-09 **Cartão** - Nova adquirente de cartão **Eminence** (`acquirer: "eminence"`). Mesmo endpoint, mesmo Throne Elements, mesmas moedas e mesmo webhook das outras. A verificação do banco, quando o banco pede, aparece dentro do próprio elemento. Guia em [Cartão com a Eminence](/docs/cartao/eminence); comparação em [Comparar adquirentes](/docs/cartao/comparar). - `GET /elements/acquirers`, o campo `acquirer` do `POST /elements/checkout` e o `acquirer` do `GET /transactions/{id}` passam a aceitar e devolver `eminence` nas contas em que ela estiver habilitada. - Throne Elements: na Eminence, `throne-elements:error` traz `code` com `declined`, `attempts_exhausted`, `token`, `network`, `timeout`, `expired`, `ended` e o código novo `limit` (a Eminence não aceita o valor da venda no cartão e o checkout fecha como `failed`). A tabela está em [Throne Elements](/docs/cartao/elements#codigos-de-erro). - Guias de cartão, erros, status, moeda, webhooks, testes e checklist passam a incluir a Eminence. ## 2026-10-06 **Cartão** - `POST /elements/checkout`: acima do máximo da adquirente, a resposta passa a ser `422 invalid_amount` com `max_amount_cents`, `currency` e `alternatives`. Antes, esse caso podia voltar `502 checkout_failed`. - `POST /elements/checkout`: abaixo do mínimo, `422 invalid_amount` ganha `min_amount_cents`, `currency` e `alternatives`. Uma adquirente pode ter mínimo próprio, maior que o geral. - `POST /elements/checkout`: adquirente em pausa, instável ou sem capacidade responde `503 temporarily_unavailable`, com `Retry-After` e `"retryable": true`. Nada é criado. - `POST /elements/checkout`: `502 checkout_failed` ganha o campo `retryable`. Com `false`, não repita. - `POST /elements/checkout`: a resposta `201` ganha `expires_at` (fim da validade do checkout, em UTC). - `POST /elements/checkout`: os campos de `customer` deixam de gerar `422`. Um campo fora do formato é ajustado ou descartado, e a resposta `201` traz `warnings` com `field`, `code` e `message`. O telefone aceita `+`, parênteses, ponto e barra; o país em 3 letras é convertido para 2; um documento só com números, sem 11 nem 14 dígitos, vale como passaporte; um e-mail inválido é descartado. O código `invalid_document` deixa de acontecer. - `GET /elements/acquirers`: cada adquirente ganha `available`. Com `?currency=`, ganha também `limits`, com o mínimo e o máximo por venda na moeda pedida. - `GET /transactions/{id}`: ganha `acquirer`, com a adquirente da venda (nulo no Pix). - Roteamento por valor, opcional por conta: sem o campo `acquirer`, a venda que passa do máximo da adquirente padrão vai para outra habilitada que aceita o valor. Vem desligado; fale com o suporte para ligar. - Throne Elements: `throne-elements:error` ganha o campo `code`, fixo e em inglês, na Sterling e na NovaCorp. - NovaCorp: as 6 tentativas passam a ser contadas por checkout, no servidor. Na 6ª recusa o checkout fecha como `failed`. Com tentativas sobrando, ele não fecha enquanto o comprador está na página. **Documentação** - Páginas novas: [Limites e tempos](/docs/limites), [Como testar](/docs/testar), [Checklist de verificação](/docs/checklist), [Comparar adquirentes](/docs/cartao/comparar), [Escolher a adquirente](/docs/cartao/escolher), [Moedas e limites](/docs/cartao/moeda), [Throne Elements](/docs/cartao/elements), [Status e ciclo de vida](/docs/cartao/status) e este Changelog. - Regra única de repetição com `Idempotency-Key`, em [Comece aqui](/docs/comecar#como-repetir-uma-chamada-com-seguranca). - Timeout recomendado de 60 segundos na criação de checkout de cartão, e `Accept: application/json` em todos os exemplos. - NovaCorp: os três caminhos de pagamento (computador, celular e navegador de app), com o aviso de que o navegador de app não emite evento na página. - Webhooks: quando o webhook chega e como enviar a conversão de campanha pelo servidor. - Referência da API: só leitura, sem envio de requisição pelo navegador. - `llms.txt` com a data da última atualização e a versão da API. ## 2026-10-03 - `POST /elements/checkout`: campo opcional `acquirer` (`sterling`, `imperium` ou `novacorp`) para escolher a adquirente de cada venda. Códigos `422 invalid_acquirer` e `422 acquirer_not_enabled`. - `GET /elements/acquirers`: lista as adquirentes de cartão habilitadas na conta, com a padrão marcada. - A resposta `201` de `POST /elements/checkout` ganha `acquirer`. ## 2026-10-02 - Cartão: nova adquirente NovaCorp. ## 2026-09-25 - Cartão: nova adquirente Imperium. ## 2026-09-17 - Cartão: o valor passa a ser enviado na moeda do comprador, que paga nessa moeda; a cotação que credita o seu saldo é fixada na criação e volta como `fx_rate`. Códigos `422 currency_not_supported` e `422 fx_unavailable`. ## 2026-09-16 - `POST /elements/checkout`: campos opcionais do titular em `customer` (`document`, `document_type`, `phone` e `address`). Código `422 invalid_document`. ## 2026-08-18 - Chamadas com chave válida deixam de ter limite geral de requisições. O limite de 10 por segundo continua para chamadas sem credencial válida. --- # Status > Verificação ao vivo da API. Fonte: https://app.thronecorporate.com/docs/status ## Verificação agora

Verificando a API...

Esta verificação consulta `GET /health` no momento em que você abre a página. ## Verificar do seu servidor O endpoint de saúde é público, não precisa de chave: ```bash curl https://app.thronecorporate.com/api/v1/health \ -H "Accept: application/json" ``` ```json { "status": "ok", "env": "production", "time": "2026-10-03T14:50:00Z" } ``` Se a resposta não for `200` com `"status": "ok"`, ou se as suas chamadas começarem a receber `5xx` em sequência, fale com o suporte do Throne pelo painel. ## Boas práticas para instabilidade - Repita chamadas que ficaram sem resposta (falha de conexão ou timeout) com a **mesma** `Idempotency-Key` e o mesmo corpo (não duplica nada). - Depois de um `4xx`, ou de um `502` ou `503` com o JSON da API, nada foi criado: repita com uma `Idempotency-Key` **nova**. Depois de um `500`, confira no painel se algo foi criado antes de usar uma chave nova. A regra completa está em [como repetir com segurança](/docs/comecar#como-repetir-uma-chamada-com-seguranca). - Em `429` e `503`, espere o `Retry-After` e aumente a espera a cada nova tentativa ([Limites e tempos](/docs/limites#quando-a-resposta-e-429-ou-503)). - No cartão, uma adquirente pode ficar indisponível sem afetar as outras. Com mais de uma habilitada, crie na outra com o campo `acquirer`. - Não dependa só do webhook: para vendas críticas, confirme com `GET /transactions/{id}`.