Assinar um plano
Crie um plano e uma oferta de plano, assine um cliente no cartão e acompanhe a confirmação e as renovações.
Use este guia para cobrar um cliente de forma recorrente, a cada semana, mês ou ano.
Pela API, a assinatura é sempre no cartão de crédito. Assinaturas em PIX ou boleto só nascem no checkout da PagPolar. Veja Ciclo de vida da assinatura.
Para uma cobrança única, use Vender com cartão de crédito.
Visão geral
O diagrama mostra o caminho completo, da criação do plano até a renovação.
Antes de começar
- Um token de acesso. Veja Autenticação; os exemplos assumem a variável
accessToken. - A URL do webhook cadastrada na credencial. Veja Configurar o webhook.
Para testar sem cobrança real, use a chave de Homologação. Veja Ambiente de testes. Com a chave de Produção, o cartão é cobrado a cada ciclo: cancele a assinatura no final do teste.
Passo a passo
Crie o plano
O plano é o produto de assinatura. Ele guarda só o nome e a descrição.
curl -X POST "https://api.pagpolar.com/v1/plans" \
-H "Authorization: Bearer <SEU_TOKEN_DE_ACESSO>" \
-H "Content-Type: application/json" \
-d '{
"name": "Clube de exemplo",
"description": "Plano criado pelo guia de assinatura"
}'const response = await fetch('https://api.pagpolar.com/v1/plans', {
method: 'POST',
headers: {
Authorization: `Bearer ${accessToken}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
name: 'Clube de exemplo',
description: 'Plano criado pelo guia de assinatura',
}),
});
console.log(response.status, await response.json());| Campo | Obrigatório | O que é |
|---|---|---|
name | Sim | Nome do plano, até 255 caracteres. |
description | Não | Descrição do plano, até 5000 caracteres. |
Resposta 201 (resumida):
{
"data": {
"id": "b7e1c2d3-4f5a-4b6c-8d7e-9f0a1b2c3d4e",
"name": "Clube de exemplo",
"type": "SUBSCRIPTION",
"is_active": true
}
}Guarde data.id. Ele é o <ID_DO_PLANO> do próximo passo. Contrato completo: POST /plans.
Crie a oferta de plano
A oferta de plano define o preço e de quanto em quanto tempo o cliente é cobrado. price é em centavos: 1990 = R$ 19,90.
curl -X POST "https://api.pagpolar.com/v1/plans/<ID_DO_PLANO>/offers" \
-H "Authorization: Bearer <SEU_TOKEN_DE_ACESSO>" \
-H "Content-Type: application/json" \
-d '{
"title": "Mensal",
"price": 1990,
"cycle": "MONTHLY",
"cycle_interval": 1,
"is_enabled_credit_card": true,
"max_credit_card_installments": 1
}'const response = await fetch('https://api.pagpolar.com/v1/plans/<ID_DO_PLANO>/offers', {
method: 'POST',
headers: {
Authorization: `Bearer ${accessToken}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
title: 'Mensal',
price: 1990,
cycle: 'MONTHLY',
cycle_interval: 1,
is_enabled_credit_card: true,
max_credit_card_installments: 1,
}),
});
console.log(response.status, await response.json());| Campo | Obrigatório | O que é |
|---|---|---|
price | Sim | Valor de cada ciclo, em centavos, número inteiro a partir de 0. |
cycle | Sim | Unidade do ciclo: WEEKLY, MONTHLY ou YEARLY. |
cycle_interval | Não | A cada quantas unidades de cycle o cliente é cobrado. Número inteiro, mínimo 1. MONTHLY com 3 cobra a cada 3 meses. |
title | Não | Nome da oferta, até 255 caracteres. |
is_enabled_credit_card | Não | Cartão na oferta. Começa ligado. A assinatura pela API precisa dele ligado. |
max_credit_card_installments | Não | Só aceita 1. Sem o campo, a API usa 1. |
is_active | Não | Sem o campo, a oferta nasce ativa. |
Abaixo de R$ 5,00, a oferta de plano fica sem cartão
Ao salvar, a API desliga o cartão quando price fica abaixo de 500 (R$ 5,00), mesmo com is_enabled_credit_card: true. Com o cartão desligado, a assinatura pela API responde 409 com Método de pagamento CREDIT_CARD não habilitado para esta oferta. Confira payment_methods na resposta. Na edição com PATCH /plan-offers/{id}, a mesma regra recalcula os meios. Veja Meios de pagamento e valor mínimo.
Envie cycle_interval
A API não define um valor padrão para cycle_interval. Sem o campo, a oferta fica com cycle_interval: null. Envie 1 para cobrar a cada semana, mês ou ano.
Resposta 201 (resumida):
{
"data": {
"id": "c8f2d3e4-5a6b-4c7d-9e8f-0a1b2c3d4e5f",
"identifier": "PPP1234567890",
"title": "Mensal",
"price": 19.9,
"product_id": "b7e1c2d3-4f5a-4b6c-8d7e-9f0a1b2c3d4e",
"payment_methods": {
"credit_card": true
},
"max_credit_card_installments": 1,
"cycle": "MONTHLY",
"cycle_interval": 1
}
}Na resposta, price volta em reais: 19.9 é R$ 19,90. Veja Valores nas respostas.
Guarde data.identifier. Ele é o <CODIGO_DA_OFERTA> do próximo passo. O data.id também funciona no lugar do código. Contrato completo: POST /plans/{id}/offers.
Assine o cliente
Envie o código da oferta de plano na URL e os dados do cliente e do cartão no corpo. Envie também o header Idempotency-Key, um valor único para esta assinatura. Veja Idempotência.
curl -X POST "https://api.pagpolar.com/v1/plans/offer/<CODIGO_DA_OFERTA>/subscribe" \
-H "Authorization: Bearer <SEU_TOKEN_DE_ACESSO>" \
-H "Idempotency-Key: 8d2f4a6b-1c3e-4f5a-9b7c-2d4e6f8a0b1c" \
-H "Content-Type: application/json" \
-d '{
"installments": 1,
"external_reference": "ASSINATURA-0001",
"customer": {
"name": "Maria Silva",
"email": "cliente@exemplo.com",
"document": "<CPF_DO_CLIENTE>",
"phone": "<TELEFONE_DO_CLIENTE>"
},
"credit_card": {
"holder_name": "MARIA SILVA",
"holder_document": "<CPF_DO_TITULAR>",
"number": "<NUMERO_DO_CARTAO>",
"expiration_month": 12,
"expiration_year": 2030,
"cvv": "<CVV>"
}
}'import { randomUUID } from 'node:crypto';
const response = await fetch(
'https://api.pagpolar.com/v1/plans/offer/<CODIGO_DA_OFERTA>/subscribe',
{
method: 'POST',
headers: {
Authorization: `Bearer ${accessToken}`,
'Idempotency-Key': randomUUID(),
'Content-Type': 'application/json',
},
body: JSON.stringify({
installments: 1,
external_reference: 'ASSINATURA-0001',
customer: {
name: 'Maria Silva',
email: 'cliente@exemplo.com',
document: '<CPF_DO_CLIENTE>',
phone: '<TELEFONE_DO_CLIENTE>',
},
credit_card: {
holder_name: 'MARIA SILVA',
holder_document: '<CPF_DO_TITULAR>',
number: '<NUMERO_DO_CARTAO>',
expiration_month: 12,
expiration_year: 2030,
cvv: '<CVV>',
},
}),
},
);
console.log(response.status, await response.json());| Campo | Obrigatório | O que é |
|---|---|---|
installments | Sim | Número de parcelas. Envie 1: a oferta de plano criada pela API aceita no máximo 1. |
external_reference | Não | Código da assinatura no seu sistema, até 255 caracteres. Fica gravado na venda do primeiro ciclo. |
Dados do cliente:
| Campo | Obrigatório | O que é |
|---|---|---|
customer.name | Sim | Nome do cliente, até 255 caracteres. |
customer.email | Sim | E-mail válido do cliente. |
customer.document | Sim | CPF ou CNPJ do cliente, com ou sem pontuação. Veja o formato em Documento e telefone. |
customer.phone | Sim | Telefone do cliente com DDD, com ou sem pontuação. Veja o formato em Documento e telefone. |
Dados do cartão:
| Campo | Obrigatório | O que é |
|---|---|---|
credit_card.holder_name | Sim | Nome impresso no cartão. |
credit_card.holder_document | Sim | CPF ou CNPJ do titular do cartão, com ou sem pontuação. Veja o formato em Documento e telefone. |
credit_card.number | Sim | Número do cartão. A API confere se o número é válido antes de enviar. |
credit_card.expiration_month | Sim | Mês de validade, número de 1 a 12. |
credit_card.expiration_year | Sim | Ano de validade com 4 dígitos, número. |
credit_card.cvv | Sim | Código de segurança, texto com 3 ou 4 caracteres. |
No ambiente de testes, use os cartões de Comprar no ambiente de testes.
Campos opcionais:
| Campo | Obrigatório | O que é |
|---|---|---|
address | Não | Endereço do cliente. Se enviar, street, number, neighborhood, city, state e postal_code são obrigatórios. complement é opcional. |
affiliate_identifier | Não | Código do afiliado que indicou a venda. Veja Vender com afiliado. |
buyer_ip | Não | IP do cliente. Se não enviar, a API usa o IP de quem chamou, ou seja, o do seu servidor. |
buyer_user_agent | Não | Navegador do cliente, até 512 caracteres. Se não enviar, a API usa o header User-Agent da sua requisição. |
Resposta 201 (resumida):
{
"data": {
"id": "e1f2a3b4-c5d6-4e7f-8a9b-0c1d2e3f4a5b",
"status": "DRAFT",
"payment_method": "CREDIT_CARD",
"start_at": "2026-09-15T13:00:00.000Z",
"end_at": null,
"next_billing_at": null,
"canceled_at": null,
"cycle_limit": null,
"created_at": "2026-09-15T13:00:00.000Z"
}
}Guarde data.id, o id da assinatura, junto do cliente. Você usa esse valor para consultar e cancelar.
A resposta também traz customer, offer e product. Contrato completo: POST /plans/offer/{id}/subscribe.
201 não quer dizer cartão aprovado
A PagPolar só envia a assinatura ao gateway depois de responder. Por isso a resposta é sempre 201 com status: DRAFT, mesmo quando o cartão vai ser recusado. Não libere o acesso ainda. O resultado chega no próximo passo.
Se a requisição não tiver resposta, repita com a mesma Idempotency-Key. Veja O que acontece ao repetir. Para achar a venda do primeiro ciclo pela sua referência, use GET /sales?external_reference=ASSINATURA-0001.
Espere a confirmação do gateway
A sua URL recebe os eventos abaixo. Depois dos dois primeiros, a PagPolar cria a assinatura no gateway e chega só um dos dois últimos.
| Evento | Quando é enviado | O que fazer |
|---|---|---|
TRANSACTION_CREATED | Logo depois da criação, com a venda do primeiro ciclo (transaction.cycle: 1). | Registre a venda. |
SUBSCRIPTION_CREATED | Logo depois da criação, com a assinatura em DRAFT. | Registre a assinatura. Não libere o acesso. |
SUBSCRIPTION_CONFIRMED | O gateway aceitou a assinatura. O status continua DRAFT e external_id vem preenchido. | Guarde external_id se precisar. Ainda não libere o acesso. |
SUBSCRIPTION_FAILED | O gateway recusou a criação. Status FAILED. A venda do primeiro ciclo também fica FAILED, sem evento de venda próprio. | Não libere o acesso. Peça outro cartão ao cliente e crie uma assinatura nova, com outra Idempotency-Key. |
Exemplo de SUBSCRIPTION_CONFIRMED (resumido):
{
"id": "3c4d5e6f-7a8b-4c9d-0e1f-2a3b4c5d6e7f",
"event": "SUBSCRIPTION_CONFIRMED",
"creation_date": "2026-09-15T13:00:20.000Z",
"version": "1.0.0",
"data": {
"subscription": {
"id": "e1f2a3b4-c5d6-4e7f-8a9b-0c1d2e3f4a5b",
"external_id": "sub_abc123",
"status": "DRAFT",
"payment_method": "CREDIT_CARD"
},
"source": {
"channel": "API",
"api_credential_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7"
}
}
}O webhook da credencial também recebe as assinaturas vendidas no checkout, com data.source.channel: CHECKOUT. Veja Canal da venda.
Um SUBSCRIPTION_CONFIRMED pode chegar antes do SUBSCRIPTION_CREATED: use o status que veio no evento. Veja Processar eventos sem duplicar.
Libere o acesso quando a assinatura ficar ACTIVE
A assinatura fica ACTIVE quando o gateway avisa que a primeira cobrança foi paga.
Nenhum evento de assinatura avisa essa ativação. Para confirmar, consulte a assinatura:
curl "https://api.pagpolar.com/v1/subscriptions/e1f2a3b4-c5d6-4e7f-8a9b-0c1d2e3f4a5b" \
-H "Authorization: Bearer <SEU_TOKEN_DE_ACESSO>"const response = await fetch(
'https://api.pagpolar.com/v1/subscriptions/e1f2a3b4-c5d6-4e7f-8a9b-0c1d2e3f4a5b',
{ headers: { Authorization: `Bearer ${accessToken}` } },
);
console.log(response.status, await response.json());Resposta 200 (resumida):
{
"data": {
"id": "e1f2a3b4-c5d6-4e7f-8a9b-0c1d2e3f4a5b",
"status": "ACTIVE",
"payment_method": "CREDIT_CARD",
"next_billing_at": "2026-10-15T13:00:00.000Z"
}
}Com ACTIVE, libere o acesso. O que fazer em cada status está em Ciclo de vida.
Consulte com moderação: a consulta conta no limite geral da credencial. Veja Limites de requisição. Contrato completo: GET /subscriptions/{id}.
Acompanhe as renovações
A cada ciclo, o gateway cobra o cartão sozinho. Você não precisa chamar a API.
Quando a cobrança de um ciclo, a partir do segundo, é paga, chega SUBSCRIPTION_RENEWED:
- a assinatura continua
ACTIVE; subscription.next_billing_attraz a data da próxima cobrança;- cada ciclo gera uma venda nova, com
idpróprio.
Mantenha o acesso e atualize a data da próxima cobrança no seu sistema.
Se o gateway informar uma cobrança pendente, a assinatura fica PROCESSING, sem evento próprio. Quando a cobrança é paga, ela volta para ACTIVE.
Limite de ciclos
cycle_limit mostra quantos ciclos a assinatura cobra no máximo. null quando não há limite. A criação de oferta de plano pela API não aceita esse limite.
Ciclo de vida
A tabela resume os status que aparecem neste guia. Todos os status e transições estão em Ciclo de vida da assinatura.
| Status | Significado | O que fazer |
|---|---|---|
DRAFT | Assinatura registrada. O gateway ainda não confirmou ou a primeira cobrança ainda não foi paga. | Não libere o acesso. Consulte de novo mais tarde. |
FAILED | O gateway recusou a criação. | Não libere o acesso. Crie uma assinatura nova. |
ACTIVE | A cobrança do ciclo foi paga. | Libere ou mantenha o acesso. next_billing_at mostra a data da próxima cobrança. |
PROCESSING | O gateway informou uma cobrança pendente. | Espere. |
Eventos de webhook deste fluxo
Os eventos da criação estão em Espere a confirmação do gateway. Nas renovações chega SUBSCRIPTION_RENEWED: veja Acompanhe as renovações.
Quando algo dá errado
Além dos erros comuns a todas as rotas, este fluxo pode responder:
| Passo | Situação | Resposta | Como resolver |
|---|---|---|---|
| 1 e 2 | Campo obrigatório ausente, ou cycle fora da lista, como cycle: DAILY. | 400 invalid_request. A message traz o campo, como O campo price é obrigatório ou Para o campo cycle os valores permitidos são [WEEKLY,MONTHLY,YEARLY]. Sem name no passo 1, a mensagem é O campo nome é obrigatório. | Corrija o campo indicado em message. |
| 2 | price com casas decimais, como 19.9. | 400 invalid_request | price é em centavos e só aceita inteiro. Envie 1990. |
| 2 | price negativo ou cycle_interval menor que 1, como 0. | 400 invalid_request com message vazia | Envie price em centavos a partir de 0 e cycle_interval inteiro a partir de 1. |
| 2 | <ID_DO_PLANO> errado, de outra conta ou de um produto que não é plano. | 404 com Plano não encontrado | Use o data.id do passo 1. |
| 2 | max_credit_card_installments maior que 1. | 400 com Ofertas de plano não podem ser parceladas no cartão de crédito (máximo de 1x) | Envie 1 ou não envie o campo. |
| 3 | Dados do cartão inválidos, como número de cartão inválido, mês 13 ou ano fora de 2000 a 2100. | 400 invalid_request, em geral com message vazia | Confira os campos na tabela do passo 3. |
| 3 | Código da oferta errado, de outra conta, de uma oferta oculta ou de uma oferta que não é de plano. | 404 com Oferta de plano não encontrada | Use o data.identifier do passo 2. |
| 3 | Oferta de plano desativada. | 409 com Oferta inativa | Ative a oferta ou use outra. |
| 3 | Oferta de plano com data de expiração vencida. | 409 com Oferta expirada | Use outra oferta. |
| 3 | Cartão desligado na oferta de plano. | 409 com Método de pagamento CREDIT_CARD não habilitado para esta oferta | Ligue is_enabled_credit_card na oferta e confira o valor mínimo. |
| 3 | installments maior que o máximo da oferta. | 400 com Número de parcelas acima do permitido para esta oferta (máximo 1) | Envie installments: 1. |
| 4 | O gateway recusou a assinatura. | SUBSCRIPTION_FAILED, status FAILED | Peça outro cartão e crie uma assinatura nova com outra Idempotency-Key. |
| 5 | Id da assinatura errado ou de outra conta. | 404 com Assinatura não encontrada | Use o data.id do passo 3. |
Confira no painel
O plano aparece em Meus produtos → Assinaturas. Na aba Ofertas e Configurações ficam as ofertas de plano, com o código, o preço por ciclo, os meios de pagamento e as parcelas:

Cada cobrança da assinatura vira uma venda em Vendas → Minhas vendas. Esta é a venda do primeiro ciclo, paga no cartão:
