SelectwinDOCS
Assinaturas

Criar uma assinatura

Cria uma assinatura recorrente (mensal, anual, etc.) para um cliente, cobrando-o automaticamente a cada ciclo. O preço não é informado por você: ele vem das variantes referenciadas em items — esse é o

Cria uma assinatura recorrente (mensal, anual, etc.) para um cliente, cobrando-o automaticamente a cada ciclo. O preço não é informado por você: ele vem das variantes referenciadas em items — esse é o modelo Stripe-strict. Use este endpoint quando quiser começar a faturar um plano de forma contínua, sem reenviar valores a cada cobrança.

POST /v1/subscriptions

Como funciona

Quando você chama este endpoint, o servidor faz, em ordem:

  1. Resolve o cliente — usa um cliente existente (por customer.id) ou cria um novo a partir dos dados completos enviados em customer.
  2. Valida os itens — cada items[].id deve apontar para uma variante recorrente (var_..., uma variante de produto com pricing.type recorrente). É daí que saem unitPrice, moeda e a agenda de cobrança (frequência mensal/anual, dias de trial, etc.). Você controla apenas a quantity.
  3. Calcula o ciclo atual e tenta a primeira cobrança com o payment.method escolhido.
  4. Retorna 201 Created com a assinatura já materializada: currentCycle, currentCharge (a transação da primeira cobrança), customer, items etc.

Por que "Stripe-strict"? Porque a fonte de verdade do preço é o catálogo (produto/variante), não o corpo da requisição. Isso evita divergência de valores e mantém o histórico de cobrança consistente. Em troca, a API rejeita qualquer tentativa de definir preço manualmente (veja Erros e a Visão Geral).

A criação é síncrona quanto à validação e ao agendamento, mas a confirmação de pagamento (PIX, boleto) e as renovações futuras chegam por webhooks (subscription.*). Não fique consultando o status em loop — reaja aos eventos (veja Proibição de Polling).

Idempotência

Criar é uma operação de escrita. Envie sempre um X-Idempotency-Key único: se a resposta se perder na rede e você repetir a chamada, a mesma assinatura é retornada em vez de uma duplicata. Veja Idempotência.

Quando usar

  • Use para iniciar uma cobrança recorrente: planos SaaS, clubes de assinatura, mensalidades.
  • Não use para uma cobrança única — para isso há POST /v1/transactions. Variantes não recorrentes (oneTime) são rejeitadas aqui.
  • Para alterar uma assinatura já existente (adicionar add-ons/upsell), use Itens em vez de recriar.

Requisição

Headers

HeaderObrigatórioDescrição
SelectKeySimSua chave de API. Produção: sl_live_...; sandbox: sl_test_.... Nunca use Authorization: Bearer/Basic. Veja Autenticação.
Content-Type: application/jsonSimO corpo é JSON.
X-Idempotency-KeyRecomendadoChave única (ex.: UUID) para evitar criação duplicada.

Corpo

CampoTipoObrigatórioDescrição
payment.methodenumSimMétodo das cobranças recorrentes: credit, billet ou pix.
payment.currencystringSimCódigo ISO 4217 — apenas BRL.
items[]arraySimLinhas da assinatura. Cada uma referencia uma variante recorrente (var_...). Itens manuais (sem id) são rejeitados.
items[].idstringSimID público da variante (var_...). Fonte do preço e da agenda.
items[].pricing.quantityintegerNãoQuantidade (1–999999). O unitPrice, a moeda e a agenda não podem ser sobrescritos.
items[].enabledbooleanNãoSe a linha está ativa na assinatura.
items[].descriptionstringNãoDescrição da linha.
items[].isOrderbump / items[].isUpsellbooleanNãoMarca a linha como order bump / upsell.
items[].imagesarrayNãoURLs de imagem da linha.
items[].metadataobjectNãoMetadados por linha (máx. 20 chaves).
items[].externalReferencestringNãoSua referência externa para a linha.
customerobjectSimCliente da assinatura — veja a tabela abaixo.
discountobjectNãoDesconto aplicado por ciclo. Use discount.id (cupom dis_...) ou discount.type + discount.value inline.
discount.idstringNãoID de um cupom/desconto existente (dis_...).
discount.typeenumNãoflat (valor em centavos) ou percentage.
discount.valueintegerNãoValor do desconto (centavos para flat; 0–100 para percentage).
billing.addressobjectNãoEndereço de cobrança (street, number, city, state, country obrigatório, postcode obrigatório no formato CEP). Não envie campos de preço aqui — só endereço.
shippingobjectNãoDados de entrega (para assinaturas com produto físico): address, recipientName, trackingCode, etc.
splits[]arrayNãoDivisão de receita por destinatário. Veja Splits.
splits[].recipientstringNãoIdentificador do recebedor.
splits[].typeenumNãopercentage (0–100) ou flat (centavos).
splits[].valuenumberNãoValor do split (0–20000000).
callback.webhookUrlstring (url)NãoURL para os callbacks de status desta assinatura e dos ciclos.
geolocationobjectNãoDados antifraude: ipAddress, latitude, longitude, deviceFingerprint.
descriptionstringNãoDescrição livre da assinatura.
externalReferencestringNãoSua referência externa (correlação com o seu sistema).
metadataobjectNãoPares chave-valor livres (máx. 40 chaves).
tagsarrayNãoTags de string (máx. 40).
sessionIdstringNãoID de sessão de checkout (prefixo_valor).
integrationIdstringNãoID de integração para rastreio/automação.

