SelectwinDOCS
Casos de uso

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 typecpf | 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.

CampoTipoObrigatórioObservação
firstNamestringsimNome do cliente
lastNamestringsimSobrenome
emailstring (email)simE-mail principal
document.typestringnãocpf, cnpj ou passport
document.numberstringnãoSó dígitos quando cpf/cnpj
telephone.countryCode / areaCode / numberstringnãoSó dígitos em cada parte
externalReferencestringnãoSeu 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):

CampoTipoObrigatórioObservação
customerIdstringsimDono do cartão (cus_...)
holderNamestringsimNome impresso no cartão
numberingstringsimNúmero do cartão (validado por Luhn)
expirationMonthnumber (1–12)simMês de expiração
expirationYearintegersimAno (2 ou 4 dígitos)
securityCodestring (\d{3,4})simCVV/CVC
primarybooleannãoDefine 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)TipoObservação
discount.idstringReutiliza um desconto/cupom existente (dis_...)
discount.typestringflat ou percentage
discount.valueintegerCentavos 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ínimo 500 = R$ 5,00, máximo 20000000 = R$ 200.000,00) ou uma lista de items que define o total — não os dois. Quando amount é omitido, items é obrigatório.
  • Meio de pagamento: payment.methodcredit | billet | pix | nupay | debit, com payment.currency: "BRL".
  • Cartão: use payment.card.id (cartão salvo) ou os dados completos em payment.card (numbering, securityCode, expirationMonth, expirationYear, holderName).
  • Parcelas: payment.installments de 1 a 12 (cartão).
  • Captura: payment.capture: true autoriza e captura na hora; false apenas autoriza (capture depois — veja a seção sobre captura).
  • Cliente: customer.id para 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

CampoTipoObrigatórioObservação
amountnumber (500–20000000)condicionalTotal em centavos. Obrigatório se items for omitido
itemsarraycondicionalLinhas do carrinho. Obrigatório se amount for omitido
payment.methodstringsimcredit, billet, pix, nupay, debit
payment.currencystringsimBRL
payment.installmentsnumber (1–12)nãoParcelas (cartão)
payment.capturebooleannãotrue = autoriza e captura; false = só autoriza
payment.card.idstringnãoCartão salvo (card_...) — omita os dados completos
customer.idstringnãoCliente existente (pula criação aninhada)
billing.address.idstringnãoEndereço de cobrança salvo (addr_...)
discount.id / discount.type + discount.valuestring / string+intnãoCupom existente ou desconto ad hoc
callback.webhookUrlstring (uri)nãoURL que recebe o callback de status desta transação
descriptionstringnãoDescrição legível da transação
externalReferencestringnãoSeu identificador do pedido
metadataobjectnãoPares 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 objeto payment traz o QR/EMV ou a linha digitável). A aprovação chega depois, por webhook.
  • Cartão com capture: true tende a já vir approved (ou refused).
  • Cartão com capture: false fica pre-authorized aguardando 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:

  1. callback.webhookUrl da transação (passo 4): a Selectwin envia um POST a essa URL quando o status muda. É o jeito mais simples para o checkout.
  2. 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.codeHTTPQuando ocorre
invalidParameters400Corpo malformado ou campo inválido (veja error.params)
customerIdIsInvalid / customerNotFound400 / 404customer.id inválido ou inexistente
cardIdIsInvalid / cardNotFound400 / 404payment.card.id inválido ou inexistente
invalidCardData / cardExpired422 / 400Dados do cartão inválidos ou cartão vencido
cardBrandNotSupported422Bandeira não suportada
couponIdIsInvalid / couponIdNotFound400 / 404Cupom/desconto inválido ou inexistente
insufficientFundsError402Pagamento recusado por saldo/limite
traCaptureAmountExceedsAuthorized400Captura maior que o valor autorizado
transactionCaptureNotProcessable409Transação não está em estado capturável
paymentProcessingFailed503Adquirente/serviço de pagamento indisponível — repita com backoff
unauthorized401SelectKey ausente, inválida ou revogada
forbidden403Sem permissão ou limite atingido
tooManyRequests429Limite de requisições excedido (respeite retryAfterMinutes)
serverError500Erro interno — tente novamente

Boas práticas

  • Idempotência em toda escrita. Use X-Idempotency-Key em POST /customers, /cards e 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 pelo callback), nunca por polling no GET. 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. Responda 200 rá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 em payment.card na própria transação e reduza sua exposição PCI.
  • Correlacione com externalReference e metadata. 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: false e capture com /capture ao despachar, evitando capturar antes de poder cumprir o pedido.
  • Trate erros pelo code. Faça switch no error.code; em 429 e 5xx, repita com backoff respeitando error.retryAfterMinutes quando presente.

Veja também

How is this guide?

On this page