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:
- Resolve o cliente — usa um cliente existente (por
customer.id) ou cria um novo a partir dos dados completos enviados emcustomer. - Valida os itens — cada
items[].iddeve apontar para uma variante recorrente (var_..., uma variante de produto compricing.typerecorrente). É daí que saemunitPrice, moeda e a agenda de cobrança (frequência mensal/anual, dias de trial, etc.). Você controla apenas aquantity. - Calcula o ciclo atual e tenta a primeira cobrança com o
payment.methodescolhido. - Retorna
201 Createdcom a assinatura já materializada:currentCycle,currentCharge(a transação da primeira cobrança),customer,itemsetc.
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
| Header | Obrigatório | Descrição |
|---|---|---|
SelectKey | Sim | Sua chave de API. Produção: sl_live_...; sandbox: sl_test_.... Nunca use Authorization: Bearer/Basic. Veja Autenticação. |
Content-Type: application/json | Sim | O corpo é JSON. |
X-Idempotency-Key | Recomendado | Chave única (ex.: UUID) para evitar criação duplicada. |
Corpo
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
payment.method | enum | Sim | Método das cobranças recorrentes: credit, billet ou pix. |
payment.currency | string | Sim | Código ISO 4217 — apenas BRL. |
items[] | array | Sim | Linhas da assinatura. Cada uma referencia uma variante recorrente (var_...). Itens manuais (sem id) são rejeitados. |
items[].id | string | Sim | ID público da variante (var_...). Fonte do preço e da agenda. |
items[].pricing.quantity | integer | Não | Quantidade (1–999999). O unitPrice, a moeda e a agenda não podem ser sobrescritos. |
items[].enabled | boolean | Não | Se a linha está ativa na assinatura. |
items[].description | string | Não | Descrição da linha. |
items[].isOrderbump / items[].isUpsell | boolean | Não | Marca a linha como order bump / upsell. |
items[].images | array | Não | URLs de imagem da linha. |
items[].metadata | object | Não | Metadados por linha (máx. 20 chaves). |
items[].externalReference | string | Não | Sua referência externa para a linha. |
customer | object | Sim | Cliente da assinatura — veja a tabela abaixo. |
discount | object | Não | Desconto aplicado por ciclo. Use discount.id (cupom dis_...) ou discount.type + discount.value inline. |
discount.id | string | Não | ID de um cupom/desconto existente (dis_...). |
discount.type | enum | Não | flat (valor em centavos) ou percentage. |
discount.value | integer | Não | Valor do desconto (centavos para flat; 0–100 para percentage). |
billing.address | object | Não | Endereç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. |
shipping | object | Não | Dados de entrega (para assinaturas com produto físico): address, recipientName, trackingCode, etc. |
splits[] | array | Não | Divisão de receita por destinatário. Veja Splits. |
splits[].recipient | string | Não | Identificador do recebedor. |
splits[].type | enum | Não | percentage (0–100) ou flat (centavos). |
splits[].value | number | Não | Valor do split (0–20000000). |
callback.webhookUrl | string (url) | Não | URL para os callbacks de status desta assinatura e dos ciclos. |
geolocation | object | Não | Dados antifraude: ipAddress, latitude, longitude, deviceFingerprint. |
description | string | Não | Descrição livre da assinatura. |
externalReference | string | Não | Sua referência externa (correlação com o seu sistema). |
metadata | object | Não | Pares chave-valor livres (máx. 40 chaves). |
tags | array | Não | Tags de string (máx. 40). |
sessionId | string | Não | ID de sessão de checkout (prefixo_valor). |
integrationId | string | Não | ID de integração para rastreio/automação. |
Objeto customer — informe customer.id de um cliente existente ou os dados completos:
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
customer.firstName | string | Sim* | Nome. |
customer.lastName | string | Sim* | Sobrenome. |
customer.email | string (email) | Sim* | E-mail principal. |
customer.document.type | enum | Não | cpf, cnpj ou passport. |
customer.document.number | string | Não | Apenas dígitos para cpf/cnpj. |
customer.telephone.{countryCode,areaCode,number} | string | Não | Telefone (apenas dígitos em cada parte). |
customer.gender | enum | Não | male, female ou other. |
customer.birthdate | string (date) | Não | YYYY-MM-DD. |
customer.additionalEmails | array | Não | E-mails adicionais (máx. 20). |
customer.externalReference | string | Não | Sua referência do cliente. |
customer.metadata | object | Não | Metadados 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
| Campo | Descrição |
|---|---|
id | ID da assinatura (subs_...). Trate como opaco. |
status | Estado atual: pending, trialing, active, pastdue, unpaid, paused ou canceled. Veja a Visão Geral. |
type | Modelo de cobrança da assinatura (ex.: prepaid). |
currentCycle | Ciclo de cobrança vigente: cycle (número), status (ex.: billed, paid), startDate/endDate/dueDate/billedAt. Valores em centavos. |
currentCharge | A 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.freeTrialDays | Agenda derivada da variante (ex.: monthly, a cada 1, com 0 dias de trial). |
discount | Desconto resolvido e travado na criação: value, type (flat/percentage), percentageOfAmount. |
spplited / splits | Se 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 acurrentChargeda 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.code | HTTP | Quando ocorre |
|---|---|---|
amountOnlyNotAllowedInSubscription | 422 | Enviou amount no corpo (preço vem da variante). |
billingNotAcceptedInStripeStrict | 422 | Enviou campos de preço em billing[*]. |
manualItemNotAllowedInSubscription | 422 | Item sem id de variante (item manual). |
oneTimeVariantNotAllowedInSubscription | 422 | Usou uma variante não recorrente (oneTime). |
variantScheduleDivergent | 422 | Variantes com agendas de cobrança divergentes entre si. |
variantIdIsInvalid | 400 | items[].id não é uma variante válida. |
variantIdNotFound | 404 | Variante referenciada não existe. |
couponIdIsInvalid | 400 | discount.id inválido. |
couponIdNotFound | 404 | Cupom referenciado não existe. |
invalidParameters | 400 | Validação genérica — veja error.params para os campos. |
unauthorized | 401 | SelectKey ausente, inválida ou revogada. |
forbidden | 403 | Sem permissão ou limite atingido. |
insufficientFundsError | 402 | Pagamento da primeira cobrança recusado por saldo/limite. |
unprocessableEntity | 422 | Regra de negócio não satisfeita. |
tooManyRequests | 429 | Limite de requisições excedido — respeite error.retryAfterMinutes. |
serverError | 500 | Erro interno — repita com backoff. |
Trate sempre pelo error.code (estável), nunca pela message. Catálogo completo: Códigos de Erro.
Boas práticas
- Modele o preço no catálogo. Crie a variante recorrente com o valor e a agenda certos; a assinatura só referencia
var_...e aquantity. Mudar preço = nova variante, não editar a assinatura. - Garanta agenda única. Todos os
itemsprecisam ter a mesma frequência de cobrança, senão você recebevariantScheduleDivergent. Para frequências diferentes, crie assinaturas separadas. - Sempre envie
X-Idempotency-Keyao criar, para nunca duplicar uma assinatura em retentativas de rede. - Reaproveite clientes com
customer.idquando já existirem, em vez de reenviar dados completos — evita duplicar cadastros. - Reaja a webhooks
subscription.created,subscription.active,subscription.pastdueem vez de consultar o status em loop. - Trate a primeira cobrança recusada (
currentCharge.error/ statuspastdue) com um fluxo de atualização de cartão. - Correlacione com
externalReference/metadatapara ligar a assinatura ao pedido/cliente no seu sistema.
Veja também
- Visão Geral — modelo do objeto, estados e ciclo de vida.
- Consultar e Listar — recuperar assinaturas.
- Cancelar — encerrar uma assinatura.
- Ciclos — histórico de cobranças e renovação.
- Itens — adicionar/remover linhas (upsell, add-ons).
- Splits — divisão de receita.
- Idempotência · Convenções · Códigos de Erro
How is this guide?