Objeto customer — informe customer.id de um cliente existente ou os dados completos:

CampoTipoObrigatórioDescrição
customer.firstNamestringSim*Nome.
customer.lastNamestringSim*Sobrenome.
customer.emailstring (email)Sim*E-mail principal.
customer.document.typeenumNãocpf, cnpj ou passport.
customer.document.numberstringNãoApenas dígitos para cpf/cnpj.
customer.telephone.{countryCode,areaCode,number}stringNãoTelefone (apenas dígitos em cada parte).
customer.genderenumNãomale, female ou other.
customer.birthdatestring (date)NãoYYYY-MM-DD.
customer.additionalEmailsarrayNãoE-mails adicionais (máx. 20).
customer.externalReferencestringNãoSua referência do cliente.
customer.metadataobjectNãoMetadados do cliente (máx. 20 chaves).

* Obrigatórios apenas quando você cria o cliente inline. Se usar um cliente existente, basta enviar customer.id.

Não defina preço manualmente

Por ser Stripe-strict, não envie amount no corpo, campos de preço dentro de billing[*], nem itens sem variante. O preço sai sempre da variante. Tentativas disso retornam 422 — veja Erros.

Exemplo curl

curl -X POST "https://api.selectwin.io/v1/subscriptions" \
  -H "SelectKey: sl_live_aBcDeFgHiJkLmNoPqRsTuVwXyZ" \
  -H "Content-Type: application/json" \
  -H "X-Idempotency-Key: 2dfc6a63-5df5-4900-baca-e49d91ef393e" \
  -d '{
    "payment": { "method": "credit", "currency": "BRL" },
    "customer": {
      "firstName": "João",
      "lastName": "Silva",
      "email": "[email protected]",
      "document": { "type": "cpf", "number": "12345678901" },
      "telephone": { "countryCode": "55", "areaCode": "11", "number": "999999999" }
    },
    "items": [
      { "id": "var_abc123", "pricing": { "quantity": 1 }, "enabled": true }
    ],
    "discount": { "id": "dis_abc123" },
    "description": "Plano Premium — assinatura mensal",
    "externalReference": "SUB-1001",
    "metadata": { "source": "checkout" }
  }'

Variação — cliente existente + desconto inline em porcentagem:

{
  "payment": { "method": "pix", "currency": "BRL" },
  "customer": { "id": "cus_01hqzvabc" },
  "items": [{ "id": "var_abc123" }],
  "discount": { "type": "percentage", "value": 10 }
}

Resposta

201 Created — a assinatura já materializada, incluindo o currentCycle e a currentCharge (a transação da primeira cobrança).

{
  "id": "subs_01hqzvabc",
  "status": "active",
  "currency": "BRL",
  "method": "credit",
  "type": "prepaid",
  "currentCycle": {
    "id": "cyc_01hqzvabc",
    "cycle": 1,
    "status": "billed",
    "startDate": "2026-04-01T00:00:00.000Z",
    "endDate": "2026-04-30T23:59:59.000Z",
    "dueDate": "2026-04-01T00:00:00.000Z",
    "billedAt": "2026-04-12T17:56:33.000Z",
    "createdAt": "2026-04-01T00:00:00.000Z",
    "updatedAt": "2026-04-12T17:56:33.000Z"
  },
  "currentCharge": {
    "error": null,
    "id": "tra_01hqzvabc",
    "amount": 9900,
    "originalAmount": 9900,
    "status": "paid",
    "method": "credit",
    "currency": "BRL",
    "payment": { "provider": "selectwin", "cardBrand": "visa", "installments": 1, "paidAt": "2026-04-12T17:56:33.000Z" },
    "createdAt": "2026-04-12T17:56:33.000Z",
    "updatedAt": "2026-04-12T17:56:33.000Z"
  },
  "customer": { "id": "cus_01hqzvabc", "firstName": "João", "lastName": "Silva", "email": "[email protected]" },
  "billing": { "frequency": "monthly", "frequencyCount": 1, "freeTrialDays": 0 },
  "discount": { "value": 500, "type": "flat", "percentageOfAmount": null },
  "shippable": false,
  "shipping": null,
  "spplited": false,
  "splits": [],
  "items": [
    { "id": "item_01hqzvabc", "quantity": 1 }
  ],
  "createdAt": "2026-04-12T17:56:33.000Z",
  "updatedAt": "2026-04-12T17:56:33.000Z",
  "merchant": { "name": "Seller Name", "merchantId": "bus_1234567890", "isSubAccount": false }
}

