Sessões de checkout
Uma Checkout Session é um carrinho de compra hospedado pela Selectwin: você cria a sessão pela API, recebe uma URL de checkout pronta e a entrega ao comprador (redirect, e-mail, SMS ou QR Code). O com
Uma Checkout Session é um carrinho de compra hospedado pela Selectwin: você cria a sessão pela API, recebe uma URL de checkout pronta e a entrega ao comprador (redirect, e-mail, SMS ou QR Code). O comprador preenche os dados e paga na página hospedada, e você confirma o resultado por webhook — sem construir um checkout próprio nem tocar em dados de cartão.
POST · GET · PUT · DELETE /v1/checkouts/sessions
Diferente de um Payment Link (uma URL reutilizável, sem comprador fixo), a session representa uma intenção de compra específica: itens já precificados pelo backend, cliente/endereço opcionais e um externalTransactionId para reconciliar com o seu ERP.
Como funciona
Quando você cria uma sessão, o servidor monta um carrinho hospedado e devolve uma url pronta. O fluxo é:
- Você cria a sessão com os
lineItems(o que vender) e, opcionalmente, cliente, cupons, métodos de pagamento e URLs de redirect. - O backend resolve o preço de cada item server-side (nunca confia no preço enviado pelo cliente — veja Modelo de precificação) e recalcula os totais.
- A API devolve o objeto
checkout.sessioncom umidpúblico (ses_…) e aurlhospedada. - Você entrega a
urlao comprador (redirect, e-mail, SMS, QR Code). - O comprador paga na página da Selectwin; a sessão gera uma transação (cobrança).
- Você reage ao webhook
checkout.session.completedpara liberar o pedido — não faça polling.
Session vs. Payment Link
| Checkout Session | Payment Link | |
|---|---|---|
| Natureza | Uma intenção de compra específica | Uma URL reutilizável |
| Comprador | Conhecido/esperado (snapshot na sessão) | Qualquer um que abrir o link |
| Itens | lineItems[] precificados na criação | items[] (variantes) fixos no link |
| Reconciliação | externalTransactionId (1:1 com o seu pedido) | Pelo id do link / transação gerada |
| Quando usar | Carrinho montado na hora para um pedido | "Um link, vários compradores" |
Modelo de precificação (autoritativo no backend)
O preço nunca é confiado cegamente ao cliente da API. Cada item de lineItems[] é resolvido no servidor de duas formas:
- Catálogo — informe o
idde uma variante (var_…). O backend lê o preço da variante. Envie apenasid+quantity; opcionalmente sobrescreva comunitPrice(em centavos). - Ad-hoc — sem variante de catálogo: informe
nameeunitPrice(centavos). Útil para serviços avulsos. Item sem preço →checkoutSessionItemPriceRequired;idque não resolve →checkoutSessionItemInvalid.
O carrinho é agnóstico ao tipo de pagamento: pode misturar itens oneTime e variantes recurring. Quando há item recorrente, a sessão vira assinatura no momento da cobrança, lendo billingType/cycles/trialInterval da própria variante — você não envia esses campos.
Valores em centavos
unitPrice e totals.* são inteiros em centavos (49700 = R$ 497,00). totals.amount já vem líquido do desconto. Veja Convenções.
Quando usar (e quando não usar)
Use Checkout Sessions quando você monta um carrinho específico para um pedido (no seu site, app ou ERP) e quer cobrar numa página hospedada, sem lidar com PCI/cartão nem construir o checkout. A sessão carrega o pedido inteiro (itens, cliente, cupons) e fecha em 1:1 com o seu externalTransactionId.
Considere outra abordagem quando:
- Você quer um link fixo, reutilizável por muitos compradores → use Payment Links.
- Você já tem um checkout próprio e só precisa processar o pagamento no backend → use Transações diretamente.
Ciclo de vida (status)
status | Significado | Terminal? |
|---|---|---|
active | Criada e válida; aguardando/recebendo o comprador. | não |
pending | Pagamento iniciado, aguardando confirmação. | não |
completed | Pagamento aprovado. | sim |
abandoned | Marcada como abandonada (cron de timeout). | sim |
expired | Passou de expiresAt ou cancelada via DELETE. | sim |
Em status terminal a
urlredireciona para uma página de estado em vez de renderizar o checkout.
Requisição
Headers
| Cabeçalho | Uso |
|---|---|
SelectKey | Obrigatório. Sua chave de API. Produção começa com sl_live_; sandbox com sl_test_. Nunca use Authorization: Bearer/Basic. Veja Autenticação. |
Content-Type: application/json | Obrigatório em POST e PUT (requisições com corpo). |
X-Idempotency-Key | Recomendado em POST. Garante que repetir a mesma criação (ex.: após timeout de rede) não gere sessões duplicadas. Veja Idempotência. |
Criar — POST /v1/checkouts/sessions
Itens resolvidos pelo catálogo (var_…):
curl -X POST "https://api.selectwin.io/v1/checkouts/sessions" \
-H "SelectKey: sl_live_aBcDeFgHiJkLmNoPqRsTuVwXyZ" \
-H "Content-Type: application/json" \
-H "X-Idempotency-Key: 1f8d2c6a-7b9e-4a1d-8c3f-0e2b4d6f8a10" \
-d '{
"ambient": "live",
"externalTransactionId": "pedido-7781",
"lineItems": [
{ "id": "var_main_product", "quantity": 1 },
{ "id": "var_orderbump_book", "quantity": 1, "isOrderbump": true }
],
"customer": { "email": "[email protected]", "fullName": "Maria Silva" },
"coupons": [{ "code": "DESCONTO10" }],
"redirectUrls": { "paid": "https://seller.com/obrigado" },
"metadata": { "erpOrderId": "9921" }
}'A resposta 201 é o objeto checkout.session (veja Resposta). Redirecione o comprador para url.
Item ad-hoc (fora do catálogo — exige name + unitPrice):
{ "lineItems": [{ "name": "Consultoria 1h", "quantity": 2, "unitPrice": 25000 }] }Atualizar — PUT /v1/checkouts/sessions/{id}
O PUT faz merge parcial, com recálculo seguro de totais, e só funciona em status não-terminal (active/pending) — senão checkoutSessionNotEditable (422):
- Campos simples (
customer,address,metadata,redirectUrls,externalTransactionId,expiresAt) atualizam sem recalcular o carrinho. - Enviar
lineItemsre-precifica itens e totais e zera os cupons — reenviecouponsno mesmoPUTpara reaplicá-los. - Enviar
couponssemlineItemsre-precifica os itens atuais para recalcular o desconto.
curl -X PUT "https://api.selectwin.io/v1/checkouts/sessions/ses_a1b2c3d4e5f6789012ab" \
-H "SelectKey: sl_live_aBcDeFgHiJkLmNoPqRsTuVwXyZ" \
-H "Content-Type: application/json" \
-d '{
"lineItems": [{ "id": "var_main_product", "quantity": 2 }],
"coupons": [{ "code": "DESCONTO10" }]
}'Cancelar — DELETE /v1/checkouts/sessions/{id}
O DELETE é um soft-cancel: marca status=expired e preserva a linha (os dados de funil/abandono continuam disponíveis). Ele rejeita com checkoutSessionNotCancelable (422) se a sessão já estiver completed ou tiver gerado uma transação — essas são mantidas para reconciliação.
curl -X DELETE "https://api.selectwin.io/v1/checkouts/sessions/ses_a1b2c3d4e5f6789012ab" \
-H "SelectKey: sl_live_aBcDeFgHiJkLmNoPqRsTuVwXyZ"Listar / Consultar — GET /v1/checkouts/sessions[/{id}]
A listagem é paginada e aceita filtros (status, externalTransactionId, datas). Use o GET por {id} para reconciliação pontual:
# Listar sessões ativas de um pedido específico
curl "https://api.selectwin.io/v1/checkouts/sessions?status=active&externalTransactionId=pedido-7781&limit=20" \
-H "SelectKey: sl_live_aBcDeFgHiJkLmNoPqRsTuVwXyZ"
# Consultar uma sessão
curl "https://api.selectwin.io/v1/checkouts/sessions/ses_a1b2c3d4e5f6789012ab" \
-H "SelectKey: sl_live_aBcDeFgHiJkLmNoPqRsTuVwXyZ"Resposta
Objeto checkout.session como retornado por criar, consultar e atualizar:
{
"object": "checkout.session",
"id": "ses_a1b2c3d4e5f6789012ab",
"url": "https://checkout.selectwin.io/p/live/ses_a1b2c3d4e5f6789012ab",
"status": "active",
"ambient": "live",
"source": "api",
"currency": "BRL",
"lineItems": [
{ "id": "var_main_product", "name": "Curso Premium", "quantity": 1, "unitPrice": 49700,
"currency": "BRL", "isOrderbump": false, "isUpsell": false,
"metadata": { "sku": "ABC-123" }, "externalReference": "erp-ref-9" }
],
"totals": { "amount": 46523, "discount": 5167, "shipping": 0, "tax": 0 },
"coupons": [{ "code": "DESCONTO10", "type": "percentage", "value": 10 }],
"customer": {
"email": "[email protected]", "fullName": "Maria Silva",
"document": { "number": "12345678909", "type": "cpf" },
"telephone": { "countryCode": "55", "areaCode": "11", "number": "999998888" }
},
"address": {
"postcode": "01310-100", "street": "Av. Paulista", "number": "1000",
"district": "Bela Vista", "city": "São Paulo", "state": "SP", "country": "BR"
},
"paymentMethods": ["pix", "credit", "billet"],
"paymentSelection": null,
"redirectUrls": {
"paid": "https://seller.com/obrigado",
"refused": "https://seller.com/recusado",
"pending": "https://seller.com/processando"
},
"utm": { "source": "newsletter", "campaign": "junho" },
"metadata": { "erpOrderId": "9921" },
"customInputs": null,
"integrationType": "shopify",
"integrationId": "cmp_shop_123",
"externalTransactionId": "pedido-7781",
"firstInitialization": true,
"online": false,
"expiresAt": "2026-06-12T12:00:00.000Z",
"createdAt": "2026-06-08T12:00:00.000Z",
"updatedAt": "2026-06-08T12:00:00.000Z"
}| Campo | Descrição |
|---|---|
id | Identificador público (ses_…), opaco. Use em GET/PUT/DELETE e na reconciliação. |
url | A URL hospedada. Entregue ao comprador. Formato {domínio}/p/{ambient}/{id}. |
status | Estado da sessão (ver Ciclo de vida). |
ambient | live ou sandbox. |
currency | Moeda da sessão (ISO 4217), ex.: BRL. |
lineItems | Itens já com preço resolvido pelo backend. |
totals | Totais recalculados pelo backend (centavos). amount já é líquido do desconto. |
coupons | Cupons aplicados/validados. |
customer / address | Snapshot do comprador (parcial até ele preencher; é um objeto JSON, ainda não cria registro). |
paymentMethods | Métodos habilitados (do corpo ou do template global da empresa). |
redirectUrls | URLs de pós-pagamento (do corpo ou do template global). |
metadata / utm / externalTransactionId | Ecoam o que você enviou (reconciliação). |
firstInitialization | true enquanto o comprador não abriu a url. |
expiresAt | Expiração (default 48h se omitido). |
Configuração (paymentMethods / redirectUrls)
Você pode enviar paymentMethods e redirectUrls no corpo da sessão. Quando omitidos, o backend usa o template de checkout global da empresa (isGlobal) para os métodos habilitados e as URLs de redirect; o redirectUrls do corpo sobrescreve por sessão. Sem template e sem corpo, o default é ["pix", "credit", "billet"].
Cupons
Envie coupons: [{ "code": "…" }] por código. O backend valida a elegibilidade (datas, limite de uso, mínimo de carrinho, itens permitidos) e calcula o desconto, refletindo em totals.discount/totals.amount e em coupons[]. Cupom inexistente → couponNotFound; inelegível → erro de cupom específico (ex.: couponExpired, couponMinCartAmount). Veja Cupons.
Webhooks
| Evento | Quando |
|---|---|
checkout.session.completed | O comprador concluiu o pagamento na sessão. Use isto (não polling) para liberar o pedido. |
O caminho recomendado de confirmação é webhook + a transação correspondente — veja Proibição de Polling e Verificando Assinaturas de Webhook.
Erros
Os endpoints de checkout retornam o envelope de erro padrão; trate sempre pelo error.code, nunca pela message. Veja Tratamento de Erros e o Catálogo de Códigos de Erro.
error.code | HTTP | Quando ocorre |
|---|---|---|
invalidParameters | 400 | Corpo/parâmetro inválido (campos detalhados em error.params). |
checkoutSessionItemPriceRequired | 400 | Item ad-hoc sem unitPrice (item fora do catálogo precisa de name + unitPrice). |
checkoutSessionItemInvalid | 400 | lineItems[].id não resolve para uma variante válida. |
checkoutSessionNotEditable | 422 | PUT numa sessão em status terminal (completed/abandoned/expired). |
checkoutSessionNotCancelable | 422 | DELETE numa sessão já completed ou que já gerou transação. |
couponNotFound | 404 | Cupom informado por código não existe. |
unauthorized | 401 | SelectKey ausente, inválida ou revogada. |
forbidden | 403 | Autenticado, mas sem permissão para a operação. |
tooManyRequests | 429 | Limite de requisições excedido — respeite error.retryAfterMinutes. |
serverError | 500 | Erro interno — repita com backoff. |
Boas práticas
- Resolva preço pelo catálogo (
var_…) sempre que possível; use ad-hoc só para itens fora do catálogo. - Use
externalTransactionIdpara reconciliar — ele também é aceito como filtro (GET /v1/checkouts/sessions?externalTransactionId=pedido-7781). - Reaja a
checkout.session.completedem vez de fazer polling noGET. - Reenvie
couponsao alterarlineItemsnumPUT, já que mudar o carrinho zera os descontos. - Defina
expiresAtse a janela default de 48h não fizer sentido para o seu fluxo. - Guarde o
id(ses_…), não aurl— oidé a chave estável para consultar, atualizar e cancelar.
Veja também
- Payment Links — a alternativa de URL reutilizável.
- Visão geral de Checkouts — o recurso e suas relações.
- Transações — a cobrança gerada pela sessão concluída.
- Produtos e Variantes — origem do preço de cada
lineItems[].id. - Cupons — validação e cálculo de desconto.
- Webhook Events · Verificando Assinaturas · Proibição de Polling
- Autenticação · Convenções · Idempotência · Códigos de Erro
How is this guide?