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, 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). 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 |
| 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 |
| 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 |
| Eminence | eminence |
Pode pedir; a tela do banco aparece dentro do próprio elemento | Tudo dentro do elemento | 5 | Eminence |
| Apex | apex |
Não tem: aprova ou recusa na hora | Tudo dentro do elemento | 5 | Apex |
| ShadowBank | shadowbank |
Não tem: aprova ou recusa na hora | Tudo dentro do elemento | 5 | ShadowBank |
| Vortex | vortex |
Não tem: aprova ou recusa na hora | Tudo dentro do elemento | 5 | 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.
Escolher a adquirente#
Com mais de uma adquirente habilitada, cada site (source) escolhe a dela com o campo acquirer no POST /elements/checkout:
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 ligado, outra habilitada que aceite o valor).
- Os valores são
sterling,imperium,novacorp,eminence,apex,shadowbankevortex. Só vale uma adquirente habilitada na sua conta; outra volta422 acquirer_not_enablede nada é criado. - Com o campo, o Throne nunca troca a adquirente que você pediu por outra.
- A resposta traz
acquirercom 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.
Passo a passo#
- O seu servidor cria o checkout com
POST /elements/checkout, na moeda do comprador, quando ele clica em comprar. Guarde otransaction_id. - A sua página coloca o container do Throne Elements com esse
transaction_ide carrega o script do Throne. - O comprador paga dentro do elemento.
- 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.
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" }
}'$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]);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 === 201import 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 == 201Campos#
| 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 |
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 |
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) 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:
{
"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.displayrepete 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. Comodisplay, pode faltar em contas com integração antiga.warningsaparece só quando há aviso. No checkout comum, o aviso é sobre um campo decustomerque foi ajustado ou descartado: cada aviso trazfield,code(converted,inferredoudiscarded) emessage. No Checkout Brasil,codetambém pode serlow_contrast, sobreappearance. Serve para log: a venda foi criada.
"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#
<!-- onde o checkout deve aparecer -->
<div data-throne-elements
data-id="ae3bf1da-0c9b-4818-9140-c6432152c23e"
data-color="#7c3aed"
data-theme="light"
data-radius="10"></div>
<script src="https://app.thronecorporate.com/throne-elements.js" async></script>| 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ínimoallow-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 odata-idde um container já montado não troca o checkout (detalhes). - Se o seu site usa Content-Security-Policy, libere
https://app.thronecorporate.comemscript-srce emimg-src, e emframe-srclibere esse domínio e o domínio docheckout_url.
A referência completa do elemento, com navegador de app e página de uma tela só, está em Throne Elements.
Eventos na sua página#
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.
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.
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_amountcommin_amount_centsna 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_amountcommax_amount_centsna 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.
3. Confirmar a venda#
Webhook transaction.paid#
{
"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": {}
}
}currencyenative_amount_centssã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"); noGETchega como número. Trate como decimal antes de calcular. Em venda criada em BRL,fx_rateénull.card_brandvem 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.
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.
Consulta#
GET /transactions/{id} com o transaction_id:
- Antes do pagamento:
status: "pending",amount_cents,fee_centsenet_amount_centsiguais a 0,fx_rateepaid_atnulos.currencyenative_amount_centsjá vêm com o que você mandou. - Depois do pagamento:
status: "paid",amount_cents,fee_centsenet_amount_centspreenchidos (o seu saldo, em reais) efx_ratecomo número. acquirertraz 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 |
expired |
O checkout passou da validade (24 horas) sem pagamento |
refunded |
Estornada |
chargeback |
Contestada pelo comprador |
failedeexpirednão são definitivos: se o pagamento for confirmado depois, a venda passa apaide o webhook é enviado. Libere o pedido sempre pelo webhook ou pela consulta, nunca pela falta de erro.- Para vender de novo depois de
failedouexpired, 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:
one_click_fromcobra um segundo produto sem pedir o cartão de novo, depois de uma venda aprovada. - Recorrência:
recurrencecria 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, e o roteiro de conferência em Checklist de verificação.
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 |
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:
{ "error": "checkout_failed", "message": "Não foi possível criar o checkout.", "retryable": false }