Campos-chave da resposta

CampoDescrição
idID da assinatura (subs_...). Trate como opaco.
statusEstado atual: pending, trialing, active, pastdue, unpaid, paused ou canceled. Veja a Visão Geral.
typeModelo de cobrança da assinatura (ex.: prepaid).
currentCycleCiclo de cobrança vigente: cycle (número), status (ex.: billed, paid), startDate/endDate/dueDate/billedAt. Valores em centavos.
currentChargeA transação da primeira cobrança (tra_...) com status, amount e detalhes de payment. Em PIX/boleto, traz os dados para o cliente pagar.
billing.frequency / billing.frequencyCount / billing.freeTrialDaysAgenda derivada da variante (ex.: monthly, a cada 1, com 0 dias de trial).
discountDesconto resolvido e travado na criação: value, type (flat/percentage), percentageOfAmount.
spplited / splitsSe a receita é dividida e as regras de split aplicadas.
items[]Linhas materializadas (item_...) com quantity. Os detalhes completos (name, unitPrice, currency) aparecem ao consultar a assinatura ou via Itens.

Primeira cobrança recusada

Se a primeira cobrança falhar, currentCharge.error descreve o motivo e o status reflete o resultado (ex.: pastdue). A assinatura é criada mesmo assim — trate o webhook subscription.pastdue e peça ao cliente para atualizar o método de pagamento.

Diferente do read (GET /v1/subscriptions/{id}), o create já inclui a currentCharge da primeira cobrança. Em listas, os itens vêm como projeção leve — consulte o recurso individual para o objeto completo (veja Convenções).

Erros

error.codeHTTPQuando ocorre
amountOnlyNotAllowedInSubscription422Enviou amount no corpo (preço vem da variante).
billingNotAcceptedInStripeStrict422Enviou campos de preço em billing[*].
manualItemNotAllowedInSubscription422Item sem id de variante (item manual).
oneTimeVariantNotAllowedInSubscription422Usou uma variante não recorrente (oneTime).
variantScheduleDivergent422Variantes com agendas de cobrança divergentes entre si.
variantIdIsInvalid400items[].id não é uma variante válida.
variantIdNotFound404Variante referenciada não existe.
couponIdIsInvalid400discount.id inválido.
couponIdNotFound404Cupom referenciado não existe.
invalidParameters400Validação genérica — veja error.params para os campos.
unauthorized401SelectKey ausente, inválida ou revogada.
forbidden403Sem permissão ou limite atingido.
insufficientFundsError402Pagamento da primeira cobrança recusado por saldo/limite.
unprocessableEntity422Regra de negócio não satisfeita.
tooManyRequests429Limite de requisições excedido — respeite error.retryAfterMinutes.
serverError500Erro interno — repita com backoff.

Trate sempre pelo error.code (estável), nunca pela message. Catálogo completo: Códigos de Erro.

Boas práticas

  1. Modele o preço no catálogo. Crie a variante recorrente com o valor e a agenda certos; a assinatura só referencia var_... e a quantity. Mudar preço = nova variante, não editar a assinatura.
  2. Garanta agenda única. Todos os items precisam ter a mesma frequência de cobrança, senão você recebe variantScheduleDivergent. Para frequências diferentes, crie assinaturas separadas.
  3. Sempre envie X-Idempotency-Key ao criar, para nunca duplicar uma assinatura em retentativas de rede.
  4. Reaproveite clientes com customer.id quando já existirem, em vez de reenviar dados completos — evita duplicar cadastros.
  5. Reaja a webhooks subscription.created, subscription.active, subscription.pastdue em vez de consultar o status em loop.
  6. Trate a primeira cobrança recusada (currentCharge.error / status pastdue) com um fluxo de atualização de cartão.
  7. Correlacione com externalReference/metadata para ligar a assinatura ao pedido/cliente no seu sistema.

Veja também

How is this guide?

On this page