Criar assinatura
Criar uma assinatura recorrente com cartão de crédito via checkout hospedado em POST /v1/subscriptions.
A Allya Payments aceita assinaturas recorrentes nos três gateways usando cartão de crédito e checkout hospedado pelo próprio gateway. Você nunca envia número, CVV, titular ou validade: a Allya retorna checkoutUrl e o cliente final preenche os dados na página do gateway.
Pré-requisitos
- Conectar um gateway que suporte assinatura recorrente (Abacate Pay, Asaas ou Pagar.me): veja Gateways.
- Configurar o roteamento de assinatura em Roteamento → Assinaturas recorrentes para que
cardresolva para o gateway desejado. - Apenas Pagar.me: criar um plano previamente na API do Pagar.me (
POST /plans) e ter oplan.idem mãos: veja a seção Pagar.me: plano obrigatório abaixo.
Sem roteamento, a chamada retorna 409 no_routing_rule. Sem gateway.pagarme.planId quando o roteamento resolver para Pagar.me, retorna 400 invalid_input.
Endpoint
POST /v1/subscriptions
Authorization: Bearer sk_test_... (ou sk_live_...)
Content-Type: application/jsonBody
{
amount: number; // BRL centavos, por ciclo
currency: "BRL";
interval: "monthly" | "yearly";
trialDays?: number; // dias de trial. Default 0.
externalId: string; // obrigatório. Idempotência por (environmentId, externalId).
customer: {
name: string;
email: string;
document: string; // CPF/CNPJ
phone?: string; // obrigatório no Pagar.me
};
metadata?: Record<string, unknown>;
card?: {
installments?: number; // ignorado; assinatura não parcela
};
gateway?: {
pagarme?: {
planId: string; // ID do plano pré-criado no Pagar.me
};
};
}| Campo | Tipo | Notas |
|---|---|---|
amount | number | Centavos. Cobrado a cada ciclo. No Pagar.me, o valor real é o do plano referenciado por gateway.pagarme.planId; o amount aqui fica informacional. |
currency | string | Apenas BRL. Aceito em qualquer caixa, normalizado para BRL. Outras moedas retornam 400 invalid_input. |
interval | string | monthly ou yearly. No Pagar.me, o intervalo real é o do plano. |
trialDays | number | 0 (padrão) cria assinatura já cobrável; valores > 0 deixam a assinatura em trialing até o trial expirar. O suporte varia por gateway: veja Período de trial abaixo. |
externalId | string | Obrigatório em todos os gateways. Identificador único do seu sistema usado como chave de idempotência por ambiente. Recomendado identificar o cliente + oferta, não o ciclo (ex.: assinatura_cliente_42_plano_pro_mensal). Máx. 255 caracteres. |
customer.phone | string | Obrigatório no Pagar.me. |
gateway.pagarme.planId | string | Obrigatório quando o roteamento resolver para Pagar.me. ID retornado por POST /plans na API do Pagar.me. |
A Allya não usa header Idempotency-Key. Para assinaturas, a idempotência vem de externalId no body, obrigatório. Use um valor que identifique a assinatura no seu sistema, como assinatura_cliente_42_plano_pro_mensal, não a fatura de um ciclo específico.
Resposta
{
"id": "sub_4a1c0e7b9d224f5fb8c6102d7e3a9014",
"status": "active",
"method": "card",
"gateway": "asaas",
"amount": 4990,
"currency": "BRL",
"interval": "monthly",
"trialDays": 0,
"trialEndsAt": null,
"nextBillingAt": "2026-06-25T00:00:00.000Z",
"cancelAtPeriodEnd": false,
"canceledAt": null,
"externalId": "plano-pro-12345",
"gatewayRef": "gateway_subscription_id",
"checkoutUrl": "https://checkout.gateway.com/...",
"customer": {
"name": "Cliente Teste",
"email": "cliente@example.com",
"documentLast4": "1234"
},
"createdAt": "2026-05-25T12:00:00.000Z"
}| Campo | Quando aparece |
|---|---|
checkoutUrl | URL hospedada do gateway onde o cliente final insere o cartão. Redirecione para essa URL. |
trialEndsAt | ISO date: só presente quando trialDays > 0. |
nextBillingAt | ISO date: pode demorar a aparecer (depende do gateway confirmar o cartão). |
cancelAtPeriodEnd | true enquanto o cancelamento agendado não foi efetivado pelo gateway. |
Idempotência
Se você reenviar a mesma requisição com o mesmo externalId, a Allya retorna a assinatura existente em vez de criar uma duplicada. Não há 409 conflict por reenvio idempotente: o servidor retorna 201 com o mesmo recurso.
Período de trial
O comportamento de trialDays muda bastante entre os gateways. A Allya não esconde essas diferenças: cada gateway impõe as próprias regras e isso afeta o que você pode prometer ao cliente final.
| Gateway | Aceita trialDays > 0? | Como funciona |
|---|---|---|
| Abacate Pay | ✅ até 90 dias | O valor de trialDays é enviado para o produto recorrente criado internamente. trialDays > 90 retorna 400 invalid_input antes de chamar o gateway. |
| Asaas | ❌ | A Allya retorna 400 invalid_input quando trialDays > 0. Use 0. Motivo abaixo. |
| Pagar.me | ✅ via plano | O trial real é definido no campo trial_period_days do Plan criado em POST /plans no Pagar.me. O trialDays enviado para a Allya só preserva o status trialing no domínio local: não é repassado ao gateway. |
Por que o Asaas não aceita trial
O fluxo de assinatura no Asaas envia o cliente para um checkout hospedado pelo próprio Asaas (a invoiceUrl da primeira cobrança da assinatura). A API do Asaas documenta que, nesse fluxo, assim que o cliente final insere o cartão na página hospedada, a cobrança é capturada imediatamente: independentemente da nextDueDate configurada na assinatura.
Como a Allya nunca recebe os dados do cartão (eles vão direto para o Asaas), não há como adiar a captura: o trial pediria que a primeira cobrança esperasse N dias, mas o gateway captura na hora do tap. Em vez de devolver um trial que silenciosamente não acontece, a Allya rejeita trialDays > 0 no Asaas no momento da chamada.
Se você precisa de trial e quer continuar no Asaas, gerencie o trial fora da Allya: libere acesso ao cliente no seu sistema durante o trial sem criar a assinatura, e chame POST /v1/subscriptions apenas quando o trial expirar. Quando o PIX recorrente do Asaas entrar, o fluxo de trial poderá voltar usando outro caminho.
Trial no Pagar.me
Como o Plan no Pagar.me carrega o trial, mudar o tempo de trial significa criar um plano novo: a API não permite editar trial_period_days de um plano em uso. Exemplo de criação com trial:
curl -X POST https://api.pagar.me/core/v5/plans \
-u "sk_test_xxx:" \
-H "Content-Type: application/json" \
-d '{
"name": "Mensal Pro com trial",
"currency": "BRL",
"interval": "month",
"interval_count": 1,
"billing_type": "prepaid",
"payment_methods": ["credit_card"],
"trial_period_days": 7,
"items": [{ "name": "Pro", "quantity": 1, "pricing_scheme": { "price": 4990 } }]
}'Ao criar a assinatura na Allya, envie o mesmo trialDays para manter os campos trialEndsAt e o status trialing consistentes: mas tenha em mente que o que governa a cobrança é o plano.
Status normalizados
| Status | Significado |
|---|---|
trialing | Trial em andamento, sem cobrança real ainda. |
active | Cobrança em dia. |
past_due | A última cobrança falhou; o gateway pode estar retentando. |
canceled | Assinatura encerrada (imediata ou ao fim do período). |
Webhooks
Após criar, você recebe subscription.created. A cada ciclo bem-sucedido, subscription.renewed. Falhas de cobrança disparam subscription.payment_failed. No Pagar.me, o subscription.created público é emitido apenas quando o gateway confirmar a criação da assinatura real via webhook; a resposta inicial da API contém o checkout pl_.... Veja Webhooks.
Cada cobrança de ciclo também gera um Payment local com seus próprios eventos payment.*. Veja Status de assinatura para o significado de cada estado.
Abacate Pay
Para o Abacate Pay, a Allya cria internamente um produto recorrente antes de abrir o checkout de assinatura:
interval: "monthly"vira produto comcycle: "MONTHLY".interval: "yearly"vira produto comcycle: "ANNUALLY".trialDays, quando maior que zero, é enviado no produto recorrente, como exigido pelo gateway. Limite imposto pela Abacate Pay: 90 dias. Acima disso,400 invalid_inputantes da chamada.
O checkout de assinatura usa exatamente um item e retorna uma URL hospedada. Depois que o cliente conclui o checkout, a Abacate Pay envia webhooks subscription.*; a Allya usa esses eventos para trocar a referência inicial do checkout pelo ID real da assinatura no gateway.
Asaas
Para o Asaas, a Allya cria uma assinatura com billingType: "CREDIT_CARD" e sem dados sensíveis de cartão. Em seguida, consulta as cobranças da assinatura em /subscriptions/{id}/payments e retorna o invoiceUrl da primeira cobrança como checkoutUrl. É essa URL que o cliente final usa para informar o cartão no ambiente hospedado do Asaas.
As assinaturas do Asaas geram cobranças (PAYMENT_*) a cada ciclo. Quando o webhook da cobrança contém payment.subscription, a Allya cria ou atualiza um Payment local vinculado à assinatura, dispara o webhook de pagamento correspondente e sincroniza subscription.renewed ou subscription.payment_failed.
Trial não é suportado nesse fluxo: veja Por que o Asaas não aceita trial.
Pagar.me: plano obrigatório
Diferente do Abacate Pay e do Asaas (onde a Allya monta o plano inline a partir do request), o Pagar.me v5 exige que a recorrência referencie um Plan pré-cadastrado via POST /plans na API deles. O plan.id retornado é passado em gateway.pagarme.planId ao criar a assinatura pela Allya.
O fluxo completo, com exemplos e boas práticas, está em Plano obrigatório no Pagar.me.
Resumo: você cria o plano uma vez (na API do Pagar.me, com sk_test_… ou sk_live_…), guarda o plan.id, e passa em cada POST /v1/subscriptions quando o gateway roteado for Pagar.me. Quando o gateway é outro, o campo é ignorado: você pode passar sempre.
Limitações atuais
A versão atual cobre o caminho mais comum:
- Apenas cartão de crédito nos três gateways.
- PIX recorrente (suportado pelo Abacate Pay) está em breve.
- Débito recorrente (suportado pelo Asaas) não está exposto.
- Catálogo de planos, trocar cartão sem cancelar, upgrade/downgrade, proration, pause/resume e cupom ainda não estão disponíveis. Se precisar de algum destes hoje, fale com o time.
Veja também
- Plano obrigatório no Pagar.me: pré-requisito específico do gateway.
- Consultar assinatura: buscar por id ou externalId.
- Cancelar assinatura: imediato ou no fim do ciclo.
- Status de assinatura: estados normalizados.
- Webhooks: eventos
subscription.*e payloads.