Checkout de e-commerce ponta a ponta
Tutorial ponta a ponta para integrar o checkout de uma loja virtual com a API Selectwin: você identifica ou cadastra o cliente, opcionalmente tokeniza o cartão, cria a transação (cartão, Pix ou boleto
Tutorial ponta a ponta para integrar o checkout de uma loja virtual com a API Selectwin: você identifica ou cadastra o cliente, opcionalmente tokeniza o cartão, cria a transação (cartão, Pix ou boleto), confirma o pagamento de forma assíncrona via webhook e exibe o histórico no painel do cliente. Use esta página como o roteiro de referência que amarra os recursos customers, cards, transactions, coupons e webhooks/events.
Todos os exemplos usam a base https://api.selectwin.io/v1, autenticam pelo header SelectKey (em sandbox a chave começa com sl_test_; em produção, com sl_live_) e expressam dinheiro em centavos (9900 = R$ 99,00) e datas em ISO 8601 UTC (2026-04-12T17:56:33.000Z). Para o detalhe de cada convenção, veja Convenções.
Visão geral do fluxo
O diagrama abaixo resume a sequência típica de chamadas — do cadastro do cliente à confirmação assíncrona do pagamento.
Importante
A confirmação de pagamento é assíncrona. Trate o webhook (ou o callback.webhookUrl da transação) como a fonte da verdade do status. Não fique consultando GET /v1/transactions/{id} em laço esperando aprovar — veja Proibição de Polling. O GET serve para reconciliação pontual e para montar telas, não como gatilho.
Glossário rápido: tokenizar é guardar o cartão na plataforma e receber um card_... opaco no lugar do número (PAN), para cobrar depois sem manusear dados sensíveis; idempotência é a garantia de que repetir a mesma requisição de escrita não cria duas cobranças; webhook é a notificação HTTP que a Selectwin envia ao seu servidor quando o status muda.
Passo 1 — Identificar ou cadastrar o cliente
Antes de cobrar, garanta que existe um cliente. Procure por e-mail ou documento; se não houver, crie.
# 1. Procurar por e-mail (lista, projeção leve)
curl -s "https://api.selectwin.io/v1/[email protected]" \
-H "SelectKey: sl_live_aBcDeFgHiJkLmNoPqRsTuVwXyZ"Se a lista vier vazia (total: 0), crie o cliente. Note os nomes exatos: o telefone é telephone (não phone) e o documento é o objeto document com type ∈ cpf | cnpj | passport.
curl -s -X POST "https://api.selectwin.io/v1/customers" \
-H "SelectKey: sl_live_aBcDeFgHiJkLmNoPqRsTuVwXyZ" \
-H "Content-Type: application/json" \
-H "X-Idempotency-Key: chk-cus-9f2c1a" \
-d '{
"firstName": "João",
"lastName": "Silva",
"email": "[email protected]",
"document": { "type": "cpf", "number": "12345678901" },
"telephone": { "countryCode": "55", "areaCode": "11", "number": "999999999" },
"externalReference": "CRM-USER-001"
}'A resposta 201 traz o id (cus_...) que você usará nos próximos passos. Guarde-o no seu pedido.
| Campo | Tipo | Obrigatório | Observação |
|---|---|---|---|
firstName | string | sim | Nome do cliente |
lastName | string | sim | Sobrenome |
email | string (email) | sim | E-mail principal |
document.type | string | não | cpf, cnpj ou passport |
document.number | string | não | Só dígitos quando cpf/cnpj |
telephone.countryCode / areaCode / number | string | não | Só dígitos em cada parte |
externalReference | string | não | Seu ID de referência (correlação) |
Dica
Você nem sempre precisa criar o cliente em uma chamada separada. O POST /v1/transactions aceita um objeto customer aninhado: passe customer.id para reaproveitar um cliente existente, ou os dados completos (firstName, email, document…) para criá-lo junto com a transação. Criar antes é útil quando você quer um perfil estável de CRM; criar inline é mais enxuto para guest checkout.
Sobre o endereço
Não há um endpoint público de endereços nestes guias. O endereço entra de duas formas: como parte do cliente (campo addresses[] retornado em cus_...) ou inline na transação, em billing.address (cobrança) e shipping.address (entrega). Se já tiver um endereço salvo, referencie-o por id (addr_...); senão, envie os campos completos. Para detalhes de entrega (transportadora, rastreio, dimensões), veja Shipping.
Passo 2 — Tokenizar o cartão (opcional)
Só faça este passo se for salvar o cartão para recompras (one-click). Para uma compra avulsa, você pode enviar os dados do cartão direto em payment.card na transação — sem criar um card_... antes.
Atenção — PCI
Nunca trafegue ou armazene PAN/CVV no seu próprio servidor sem o devido escopo PCI. Prefira coletar os dados do cartão pelo fluxo seguro da plataforma e enviar à API por canal seguro. Veja Segurança e PCI. Os números de teste estão em Cartões para teste.
curl -s -X POST "https://api.selectwin.io/v1/cards" \
-H "SelectKey: sl_live_aBcDeFgHiJkLmNoPqRsTuVwXyZ" \
-H "Content-Type: application/json" \
-H "X-Idempotency-Key: chk-card-3b8e7d" \
-d '{
"customerId": "cus_01hqzvabc",
"holderName": "JOAO SILVA",
"numbering": "4111111111111111",
"expirationMonth": 12,
"expirationYear": 2028,
"securityCode": "123",
"primary": true
}'Atenção aos nomes corretos dos campos (eles diferem do que muita gente espera):
| Campo | Tipo | Obrigatório | Observação |
|---|---|---|---|
customerId | string | sim | Dono do cartão (cus_...) |
holderName | string | sim | Nome impresso no cartão |
numbering | string | sim | Número do cartão (validado por Luhn) |
expirationMonth | number (1–12) | sim | Mês de expiração |
expirationYear | integer | sim | Ano (2 ou 4 dígitos) |
securityCode | string (\d{3,4}) | sim | CVV/CVC |
primary | boolean | não | Define como cartão principal do cliente |
A resposta 201 devolve o cartão tokenizado: apenas firstDigits, lastDigits, brand e o id (card_...) — o PAN completo nunca volta. Use esse id na transação.
Passo 3 — Aplicar cupom de desconto (opcional)
Se sua campanha usa cupom, crie/consulte um cupom e aplique o desconto na transação. O tipo é flat (valor fixo em centavos) ou percentage (porcentagem) — atenção: o enum é percentage, não percent.
# Consultar cupom pelo código
curl -s "https://api.selectwin.io/v1/coupons?code=BLACKFRIDAY25" \
-H "SelectKey: sl_live_aBcDeFgHiJkLmNoPqRsTuVwXyZ"Exemplo de cupom (resposta de GET/POST /v1/coupons):
{
"id": "dis_01hqzvabc",
"name": "Black Friday 25%",
"code": "BLACKFRIDAY25",
"type": "percentage",
"value": 25,
"enabled": true,
"minCartAmount": 10000,
"initDate": "2026-04-01T00:00:00.000Z",
"endDate": "2026-04-30T23:59:59.000Z"
}Na transação, você passa o desconto pelo objeto discount: reutilize um cupom da plataforma por discount.id (dis_...) ou aplique um desconto ad hoc com discount.type (flat/percentage) e discount.value (centavos quando flat).
Campo (em discount) | Tipo | Observação |
|---|---|---|
discount.id | string | Reutiliza um desconto/cupom existente (dis_...) |
discount.type | string | flat ou percentage |
discount.value | integer | Centavos quando flat |
Passo 4 — Criar a transação
Este é o coração do checkout. O corpo segue o contrato transaction.create. Pontos-chave:
- Valor: envie
amount(total em centavos, mínimo500= R$ 5,00, máximo20000000= R$ 200.000,00) ou uma lista deitemsque define o total — não os dois. Quandoamounté omitido,itemsé obrigatório. - Meio de pagamento:
payment.method∈credit | billet | pix | nupay | debit, compayment.currency: "BRL". - Cartão: use
payment.card.id(cartão salvo) ou os dados completos empayment.card(numbering,securityCode,expirationMonth,expirationYear,holderName). - Parcelas:
payment.installmentsde 1 a 12 (cartão). - Captura:
payment.capture: trueautoriza e captura na hora;falseapenas autoriza (capture depois — veja a seção sobre captura). - Cliente:
customer.idpara reaproveitar, ou dados completos para criar junto.
curl -s -X POST "https://api.selectwin.io/v1/transactions" \
-H "SelectKey: sl_live_aBcDeFgHiJkLmNoPqRsTuVwXyZ" \
-H "Content-Type: application/json" \
-H "X-Idempotency-Key: chk-tra-PEDIDO-1042" \
-d '{
"amount": 10000,
"description": "Pedido PEDIDO-1042",
"externalReference": "PEDIDO-1042",
"payment": {
"method": "credit",
"currency": "BRL",
"capture": true,
"installments": 3,
"card": { "id": "card_01hqzvabc", "securityCode": "123" }
},
"customer": { "id": "cus_01hqzvabc" },
"billing": { "address": { "id": "addr_01hqzvabc" } },
"discount": { "id": "dis_01hqzvabc" },
"callback": { "webhookUrl": "https://minhalojavirtual.com/webhooks/selectwin" },
"metadata": { "source": "website", "campaign": "summer_sale" }
}'Campos principais do corpo
| Campo | Tipo | Obrigatório | Observação |
|---|---|---|---|
amount | number (500–20000000) | condicional | Total em centavos. Obrigatório se items for omitido |
items | array | condicional | Linhas do carrinho. Obrigatório se amount for omitido |
payment.method | string | sim | credit, billet, pix, nupay, debit |
payment.currency | string | sim | BRL |
payment.installments | number (1–12) | não | Parcelas (cartão) |
payment.capture | boolean | não | true = autoriza e captura; false = só autoriza |
payment.card.id | string | não | Cartão salvo (card_...) — omita os dados completos |
customer.id | string | não | Cliente existente (pula criação aninhada) |
billing.address.id | string | não | Endereço de cobrança salvo (addr_...) |
discount.id / discount.type + discount.value | string / string+int | não | Cupom existente ou desconto ad hoc |
callback.webhookUrl | string (uri) | não | URL que recebe o callback de status desta transação |
description | string | não | Descrição legível da transação |
externalReference | string | não | Seu identificador do pedido |
metadata | object | não | Pares chave/valor (até 40 chaves) |
Carrinho com itens em vez de amount
Se preferir que o total venha das linhas, omita amount e envie items. Cada linha é de catálogo (id = variante var_..., com name/pricing opcionais para sobrepor) ou manual (sem id: informe name e pricing completo com unitPrice em centavos, quantity e currency: "BRL"). Não confunda com price/qty — os campos corretos são unitPrice e quantity.
{
"items": [
{
"name": "Smartphone XYZ",
"pricing": { "unitPrice": 10000, "quantity": 1, "currency": "BRL" }
}
]
}Resposta e ciclo de vida da transação
A resposta 201 traz id (tra_...), status, amount, originalAmount, o objeto payment (com pixQrCodeEmv/pixQrCodeUrl para Pix, billetUrl/billetBarcode para boleto, ou os últimos dígitos do cartão) e a timeline de eventos.
O status inicial depende do método:
- Pix / boleto começam em
pending— o cliente ainda precisa pagar (o objetopaymenttraz o QR/EMV ou a linha digitável). A aprovação chega depois, por webhook. - Cartão com
capture: truetende a já virapproved(ourefused). - Cartão com
capture: falseficapre-authorizedaguardando captura.
Valores possíveis de status: pre-authorized, pending, failed, approved, refused, canceled, chargeback, unauthorized, refunded, fraud-review, analyzing, awaiting, dispute. Quando um cartão é recusado, oriente o cliente conforme Recusas de Pagamento.
Use sempre X-Idempotency-Key ao criar transações
Uma transação criada duas vezes é uma cobrança dobrada. Gere uma chave estável por tentativa de pedido (ex.: derivada do seu externalReference) e reenvie a mesma chave em retries de rede. Veja Idempotência.
Captura posterior (autorizar agora, capturar depois)
Se criou a transação com capture: false (ex.: só captura ao despachar o pedido), capture quando a regra de negócio mandar:
curl -s -X POST "https://api.selectwin.io/v1/transactions/tra_01hqzvabc/capture" \
-H "SelectKey: sl_live_aBcDeFgHiJkLmNoPqRsTuVwXyZ" \
-H "Content-Type: application/json" \
-d '{ "amount": 10000 }'O amount capturado não pode exceder o autorizado. Erros comuns aqui: traCaptureAmountExceedsAuthorized, transactionCaptureNotProcessable.
Passo 5 — Receber a confirmação por webhook
Aqui é onde a venda se confirma. Há dois canais de notificação, que se complementam:
callback.webhookUrlda transação (passo 4): a Selectwin envia umPOSTa essa URL quando o status muda. É o jeito mais simples para o checkout.- Endpoints de webhook da conta: configurados no painel da Selectwin, recebem os eventos de toda a conta. Os eventos ficam consultáveis em
GET /v1/webhooks/events(catálogo de tipos em Catálogo de Eventos).
Nota
Não existe endpoint público para cadastrar endpoints de webhook nestes guias — a configuração de endpoints da conta é feita no painel. Para o checkout, o caminho direto é informar callback.webhookUrl na própria transação.
Seu servidor deve expor um endpoint HTTP que: (a) valide a assinatura do evento, (b) responda 200 rápido e (c) processe de forma idempotente (o mesmo evento pode chegar mais de uma vez). A validação de assinatura é obrigatória — veja Verificando Assinaturas de Webhook.
Exemplo de evento, no formato retornado pela API (campo requestPayload carrega o snapshot do recurso):
{
"id": "wbh_01hqzvabc",
"resource": "transaction",
"type": "transaction.approved",
"attempts": 1,
"lastAttempt": "2026-04-12T17:56:33.000Z",
"description": "Transaction approved event",
"requestPayload": {
"id": "tra_987654321",
"amount": 10000,
"status": "approved"
},
"source": "api",
"createdAt": "2026-04-12T17:56:32.000Z",
"updatedAt": "2026-04-12T17:56:33.000Z"
}Esboço de handler (Node/Express):
app.post('/webhooks/selectwin', async (req, res) => {
// 1. valide a assinatura ANTES de confiar no corpo (ver VerifyingSignatures)
if (!isValidSignature(req)) return res.status(401).end();
const event = req.body;
// 2. idempotência: ignore se já processou este event.id
if (await alreadyProcessed(event.id)) return res.status(200).end();
// 3. trate pelo type
if (event.type === 'transaction.approved') {
await markOrderPaid(event.requestPayload.id); // libere o pedido
}
// 4. responda 200 rápido; trabalho pesado vai para fila
return res.status(200).end();
});Se um evento falhou na entrega, você pode reenviá-lo manualmente com POST /v1/webhooks/events/{eventId} (ver Reenvio).
Passo 6 — Reconciliar e montar o painel do cliente
Depois da confirmação (ou para telas de status), consulte a transação pelo id. Não consulte em laço — use isto para reconciliação pontual ou quando o usuário abrir a tela.
curl -s "https://api.selectwin.io/v1/transactions/tra_01hqzvabc" \
-H "SelectKey: sl_live_aBcDeFgHiJkLmNoPqRsTuVwXyZ"O GET individual traz o objeto completo: status, payment (incluindo paidAt), items, receivables (o que você vai receber, com expectedOn) e a timeline de eventos (cada item tem message, type, createdAt).
Para o histórico no painel, liste as transações do cliente (projeção leve, ideal para tabelas):
curl -s "https://api.selectwin.io/v1/transactions?customerid=cus_01hqzvabc&sort=descending&limit=10" \
-H "SelectKey: sl_live_aBcDeFgHiJkLmNoPqRsTuVwXyZ"{
"offset": 0,
"limit": 10,
"total": 2,
"hasMore": false,
"data": [
{
"id": "tra_01hqzvabc",
"customId": "PEDIDO-1042",
"customerId": "cus_01hqzvabc",
"amount": 10000,
"status": "approved",
"method": "credit",
"currency": "BRL",
"createdAt": "2026-04-12T17:56:32.000Z"
},
{
"id": "tra_987654321",
"customerId": "cus_01hqzvabc",
"amount": 5000,
"status": "approved",
"method": "pix",
"currency": "BRL",
"createdAt": "2026-04-10T11:22:33.000Z"
}
]
}Filtros úteis: status, method, customeremail, e os parâmetros de período daterangegte/daterangelte. Para navegar páginas, use offset/limit e o campo hasMore — veja Paginação.
Erros comuns no checkout
Trate sempre pelo error.code (estável), nunca pela message. Catálogo completo em Códigos de Erro.
error.code | HTTP | Quando ocorre |
|---|---|---|
invalidParameters | 400 | Corpo malformado ou campo inválido (veja error.params) |
customerIdIsInvalid / customerNotFound | 400 / 404 | customer.id inválido ou inexistente |
cardIdIsInvalid / cardNotFound | 400 / 404 | payment.card.id inválido ou inexistente |
invalidCardData / cardExpired | 422 / 400 | Dados do cartão inválidos ou cartão vencido |
cardBrandNotSupported | 422 | Bandeira não suportada |
couponIdIsInvalid / couponIdNotFound | 400 / 404 | Cupom/desconto inválido ou inexistente |
insufficientFundsError | 402 | Pagamento recusado por saldo/limite |
traCaptureAmountExceedsAuthorized | 400 | Captura maior que o valor autorizado |
transactionCaptureNotProcessable | 409 | Transação não está em estado capturável |
paymentProcessingFailed | 503 | Adquirente/serviço de pagamento indisponível — repita com backoff |
unauthorized | 401 | SelectKey ausente, inválida ou revogada |
forbidden | 403 | Sem permissão ou limite atingido |
tooManyRequests | 429 | Limite de requisições excedido (respeite retryAfterMinutes) |
serverError | 500 | Erro interno — tente novamente |
Boas práticas
- Idempotência em toda escrita. Use
X-Idempotency-KeyemPOST /customers,/cardse principalmente/transactions, com uma chave estável por pedido. Reenvie a mesma chave em retries. - Webhook como fonte da verdade. Confirme a venda pelo evento
transaction.approved(ou pelocallback), nunca por polling noGET. Sempre valide a assinatura antes de confiar no corpo. - Processe eventos de forma idempotente. Deduplique pelo
event.id; o mesmo evento pode ser entregue mais de uma vez. Responda200rápido e jogue o trabalho pesado para uma fila. - Tokenize só quando fizer sentido. Crie
card_...apenas para recompra/one-click; para compra avulsa, mande os dados empayment.cardna própria transação e reduza sua exposição PCI. - Correlacione com
externalReferenceemetadata. Carregue o número do seu pedido — facilita conciliação, suporte e filtros nas listagens. - Dinheiro em centavos, sempre. Converta para centavos antes de enviar (
amount,unitPrice,discount.value) e divida por 100 só na exibição. - Capture no momento certo. Para fluxos de envio físico, use
capture: falsee capture com/captureao despachar, evitando capturar antes de poder cumprir o pedido. - Trate erros pelo
code. Façaswitchnoerror.code; em429e5xx, repita com backoff respeitandoerror.retryAfterMinutesquando presente.
Veja também
- Convenções — camelCase, centavos, datas, IDs opacos
- Autenticação — header
SelectKey - Idempotência —
X-Idempotency-Key - Proibição de Polling — por que confiar no webhook
- Verificando Assinaturas de Webhook e Catálogo de Eventos
- Segurança e PCI e Cartões para teste
- Shipping, Recusas de Pagamento e Códigos de Erro
How is this guide?