# PagPolar Docs > Documentação da API pública da PagPolar > Tamanho aproximado: 168.881 tokens. --- # Documentação da API PagPolar URL: https://staging.pagpolar.com/docs > Guias passo a passo, a referência de cada operação e os avisos de webhook para integrar a sua loja à PagPolar. Esta é a página inicial do portal. Ela lista, em quatro blocos, tudo o que a documentação cobre: o que vale para toda a API, o passo a passo de cada objetivo, o contrato de cada operação e os avisos que a PagPolar envia ao seu servidor. Para começar a integrar, vá para o [Início rápido](/docs/guias/inicio-rapido). Para descobrir qual rota chamar para um objetivo, abra [Onde começar?](/docs/guias). ## Arquivos para agentes de IA Todos são públicos e não precisam de chave. * [Índice do portal em texto](/docs/llms.txt) — o endereço e o resumo de cada página. * [Conteúdo inteiro do portal em texto](/docs/llms-full.txt) — tudo num arquivo só. * [OpenAPI da API pública](/docs/openapi.json) — todas as operações, parâmetros, corpos, respostas e erros (OpenAPI 3.0). * [OpenAPI dos webhooks](/docs/webhooks.json) — o formato de cada evento (OpenAPI 3.1). * [Como entregar a documentação a um assistente](/docs/guias/para-agentes-de-ia). Qualquer página pode ser lida em markdown acrescentando `.mdx` ao endereço ou enviando o header `Accept: text/markdown`. ## Fundamentos O que vale para toda a API: endereço, token, erros, repetição segura e formato dos valores. * [Ambientes e URL base](/docs/guias/fundamentos/ambientes) — o endereço da API e o que muda entre Produção e Homologação. * [Autenticação](/docs/guias/fundamentos/autenticacao) — troque a chave pelo token e envie em `Authorization`. * [Credenciais da API](/docs/guias/fundamentos/credenciais) — crie, restrinja por IP e revogue credenciais. * [Erros](/docs/guias/fundamentos/erros) — o formato do erro, o que pode ser repetido e o `request_id`. * [Idempotência](/docs/guias/fundamentos/idempotencia) — repita uma cobrança sem cobrar o cliente duas vezes. * [Limites de requisição](/docs/guias/fundamentos/limites-de-requisicao) — quantas chamadas por minuto e o que fazer no 429. * [Paginação e filtros](/docs/guias/fundamentos/paginacao-e-filtros) — percorra qualquer listagem do começo ao fim. * [Valores, datas e identificadores](/docs/guias/fundamentos/valores-datas-e-identificadores) — centavos, fuso e os tipos de identificador. * [Glossário](/docs/guias/fundamentos/glossario) — o termo e o campo da API que corresponde a ele. ## Jornadas O caminho completo de cada objetivo, da primeira chamada ao aviso de webhook. * [Criar produto e oferta](/docs/guias/jornadas/criar-produto-e-oferta) — cadastre o produto e a oferta com preço, meios de pagamento e parcelas. * [Vender com PIX ou boleto](/docs/guias/jornadas/vender-com-pix-ou-boleto) — cobre, mostre o código de pagamento e confirme pelo webhook. * [Vender com cartão de crédito](/docs/guias/jornadas/vender-com-cartao) — cobre à vista ou parcelado e saiba se foi aprovado ou recusado. * [Vender um produto físico](/docs/guias/jornadas/vender-um-produto-fisico) — consulte o frete pelo CEP, cobre com o endereço e acompanhe o pedido. * [Vender uma assinatura](/docs/guias/jornadas/assinar-um-plano) — crie o plano, assine o cliente no cartão e acompanhe as renovações. * [Acompanhar reembolsos](/docs/guias/jornadas/acompanhar-reembolsos-e-chargebacks) — receba o pedido, consulte na API e reaja ao estorno e ao chargeback. * [Conciliar vendas](/docs/guias/jornadas/conciliar-vendas) — baixe as vendas e assinaturas de um período sem perder nem duplicar. Todas as jornadas estão listadas em [Onde começar?](/docs/guias). ## Referência da API O contrato de cada operação: caminho, parâmetros, corpo, respostas e erros. * [Autenticação](/docs/referencia/autenticacao) — troque a chave pelo token de acesso e confira a credencial. * [Vendas](/docs/referencia/vendas) — cobre por PIX, boleto ou cartão e consulte as vendas. * [Produtos e ofertas](/docs/referencia/produtos) — crie e altere produtos e as ofertas com preço e parcelas. * [Planos e assinaturas](/docs/referencia/planos) — planos, ofertas de plano, troca de plano e troca de cartão. * [Reembolsos](/docs/referencia/reembolsos) — liste os pedidos de reembolso e peça o reembolso de uma venda. * [Entidades](/docs/referencia/entidades) — os campos de cada objeto que a API devolve ou recebe. ## Webhooks Os avisos que a PagPolar envia ao seu servidor quando algo muda. * [Visão geral](/docs/webhooks) — como o aviso chega, o que ele traz e quando usar. * [Playground](/docs/webhooks/playground) — monte um evento de teste, edite o JSON e envie para o seu webhook. * [Configurar o webhook](/docs/webhooks/configurar) — cadastre a URL, escolha os eventos e entenda o token. * [Formato do evento](/docs/webhooks/formato-do-evento) — o envelope e cada bloco de dados do aviso. * [Processar sem duplicar](/docs/webhooks/processar-sem-duplicar) — trate repetições, ordem fora de sequência e envios de teste. * [Catálogo de eventos](/docs/webhooks/eventos) — os 15 eventos, quando cada um dispara e o que fazer. --- # Onde começar? URL: https://staging.pagpolar.com/docs/guias > Veja o que a API faz, o que você precisa antes de integrar e quais rotas e eventos usar em cada objetivo. > **É um agente de IA? Comece por aqui** > > Leia [/docs/llms.txt](/docs/llms.txt): é o índice desta documentação em texto, com o endereço e o resumo de cada página. O contrato completo da API está em [/docs/openapi.json](/docs/openapi.json) (OpenAPI 3.0) e o dos webhooks em [/docs/webhooks.json](/docs/webhooks.json) (OpenAPI 3.1). Qualquer página pode ser lida em markdown acrescentando `.mdx` ao endereço ou enviando o header `Accept: text/markdown`. A API da PagPolar deixa o seu sistema vender sem passar pelo checkout da PagPolar. Com ela você: * cria produtos, ofertas, planos e ofertas de plano; * cobra por PIX, boleto ou cartão de crédito; * assina um cliente em um plano, com cobrança no cartão; * consulta vendas, pedidos de reembolso, assinaturas e clientes; * recebe avisos no seu servidor quando uma venda ou uma assinatura muda (webhooks). ## Antes de começar Você precisa de três coisas: 1. **Uma conta de vendedor na PagPolar** com o menu **Configurações → API** no painel. É nessa tela que você cria a credencial. 2. **Um servidor seu** para chamar a API. Veja [Chame a API do seu servidor](/docs/guias/fundamentos/ambientes#servidor). 3. **Uma URL pública** no seu servidor para receber os webhooks. Comece pela chave de Homologação, que leva as chamadas para o [ambiente de testes](/docs/guias/fundamentos/ambientes#ambiente-de-testes), e troque pela chave de Produção quando a integração estiver pronta. ## Qual rota chamar Cada objetivo abaixo lista as operações na ordem em que você as chama e o evento de webhook que avisa o resultado. Antes de qualquer uma delas, [obtenha o token de acesso](/docs/guias/fundamentos/autenticacao#obter-o-token). O passo a passo de cada objetivo está no guia da jornada indicado em cada seção. ## Escolha pelo tipo de cobrança ```mermaid flowchart TD A[O que você quer cobrar?] --> B{Pagamento único ou recorrente?} B -->|Único| C{Qual meio de pagamento?} C -->|PIX| D[POST /payments/pix] C -->|Boleto| E[POST /payments/boleto] C -->|Cartão| F[POST /payments/credit-card] B -->|Recorrente| G[POST /plans/offer/ID/subscribe, só cartão] ``` ## Vender um produto avulso 1. Crie o produto: [`POST /products`](/docs/referencia/produtos/create-product). Veja [Criar produto e oferta](/docs/guias/jornadas/criar-produto-e-oferta). 2. Crie a oferta, com preço e [meios de pagamento](/docs/guias/jornadas/criar-produto-e-oferta#meios-de-pagamento): [`POST /offers`](/docs/referencia/ofertas/create-offer). Se preferir, [informe a oferta na hora da venda](/docs/guias/conceitos/ofertas-planos-e-ofertas-ocultas). 3. Cobre o cliente: * PIX: [`POST /payments/pix`](/docs/referencia/vendas/create-pix-payment); * boleto: [`POST /payments/boleto`](/docs/referencia/vendas/create-boleto-payment); * cartão: [`POST /payments/credit-card`](/docs/referencia/vendas/create-credit-card-payment). 4. Espere o aviso de pagamento: [`TRANSACTION_PAID`](/docs/webhooks/eventos/transaction-paid). Se o produto é físico, a cobrança exige o endereço e a opção de frete, consultada antes em [`GET /offers/{identifier}/shipping`](/docs/referencia/ofertas/list-offer-shipping). Veja [Vender um produto físico](/docs/guias/jornadas/vender-um-produto-fisico). Guias: [Início rápido](/docs/guias/inicio-rapido), [Vender com PIX ou boleto](/docs/guias/jornadas/vender-com-pix-ou-boleto), [Vender com cartão de crédito](/docs/guias/jornadas/vender-com-cartao) e [Vender com afiliado](/docs/guias/jornadas/vender-com-afiliado). ## Vender uma assinatura Pela API, a assinatura é sempre cobrada no cartão de crédito. 1. Crie o plano: [`POST /plans`](/docs/referencia/planos/create-plan). 2. Crie a oferta de plano, com preço e ciclo: [`POST /plans/{id}/offers`](/docs/referencia/planos/create-plan-offer). 3. Assine o cliente: [`POST /plans/offer/{id}/subscribe`](/docs/referencia/assinaturas/create-subscription). 4. Acompanhe o status pelos webhooks e por [`GET /subscriptions/{id}`](/docs/referencia/assinaturas/get-subscription). Quando liberar o acesso está em [Ciclo de vida da assinatura](/docs/guias/conceitos/ciclo-de-vida-da-assinatura). 5. Para cancelar: [`DELETE /subscriptions/{id}`](/docs/referencia/assinaturas/cancel-subscription). Guias: [Assinar um plano](/docs/guias/jornadas/assinar-um-plano), [Cancelar assinatura](/docs/guias/jornadas/cancelar-assinatura), [Trocar de plano](/docs/guias/jornadas/trocar-de-plano) e [Trocar o cartão da assinatura](/docs/guias/jornadas/trocar-cartao-da-assinatura). ## Acompanhar o que acontece depois da venda * Receba os avisos no seu servidor: [Visão geral dos webhooks](/docs/webhooks). * Veja cada status da venda e o evento que avisa: [Ciclo de vida da venda](/docs/guias/conceitos/ciclo-de-vida-da-venda). * Liste os pedidos de reembolso: [`GET /refunds`](/docs/referencia/reembolsos/list-refunds). Veja [Acompanhar reembolsos e chargebacks](/docs/guias/jornadas/acompanhar-reembolsos-e-chargebacks). ## Conferir e conciliar dados * Liste as vendas de um período: [`GET /sales`](/docs/referencia/vendas/list-sales). Veja [Paginação e filtros](/docs/guias/fundamentos/paginacao-e-filtros). * Consulte uma venda pelo id, pelo código ou pela sua referência do pedido: [`GET /sales/{identifier}`](/docs/referencia/vendas/get-sale). Veja [Como a venda é encontrada](/docs/guias/fundamentos/valores-datas-e-identificadores#como-a-venda-e-encontrada). * Liste as assinaturas: [`GET /subscriptions`](/docs/referencia/assinaturas/list-subscriptions). * Liste os clientes: [`GET /customers`](/docs/referencia/clientes/list-customers). Guia: [Conciliar vendas e assinaturas](/docs/guias/jornadas/conciliar-vendas). --- # Início rápido URL: https://staging.pagpolar.com/docs/guias/inicio-rapido > Crie uma credencial, obtenha o token de acesso e faça a sua primeira venda PIX pela API. Ao final deste guia você terá: * uma credencial com chave de API e webhook; * um token de acesso para autenticar as chamadas; * um produto e uma oferta criados pela API; * uma venda PIX com o código "copia e cola" para o cliente pagar. > **Este guia usa a chave de Homologação** > > Com a chave de Homologação, todas as chamadas vão para o [ambiente de testes](/docs/guias/fundamentos/ambientes#ambiente-de-testes). O produto, a oferta e a venda ficam só lá, e o PIX do passo 7 é aprovado sozinho cerca de 30 segundos depois de criado. Veja [Comprar no ambiente de testes](/docs/guias/fundamentos/ambientes#dados-de-teste). ## Visão geral O diagrama mostra os passos deste guia, na ordem. ```mermaid sequenceDiagram autonumber participant V as Você no painel participant S as Seu servidor participant A as API PagPolar participant W as Seu servidor de webhook V->>A: cria a credencial com nome, ambiente e URL do webhook A-->>V: chave de API e token do webhook, uma única vez S->>A: POST /auth/token com X-API-Key A-->>S: 200 com access_token válido por 24 horas S->>A: GET /me com o token no header Authorization A-->>S: 200 com os dados da credencial S->>A: POST /products e POST /offers A-->>S: 201 com o id do produto e o código da oferta S->>A: POST /payments/pix com Idempotency-Key A-->>S: 201 com transactions e pix.qr_code A-)W: TRANSACTION_CREATED ``` ## Antes de começar * Uma conta de vendedor na PagPolar com o menu **Configurações → API**. * Um terminal com `curl`, ou Node.js 18 ou mais novo. Os exemplos em Node.js usam `await` direto no arquivo: salve o código em um arquivo `.mjs` e rode com `node arquivo.mjs`. * Uma URL pública no seu servidor para receber os webhooks. 1. **Crie a credencial no painel** 1. No painel da PagPolar, abra **Configurações → API**. 2. Clique em **Nova chave**. Abre o painel lateral **Criar nova chave de integração**. 3. Preencha os campos: | Campo | O que colocar | | ---------------------- | ---------------------------------------------------------------------------------------------- | | **Nome da integração** | Um nome para você reconhecer a credencial, como `Loja virtual`. | | **Ambiente** | **Homologação**. Não dá para mudar depois. Cada conta pode ter uma chave de Homologação ativa. | | **URL do webhook** | A URL do seu servidor que vai receber os avisos. | | **Eventos** | Deixe em branco para receber todos os eventos. | | **IPs autorizados** | Deixe em branco neste teste. Assim, qualquer IP é aceito. | A imagem mostra o ambiente **Produção**, o valor inicial do campo. Troque para **Homologação**. 4. Clique em **Salvar**. A PagPolar começa a preparar a sua conta de testes. Na lista de **Configurações → API**, a chave aparece com a etiqueta **Preparando ambiente de testes**. Atualize a tela até a etiqueta mudar para **Ambiente de testes pronto** antes de seguir para o passo 3. Veja [A chave de Homologação](/docs/guias/fundamentos/credenciais#homologacao). 2. **Guarde a chave e o token do webhook** Depois de salvar, abre a janela **Chave criada com sucesso**. Ela mostra dois valores: | Valor | Para que serve | | ---------------- | ------------------------------------------------------------------------------------------------------------------------------- | | Chave de API | Vai no header `X-API-Key` de `POST /auth/token`, a rota que devolve o token de acesso. É o único lugar em que a chave é aceita. | | Token do webhook | Chega no header `Authorization` de cada webhook, para você confirmar que o aviso é da PagPolar. | Copie os dois e guarde no seu servidor, em variáveis de ambiente ou em um cofre de segredos. > **Os dois valores aparecem uma única vez** > > Depois de fechar a janela, não há como ver a chave nem o token de novo. Se perder algum dos dois, revogue a credencial e crie outra. 3. **Troque a chave pelo token de acesso** `POST /auth/token` é a única rota que recebe a chave. Ela devolve o token que autentica todas as outras chamadas. A requisição não tem corpo. #### cURL ```bash curl -X POST "https://api.pagpolar.com/v1/auth/token" \ -H "X-API-Key: " ``` #### Node.js ```js const apiUrl = 'https://api.pagpolar.com/v1'; async function obterToken() { const response = await fetch(`${apiUrl}/auth/token`, { method: 'POST', headers: { 'X-API-Key': '' }, }); const { data } = await response.json(); return data.access_token; } const accessToken = await obterToken(); console.log(accessToken); ``` Resposta `200`: ```json { "data": { "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", "token_type": "Bearer", "expires_in": 86400 } } ``` Guarde `data.access_token` em memória. Ele é o valor que vai no header `Authorization` dos próximos passos. Os exemplos em Node.js daqui em diante usam a variável `accessToken` criada acima. Quando ele expirar, peça outro: veja [Validade](/docs/guias/fundamentos/autenticacao#validade) e [Renove o token quando receber 401](/docs/guias/fundamentos/autenticacao#renovar-no-401). 4. **Confirme o token com ** `GET /me` Chame `GET /me` com o token no header `Authorization`: #### cURL ```bash curl "https://api.pagpolar.com/v1/me" \ -H "Authorization: Bearer " ``` #### Node.js ```js const response = await fetch('https://api.pagpolar.com/v1/me', { headers: { Authorization: `Bearer ${accessToken}` }, }); console.log(response.status, await response.json()); ``` Se o token estiver certo, a resposta é `200`: ```json { "data": { "credential_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7", "environment": "STAGING", "rate_limit_per_minute": 120 } } ``` `environment: STAGING` confirma que a chamada foi atendida pelo ambiente de testes. Se a resposta for `401`, veja [Autenticação](/docs/guias/fundamentos/autenticacao). 5. **Crie um produto** #### cURL ```bash curl -X POST "https://api.pagpolar.com/v1/products" \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "name": "Curso de exemplo", "description": "Produto criado no início rápido" }' ``` #### Node.js ```js const response = await fetch('https://api.pagpolar.com/v1/products', { method: 'POST', headers: { Authorization: `Bearer ${accessToken}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ name: 'Curso de exemplo', description: 'Produto criado no início rápido', }), }); console.log(response.status, await response.json()); ``` Resposta `201` (resumida): ```json { "data": { "id": "c4d5e6f7-a8b9-4c0d-1e2f-3a4b5c6d7e8f", "name": "Curso de exemplo", "type": "DIGITAL", "is_active": true } } ``` Guarde `data.id`. Ele é o `` do próximo passo. Contrato completo: [`POST /products`](/docs/referencia/produtos/create-product). 6. **Crie uma oferta** A oferta define o preço e os meios de pagamento. `price` vai em **centavos**, como número inteiro (`1000` = R$ 10,00), e volta em **reais** na resposta (`10`). Veja [Valores nas respostas](/docs/guias/fundamentos/valores-datas-e-identificadores#valores-nas-respostas). #### cURL ```bash curl -X POST "https://api.pagpolar.com/v1/offers" \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "product_id": "", "title": "Oferta de lançamento", "price": 1000, "is_enabled_pix": true, "is_enabled_credit_card": false, "is_enabled_billet": false, "max_credit_card_installments": 1 }' ``` #### Node.js ```js const response = await fetch('https://api.pagpolar.com/v1/offers', { method: 'POST', headers: { Authorization: `Bearer ${accessToken}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ product_id: '', title: 'Oferta de lançamento', price: 1000, is_enabled_pix: true, is_enabled_credit_card: false, is_enabled_billet: false, max_credit_card_installments: 1, }), }); console.log(response.status, await response.json()); ``` Resposta `201` (resumida): ```json { "data": { "id": "d5e6f7a8-b9c0-4d1e-2f3a-4b5c6d7e8f9a", "identifier": "PPP1234567890", "title": "Oferta de lançamento", "price": 10, "payment_methods": { "pix": true, "credit_card": false, "billet": false }, "max_credit_card_installments": 1 } } ``` Guarde `data.identifier`. Ele é o `` do próximo passo. Contrato completo: [`POST /offers`](/docs/referencia/ofertas/create-offer). > **Por que enviar max_credit_card_installments** > > Sem esse campo, a oferta vale 12 parcelas, e R$ 10,00 em 12 parcelas fica abaixo da [parcela mínima](/docs/guias/jornadas/criar-produto-e-oferta#parcela-minima): a criação responde `400`. Com `1`, a oferta passa. 7. **Crie a venda PIX** Envie o código da oferta e os dados do cliente. No ambiente de testes, `customer.document` pode ser qualquer CPF válido. Três headers são obrigatórios: | Header | Valor | | ----------------- | ----------------------------------------------------------------------------------------------------------- | | `Authorization` | `Bearer ` seguido do token de acesso do passo 3. | | `Idempotency-Key` | Um valor único para esta cobrança, como um UUID. Veja [Idempotência](/docs/guias/fundamentos/idempotencia). | | `Content-Type` | `application/json` | #### cURL ```bash curl -X POST "https://api.pagpolar.com/v1/payments/pix" \ -H "Authorization: Bearer " \ -H "Idempotency-Key: 6b1f2c3d-4e5f-4a6b-8c7d-9e0f1a2b3c4d" \ -H "Content-Type: application/json" \ -d '{ "offer_identifier": "", "external_reference": "PEDIDO-0001", "customer": { "name": "Maria Silva", "email": "cliente@exemplo.com", "document": "", "phone": "" } }' ``` #### Node.js ```js import { randomUUID } from 'node:crypto'; const response = await fetch('https://api.pagpolar.com/v1/payments/pix', { method: 'POST', headers: { Authorization: `Bearer ${accessToken}`, 'Idempotency-Key': randomUUID(), 'Content-Type': 'application/json', }, body: JSON.stringify({ offer_identifier: '', external_reference: 'PEDIDO-0001', customer: { name: 'Maria Silva', email: 'cliente@exemplo.com', document: '', phone: '', }, }), }); console.log(response.status, await response.json()); ``` | Campo | Obrigatório | O que é | | -------------------- | --------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `offer_identifier` | Sim, ou `offer` | Código da oferta. | | `external_reference` | Não | Código do seu pedido, até 255 caracteres. Serve para achar a venda depois. Veja [Como a venda é encontrada](/docs/guias/fundamentos/valores-datas-e-identificadores#como-a-venda-e-encontrada). | 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](/docs/guias/fundamentos/valores-datas-e-identificadores#documento-e-telefone). | | `customer.phone` | Sim | Telefone do cliente com DDD, com ou sem pontuação. Veja o formato em [Documento e telefone](/docs/guias/fundamentos/valores-datas-e-identificadores#documento-e-telefone). | Resposta `201`: ```json { "data": { "offer_identifier": "PPP1234567890", "transactions": ["a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d"], "subscriptions": [], "pix": { "qr_code": "00020126580014br.gov.bcb.pix0136a1b2c3d4-e5f6-4a7b-8c9d" } } } ``` | Campo | O que fazer com ele | | ----------------- | -------------------------------------------------------------------------- | | `transactions[0]` | É o `id` da venda. Guarde junto do seu pedido. | | `pix.qr_code` | Mostre ao cliente como "copia e cola" ou gere a imagem do QR Code com ele. | Contrato completo: [`POST /payments/pix`](/docs/referencia/vendas/create-pix-payment). 8. **Confira o resultado** **1. Consulte a venda.** Use o `id` que veio em `transactions[0]`: #### cURL ```bash curl "https://api.pagpolar.com/v1/sales/a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d" \ -H "Authorization: Bearer " ``` #### Node.js ```js const response = await fetch( 'https://api.pagpolar.com/v1/sales/a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d', { headers: { Authorization: `Bearer ${accessToken}` } }, ); console.log(response.status, await response.json()); ``` Resposta `200` (resumida): ```json { "data": { "id": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d", "identifier": "PPO9876543210", "external_reference": "PEDIDO-0001", "status": "PROCESSING", "payment_method": "PIX", "total_amount": 10, "paid_at": null } } ``` Você também pode consultar pelo código da venda (`identifier`) ou pela sua referência: `GET /sales/PEDIDO-0001`. Veja [Como a venda é encontrada](/docs/guias/fundamentos/valores-datas-e-identificadores#como-a-venda-e-encontrada). **2. Veja o webhook.** A sua URL recebe o evento [`TRANSACTION_CREATED`](/docs/webhooks/eventos/transaction-created). **3. Entenda o pagamento.** Quando um PIX é pago, chega [`TRANSACTION_PAID`](/docs/webhooks/eventos/transaction-paid). A venda passa a `status: PAID` e `paid_at` é preenchido. No ambiente de testes, isso acontece sozinho, como no aviso do início deste guia. ## Se algo deu errado Além dos [erros comuns a todas as rotas](/docs/guias/fundamentos/erros#erros-comuns), este guia pode responder: | Passo | Situação | Resposta | Como resolver | | ----- | ------------------------------------------------------------------------ | --------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | | 1 | A conta já tem 5 credenciais ativas, somando Produção e Homologação. | `Limite de 5 credenciais ativas atingido. Revogue uma credencial antes de criar outra.` | Revogue uma credencial sem uso. | | 1 | A conta já tem uma chave de Homologação ativa. | A opção **Homologação** fica desabilitada no campo **Ambiente**. | Use a chave de Homologação que você já tem, ou revogue-a e crie outra. | | 1 | A etiqueta da chave mudou para **Falha ao preparar ambiente de testes**. | O motivo aparece ao passar o mouse sobre a etiqueta. | Revogue a chave e crie outra. | | 3 | A chave ainda não está pronta no ambiente de testes. | `401 unauthorized` | Espere a etiqueta **Ambiente de testes pronto** e repita. | | 6 | `product_id` errado ou de outra conta. | `404` com `Produto não encontrado` | Use o `data.id` do passo 5. | | 6 | Parcela abaixo do mínimo. | `400` com `O valor da parcela (...) fica abaixo do mínimo permitido (...)` | Envie `max_credit_card_installments: 1`. | | 7 | Código da oferta errado. | `404` com `Oferta não encontrada` | Use o `data.identifier` do passo 6. | | 7 | PIX desligado na oferta. | `409` com `Método de pagamento PIX não habilitado para esta oferta` | Crie a oferta com `is_enabled_pix: true` e `price` a partir do [valor mínimo do PIX](/docs/guias/jornadas/criar-produto-e-oferta#meios-de-pagamento). | | 3 a 8 | O ambiente de testes está fora do ar ou não respondeu em 30 segundos. | `502 sandbox_unavailable` | Espere alguns segundos e repita. Veja [Erros](/docs/guias/fundamentos/erros#sandbox-indisponivel). | ## Próximos passos - [Credenciais da API](/docs/guias/fundamentos/credenciais) — Restrinja por IP e revogue credenciais. - [Autenticar as requisições recebidas](/docs/webhooks/autenticar-requisicoes) — Confirme que o webhook veio da PagPolar. - [Idempotência](/docs/guias/fundamentos/idempotencia) — Repita uma cobrança sem cobrar duas vezes. --- # Para agentes de IA URL: https://staging.pagpolar.com/docs/guias/para-agentes-de-ia > Entregue a especificação da API e dos webhooks a um assistente de IA para ele ajudar a escrever a sua integração. Assistentes de IA escrevem código melhor quando recebem o contrato exato da API. A PagPolar publica esse contrato em arquivos OpenAPI, que qualquer assistente consegue ler. ## Arquivos disponíveis Todos os arquivos ficam no endereço do portal, são públicos e não precisam de chave. | Ambiente | Endereço do portal | | -------- | ----------------------------------- | | Produção | `https://app.pagpolar.com/docs` | | Staging | `https://staging.pagpolar.com/docs` | | Arquivo | Conteúdo | | ---------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `/llms.txt` | Índice do portal para assistentes. Começa pelos links do OpenAPI e depois lista todas as páginas — guias, webhooks e referência — com título, endereço e resumo. | | `/openapi.json` | Todas as operações da API, com parâmetros, corpos, respostas e erros (OpenAPI 3.0). É o arquivo que gera a [Referência da API](/docs/referencia). | | `/webhooks.json` | Os 15 eventos de webhook, com o formato de cada payload (OpenAPI 3.1). É o arquivo que gera o [Catálogo de eventos](/docs/webhooks/eventos). | | `/llms-full.txt` | O conteúdo inteiro do portal num arquivo só. | | qualquer página com `.mdx` no fim | O texto daquela página. Exemplo em Produção: `https://app.pagpolar.com/docs/guias/fundamentos/erros.mdx`. | | qualquer página com o header `Accept: text/markdown` | O mesmo texto da versão `.mdx`, no endereço normal da página. | | `/api/search?query=` | Busca no conteúdo do portal, em JSON. Exemplo: `https://app.pagpolar.com/docs/api/search?query=reembolso`. | Comece pelo `llms.txt` quando quiser que o assistente escolha o que ler; use o `llms-full.txt` quando quiser dar tudo de uma vez. `openapi.json`, `webhooks.json` e `llms.txt` podem ficar em cache por até 5 minutos. ## Como usar com um assistente 1. Baixe os arquivos que a tarefa precisa. Para cobrar, baixe `openapi.json`. Para receber avisos, baixe também `webhooks.json`. 2. Anexe os arquivos na conversa com o assistente, ou cole o conteúdo. 3. Diga a linguagem, o que você quer fazer e as regras abaixo. Exemplo de pedido: ```text Anexei a especificação OpenAPI da API PagPolar e a dos webhooks. Escreva, em Node.js, uma função que cria uma venda PIX com POST /payments/pix. Regras: - antes de tudo, chame POST /auth/token com o header X-API-Key vindo da variável de ambiente PAGPOLAR_API_KEY, e guarde o access_token da resposta em memória; - o access_token vale 24 horas e vai no header Authorization, no formato "Bearer ", em POST /payments/pix e em qualquer outra rota; - não existe rota de renovação: se a resposta for 401, peça outro token em POST /auth/token e repita a chamada uma única vez, com a mesma Idempotency-Key; - gere uma Idempotency-Key por pedido e guarde antes de enviar; - envie external_reference com o código do pedido; - trate os erros no formato { error: { code, message, request_id } }; - não invente campos que não estão na especificação. Depois, escreva o endpoint que recebe o webhook TRANSACTION_PAID e valida o header Authorization. ``` ## Limites * **O assistente pode errar.** Confira cada campo que ele usar na [Referência da API](/docs/referencia) antes de colocar o código no ar. * **A especificação não traz todas as regras de negócio.** Leia os [Fundamentos](/docs/guias/fundamentos/credenciais) e a página [Processar eventos sem duplicar](/docs/webhooks/processar-sem-duplicar). * **Teste o código com a chave de Homologação** e só troque pela de Produção quando o fluxo estiver conferido. Veja [Ambiente de testes](/docs/guias/fundamentos/ambientes#ambiente-de-testes). * **Não cole a sua chave de API nem um token de acesso na conversa.** Use um placeholder, como ``. O token dá o mesmo acesso da chave [enquanto valer](/docs/guias/fundamentos/autenticacao#validade). --- # Introdução URL: https://staging.pagpolar.com/docs/referencia > Encontre o contrato exato de cada operação da API e baixe a especificação OpenAPI. Esta seção mostra cada operação da API: caminho, parâmetros, corpo, respostas e erros. As páginas são geradas da especificação OpenAPI publicada pela própria API e são atualizadas a cada 5 minutos, no máximo. ## Como a referência está organizada As operações ficam em 8 grupos: | Grupo | O que você faz | Comece por | | ------------ | ---------------------------------------------------------------- | -------------------------------------------------------------------------------------- | | Autenticação | Obtém o token de acesso e confere a credencial usada na chamada. | [`POST /auth/token`](/docs/referencia/autenticacao/create-access-token) | | Produtos | Cria, lista e altera produtos. | [`POST /products`](/docs/referencia/produtos/create-product) | | Ofertas | Cria, consulta e altera ofertas de produto. | [`POST /offers`](/docs/referencia/ofertas/create-offer) | | Planos | Cria planos e ofertas de plano. | [`POST /plans`](/docs/referencia/planos/create-plan) | | Vendas | Cobra por PIX, boleto ou cartão e consulta vendas. | [`POST /payments/pix`](/docs/referencia/vendas/create-pix-payment) | | Reembolsos | Lista os pedidos de reembolso e reembolsa uma venda. | [`POST /refunds`](/docs/referencia/reembolsos/create-refund) | | Assinaturas | Assina, consulta, cancela, troca o plano e troca o cartão. | [`POST /plans/offer/{id}/subscribe`](/docs/referencia/assinaturas/create-subscription) | | Clientes | Lista e consulta clientes. | [`GET /customers`](/docs/referencia/clientes/list-customers) | ## Autenticação em todas as operações Todas as operações, menos [`POST /auth/token`](/docs/referencia/autenticacao/create-access-token), exigem o token de acesso no header `Authorization`. Veja [Autenticação](/docs/guias/fundamentos/autenticacao#obter-o-token). ## Testar as operações Cada operação tem o botão **Send**, que executa a chamada no **ambiente de testes**, com a sua chave de Homologação. Para usar, entre no painel neste navegador e tenha uma chave de Homologação pronta. Nenhuma cobrança feita por aqui é real. Veja [Testar pelo portal](/docs/guias/fundamentos/ambientes#playground). ## Como ler os schemas * A resposta de sucesso traz o objeto em `data`. Nas listagens, `data` é uma lista e `meta` traz a paginação. Veja [Paginação e filtros](/docs/guias/fundamentos/paginacao-e-filtros). * Campos que podem vir vazios estão marcados como anuláveis e chegam com `null`. * Campos com opções fixas, como `status` e `payment_method`, listam todos os valores aceitos. * Os valores em dinheiro das respostas são números em reais. Veja [Valores nas respostas](/docs/guias/fundamentos/valores-datas-e-identificadores#valores-nas-respostas). ## Baixar a especificação OpenAPI | Arquivo | Conteúdo | | --------------------------------------------- | ----------------------------------------- | | `https://app.pagpolar.com/docs/openapi.json` | Especificação da API (OpenAPI 3.0). | | `https://app.pagpolar.com/docs/webhooks.json` | Especificação dos webhooks (OpenAPI 3.1). | Use esses arquivos para gerar clientes em outras linguagens ou para dar contexto a um assistente de IA. Veja [Para agentes de IA](/docs/guias/para-agentes-de-ia). ## Guias relacionados | Grupo | Leia antes | | ---------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Vendas | [Idempotência](/docs/guias/fundamentos/idempotencia), [Limites de requisição](/docs/guias/fundamentos/limites-de-requisicao), [Ciclo de vida da venda](/docs/guias/conceitos/ciclo-de-vida-da-venda) | | Ofertas e Planos | [Ofertas, planos e ofertas ocultas](/docs/guias/conceitos/ofertas-planos-e-ofertas-ocultas) | | Assinaturas | [Ciclo de vida da assinatura](/docs/guias/conceitos/ciclo-de-vida-da-assinatura) | | Listagens | [Paginação e filtros](/docs/guias/fundamentos/paginacao-e-filtros) | --- # Autenticar as requisições recebidas URL: https://staging.pagpolar.com/docs/webhooks/autenticar-requisicoes > Valide o header Authorization antes de processar qualquer evento de webhook. A URL do seu webhook é pública. Qualquer pessoa pode enviar um `POST` para ela fingindo ser a PagPolar. Por isso, confira o header `Authorization` **antes** de processar o evento. ## Como a PagPolar se identifica Todo aviso do webhook da credencial chega com este header: ```text Authorization: Bearer ``` > **Não é o token de acesso da API** > > Este `Authorization` é o que a PagPolar envia **para** o seu servidor, com o **token do webhook**. Ele não tem relação com o token de acesso que você envia **para** a API, obtido em [`POST /auth/token`](/docs/referencia/autenticacao/create-access-token). São dois valores diferentes, em direções diferentes. Veja [Autenticação](/docs/guias/fundamentos/autenticacao). ## O token do webhook O token é exibido uma única vez na janela **Chave criada com sucesso**, ao criar a credencial. O token criado junto com a credencial tem 32 caracteres hexadecimais. Se você trocar o token em **Configurações → Webhooks**, ele passa a ter o formato que você digitou ou gerou. O valor é o mesmo em todos os avisos daquele webhook até ser trocado. O webhook da credencial sempre tem token. Um [webhook adicional](/docs/webhooks/configurar#webhooks-adicionais) sem token envia os avisos sem o header `Authorization`. > **Digite só o token, sem Bearer** > > O valor do campo **Authorization (Bearer token)** chega no header como `Authorization: Bearer `. A PagPolar acrescenta o `Bearer ` sozinha: se você digitar `Bearer abc`, o header chega como `Bearer Bearer abc`. Ao trocar o valor, atualize também o valor que o seu servidor confere. ## Validar o header 1. Guarde o token em uma variável de ambiente do servidor. 2. Monte o valor esperado: `Bearer ` seguido do token. 3. Compare com o header recebido usando uma comparação de tempo constante. 4. Se não bater, responda `401` e não processe nada. 5. Se bater, responda `200` e processe o evento. #### cURL Use cURL para testar o seu servidor, simulando um aviso: ```bash curl -X POST "https://seu-servidor.exemplo.com/webhooks/pagpolar" \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "id": "550e8400-e29b-41d4-a716-446655440000", "event": "TRANSACTION_PAID", "creation_date": "2026-09-15T14:35:05.000Z", "version": "1.0.0", "data": {} }' ``` Com o token certo, o seu servidor deve responder `200`. Troque o token por qualquer outro valor: a resposta deve ser `401`. #### Node.js Exemplo com Express. Instale com `npm install express`. ```js import express from 'express'; import { timingSafeEqual } from 'node:crypto'; const app = express(); app.use(express.json()); const expectedAuthorization = Buffer.from( `Bearer ${process.env.PAGPOLAR_WEBHOOK_TOKEN}`, ); const isFromPagPolar = (request) => { const receivedAuthorization = Buffer.from(request.get('authorization') ?? ''); return ( receivedAuthorization.length === expectedAuthorization.length && timingSafeEqual(receivedAuthorization, expectedAuthorization) ); }; app.post('/webhooks/pagpolar', (request, response) => { if (!isFromPagPolar(request)) { return response.sendStatus(401); } response.sendStatus(200); console.log('Evento recebido:', request.body.event); }); app.listen(3000); ``` > **Por que comparação de tempo constante** > > Uma comparação comum (`===`) para no primeiro caractere diferente. Medindo o tempo de resposta, um atacante consegue descobrir o token aos poucos. `timingSafeEqual` leva sempre o mesmo tempo. Responder `401` conta como falha de entrega. Se for um aviso verdadeiro com o token desatualizado no seu servidor, a PagPolar tenta de novo. Veja [Entregas e retentativas](/docs/webhooks/entregas-e-retentativas). ## O que não existe * **Não há assinatura do corpo (HMAC).** O token prova quem enviou, mas o corpo não é assinado. * **Não há lista de IPs de origem** publicada para os avisos. Por isso: * use [HTTPS na URL do webhook](/docs/webhooks/configurar#requisitos-da-url), para o token e o corpo não trafegarem abertos; * antes de uma ação de alto valor, como liberar um produto caro, confirme o status em [`GET /sales/{identifier}`](/docs/referencia/vendas/get-sale) usando `data.transaction.id`. ## Se o token vazar Você tem duas saídas: | Opção | Como | Efeito | | -------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------ | | Trocar o token | Em **Configurações → Webhooks**, edite o webhook da credencial e troque o valor do campo **Authorization (Bearer token)**, [sem `Bearer`](#token-do-webhook). Atualize o valor no seu servidor. | A PagPolar passa a enviar o novo valor. A chave de API continua a mesma. | | Trocar tudo | [Revogue a credencial](/docs/guias/fundamentos/credenciais#revogar) e crie outra. | Chave de API e token novos. Atualize os dois no seu servidor. | ## Próximos passos - [Processar eventos sem duplicar](/docs/webhooks/processar-sem-duplicar) — Trate repetições e eventos fora de ordem. - [Entregas e retentativas](/docs/webhooks/entregas-e-retentativas) — Saiba o que conta como entrega. --- # Configurar o webhook URL: https://staging.pagpolar.com/docs/webhooks/configurar > Cadastre a URL, escolha os eventos e saiba o que pode ou não mudar no webhook da credencial. ## O webhook da credencial Ao criar uma credencial da API, a PagPolar cria **um webhook junto**. A URL é informada em **Configurações → API → Nova chave**, no campo **URL do webhook**, que aceita qualquer URL válida. O webhook vale para todos os produtos da conta e aparece em **Configurações → Webhooks** com o nome `API Integration — ` seguido do nome da sua integração. Você não precisa cadastrar outro webhook em **Configurações → Webhooks** para receber os avisos dessa integração. Se cadastrar outro para a mesma URL, cada aviso chega duas vezes. O token que o webhook envia é gerado junto com a credencial. Veja [O token do webhook](/docs/webhooks/autenticar-requisicoes#token-do-webhook). O passo a passo da criação está em [Credenciais da API](/docs/guias/fundamentos/credenciais). ## Escolher os eventos No mesmo formulário, o campo **Eventos** define quais avisos o webhook recebe: * **Em branco:** recebe os 15 eventos. * **Com eventos marcados:** recebe só os marcados. Veja o que cada evento significa no [Catálogo de eventos](/docs/webhooks/eventos). > **Na dúvida, receba todos** > > Receber um evento que você não usa não causa problema: responda `200` e ignore. Deixar de receber um evento importante faz o seu sistema perder uma mudança de status. ## Mudar a URL ou os eventos depois O formulário de edição da credencial não mostra o webhook. Para mudar a URL, os eventos ou o token: 1. Abra **Configurações → Webhooks**. 2. Encontre o webhook com o nome `API Integration — ` seguido do nome da sua integração. 3. Abra **Editar webhook** e altere **URL**, **Tipos de evento**, **Máximo de tentativas** ou **Authorization (Bearer token)**. 4. Salve. Na chave de Homologação, a edição não chega ao ambiente de testes. Veja [O webhook da chave de Homologação](/docs/webhooks#homologacao). Antes de trocar o valor de **Authorization (Bearer token)**, leia [O token do webhook](/docs/webhooks/autenticar-requisicoes#token-do-webhook). ## O que não dá para fazer Enquanto a credencial estiver ativa, o webhook dela **não pode ser desativado nem removido**. A tentativa é recusada com status `409` e um destes avisos. Ao desativar: ```text Este webhook está vinculado a uma credencial de API ativa e não pode ser desativado. Revogue a credencial primeiro. ``` Ao remover: ```text Este webhook está vinculado a uma credencial de API ativa e não pode ser removido. Revogue a credencial primeiro. ``` Ao [revogar a credencial](/docs/guias/fundamentos/credenciais#revogar), o webhook é desativado junto. ## Webhooks adicionais Você pode criar outros webhooks, sem ligação com uma credencial, em **Configurações → Webhooks → Novo Webhook**. Eles recebem os mesmos eventos, com estes campos: | Campo | Regras | | -------------------------------- | -------------------------------------------------------------------------------------------- | | **Nome** | Opcional, até 255 caracteres. | | **URL** | Obrigatória. Uma URL válida. | | **Máximo de tentativas** | De 1 a 10. Padrão: 5. | | **Authorization (Bearer token)** | Opcional. Veja [O token do webhook](/docs/webhooks/autenticar-requisicoes#token-do-webhook). | | **Tipos de evento** | Os eventos que o webhook recebe. | | Produtos | Todos os produtos ou só os escolhidos. | Um webhook restrito a produtos só recebe os eventos de vendas e assinaturas desses produtos. ## Requisitos da sua URL * **Pública.** A PagPolar precisa alcançar a URL pela internet. * **HTTPS.** Use HTTPS para proteger o token e os dados pessoais do cliente, que chegam no corpo do aviso. * **Rápida e estável.** O prazo de resposta, os status aceitos e o efeito de um `404` estão em [Entregas e retentativas](/docs/webhooks/entregas-e-retentativas#sucesso). ## Próximos passos - [Autenticar as requisições recebidas](/docs/webhooks/autenticar-requisicoes) — Valide o header Authorization. - [Formato do evento](/docs/webhooks/formato-do-evento) — Leia os campos que chegam. --- # Entregas e retentativas URL: https://staging.pagpolar.com/docs/webhooks/entregas-e-retentativas > Saiba o que conta como entrega bem-sucedida, quantas tentativas a PagPolar faz e como reenviar avisos pelo painel. ## O que conta como entrega bem-sucedida A entrega dá certo quando o seu servidor responde com **qualquer status 2xx** (200, 201, 202, 204...) em até **10 segundos**. Passado esse tempo, a tentativa conta como falha, mesmo que o seu servidor termine o processamento depois. O corpo da resposta não importa. Responda `200` com corpo vazio. Por isso: grave o evento, responda e processe em seguida. Veja [Responda rápido e processe depois](/docs/webhooks/processar-sem-duplicar#responda-rapido). ## Novas tentativas Quando a entrega falha por erro passageiro, a PagPolar tenta de novo: status fora de 2xx (exceto `404` e `410`), conexão recusada ou tempo esgotado. | Webhook | Máximo de tentativas | Intervalo entre tentativas | | --------------------- | -------------------------------------------------------------------------------------- | -------------------------- | | Webhook da credencial | 5, até você mudar **Máximo de tentativas** em **Configurações → Webhooks** (de 1 a 10) | Cerca de 30 segundos | | Webhooks adicionais | O valor de **Máximo de tentativas**, de 1 a 10 (padrão 5) | Cerca de 30 segundos | O número de tentativas conta a primeira. Exemplo com 5 tentativas: a primeira falha e chegam mais 4, uma a cada 30 segundos, mais ou menos. Depois da quinta falha, o envio para e o webhook é desativado. As novas tentativas automáticas vão para a mesma URL e com o mesmo token do primeiro envio. O corpo é montado de novo em cada tentativa, com o [estado daquele momento](/docs/webhooks/formato-do-evento#estado-no-envio). ## Quando o webhook é desativado Alguns erros mostram que a URL não vai funcionar sem uma correção sua. Nesses casos a PagPolar **desativa o webhook** na hora, sem novas tentativas, e grava o motivo no webhook: | Situação | Motivo gravado | | ------------------------------------------------------ | -------------------------------------------- | | Seu servidor respondeu `404` ou `410` | `URL não encontrada (404)` ou `(410)` | | O domínio da URL não existe | `Domínio não encontrado` | | A URL é inválida | `URL inválida` | | O certificado SSL é inválido, expirado ou autoassinado | `Certificado SSL inválido` | | Um envio esgotou as tentativas | `Falha após N tentativas`, com o último erro | O motivo começa com `Webhook desativado automaticamente.` Isso vale também para o webhook da credencial. Com o webhook desativado, os envios que ainda estavam na fila são descartados e os eventos seguintes não são enviados. O mesmo acontece ao revogar a credencial. Para voltar a receber, corrija a URL ou o seu servidor e **ative o webhook** de novo em **Configurações → Webhooks**. Ao reativar, o motivo é apagado. Os avisos perdidos no período podem ser reenviados pelo painel, como mostra a seção abaixo; o reenvio só entrega com o webhook ativo. ## Histórico de envios no painel Há dois lugares para ver os envios: | Tela | Como abrir | O que mostra | | ------------------- | --------------------------------------------------------------------------- | ---------------------------------------- | | **Envios** | **Configurações → Webhooks**, aba **Envios** | Os envios de todos os webhooks da conta. | | **Logs do Webhook** | Em **Configurações → API**, clique no ícone **Ver histórico** da credencial | Os envios do webhook daquela credencial. | Nas duas telas, você pode pesquisar pelo código da venda ou pelo e-mail do cliente e filtrar por data e por evento. Ao abrir um envio, a janela **Detalhes do Log** mostra: | Detalhe | O que significa | | ----------- | ----------------------------------------------------------- | | Status | Situação do envio: pendente, enviando, enviado ou com erro. | | Evento | Nome do evento. | | URL | Para onde o aviso foi enviado. | | Status Code | Status HTTP que o seu servidor respondeu. | | Tentativa | Número da tentativa. | | Sucesso | Se a entrega deu certo. | | Erro | Mensagem de erro, quando houver. | | Data | Quando o envio foi registrado. | ## Reenviar um aviso 1. Abra a tela **Envios** ou **Logs do Webhook**. 2. Encontre o envio. 3. No menu do envio, clique em **Reenviar**. O reenvio monta o corpo com os dados atuais e usa a URL e o token que o webhook tem **agora**. A contagem de tentativas começa de novo. ## Reenvio em massa Use quando o seu servidor ficou fora do ar e perdeu vários avisos. 1. Abra a tela **Logs do Webhook** do webhook. 2. Clique em **Reenvio em massa**. 3. Em **A partir de**, escolha a data e o horário do primeiro envio que você quer reenviar. 4. Em **Situação dos envios**, escolha quais envios reenviar. Em branco, reenvia todos. 5. Clique em **Reenviar**. > **Reenviar tudo gera repetições** > > Se você reenviar também os envios que já tinham dado certo, o seu servidor recebe esses eventos de novo. Filtre pela situação com erro ou garanta que o seu servidor descarta repetidos. Veja [Processar eventos sem duplicar](/docs/webhooks/processar-sem-duplicar). ## Próximos passos - [Processar eventos sem duplicar](/docs/webhooks/processar-sem-duplicar) — Trate repetições e eventos fora de ordem. - [Catálogo de eventos](/docs/webhooks/eventos) — Veja quando cada evento é enviado. --- # Formato do evento URL: https://staging.pagpolar.com/docs/webhooks/formato-do-evento > Leia o envelope do webhook e cada bloco de data em eventos de venda e de assinatura. ## A requisição que chega A PagPolar envia um `POST` para a URL do webhook com: | Header | Valor | | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | | `Content-Type` | `application/json` | | `Authorization` | `Bearer ` seguido do token do webhook. Veja [Autenticar as requisições recebidas](/docs/webhooks/autenticar-requisicoes#token-do-webhook). | | `X-PagPolar-Test` | `true`, **só** nos envios de teste do [Playground](/docs/webhooks/playground). Os eventos reais não trazem este header. | ## Envelope Todo evento tem o mesmo envelope: | Campo | Tipo | O que é | | --------------- | ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `id` | texto (uuid) | Id **desta tentativa** de entrega. [Não serve para descartar repetidos](/docs/webhooks/processar-sem-duplicar#id-do-envelope). | | `event` | texto | Nome do evento, como `TRANSACTION_PAID`. | | `creation_date` | texto (data e hora) | Quando esta tentativa foi montada, em UTC. | | `version` | texto | Versão do formato. Hoje é `1.0.0`. | | `test` | booleano | Só aparece, com `true`, nos envios de teste do [Playground](/docs/webhooks/playground) Os eventos reais não trazem o campo. Em produção, ignore qualquer evento com `test: true`. | | `data` | objeto | Os dados do evento. | ## Exemplo de evento de venda ```json { "id": "550e8400-e29b-41d4-a716-446655440000", "event": "TRANSACTION_PAID", "creation_date": "2026-09-15T14:35:05.000Z", "version": "1.0.0", "data": { "transaction": { "id": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d", "identifier": "PPO0087103960", "status": "PAID", "type": "BILLING", "payment_method": "PIX", "total_amount": "97.0000", "net_amount": 97, "effective_value": "91.1800", "base_tax": "5.8200", "installment_tax": "0.0000", "base_fixed_tax": "0.9900", "base_percentage_tax": "4.8300", "installments": 1, "cycle": 1, "paid_at": "2026-09-15T14:35:00.000Z", "created_at": "2026-09-15T14:00:00.000Z" }, "items": [ { "id": "f47ac10b-58cc-4372-a567-0e02b2c3d479", "quantity": 1, "amount": "97.0000", "original_amount": "97.0000", "discount_value": "0.0000", "product": { "id": "c4d5e6f7-a8b9-4c0d-1e2f-3a4b5c6d7e8f", "name": "Curso de exemplo", "type": "DIGITAL" }, "price": { "id": "d5e6f7a8-b9c0-4d1e-2f3a-4b5c6d7e8f9a", "title": "Oferta de lançamento", "price": "97.0000", "identifier": "PPP1234567890" } } ], "product": { "id": "c4d5e6f7-a8b9-4c0d-1e2f-3a4b5c6d7e8f", "name": "Curso de exemplo", "type": "DIGITAL" }, "buyer": { "name": "Maria Silva", "email": "cliente@exemplo.com", "document": "", "phone": "" }, "address": null, "payment_details": { "origin": "DIRECT", "qr_code": "00020126580014br.gov.bcb.pix0136a1b2c3d4-e5f6-4a7b-8c9d", "billet_barcode": null, "billet_link": null, "last_credit_card_digits": null, "shipping_value": null }, "coupon": null, "subscription": null, "affiliate": null, "source": { "channel": "API", "api_credential_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7" } } } ``` Os exemplos de cada evento estão no [Catálogo de eventos](/docs/webhooks/eventos). ## Blocos dos eventos de venda Os eventos que começam com `TRANSACTION_` trazem estes blocos em `data`: | Bloco | O que traz | | ----------------------- | ------------------------------------------------------------------------------------ | | `transaction` | A venda. | | `items` | Os itens da venda, com produto e oferta. | | `product` | O produto principal da venda. Pode vir `null`. | | `buyer` | O cliente. Pode vir `null`. | | `address` | O endereço informado na compra. Pode vir `null`. | | `payment_details` | Dados do pagamento: QR Code, boleto e final do cartão. Pode vir `null`. | | `coupon` | O cupom usado. `null` sem cupom. | | `subscription` | Resumo da assinatura, quando a venda é de uma assinatura. Senão, `null`. | | `affiliate` | O afiliado que indicou a venda. `null` sem afiliado. Veja [`affiliate`](#affiliate). | | `source` | O canal em que a venda nasceu. | | `order_bumps` | Só aparece em alguns casos. Veja [Order bumps e upsells](#order-bumps-e-upsells). | | `reference_transaction` | Só aparece em alguns casos. Veja [Order bumps e upsells](#order-bumps-e-upsells). | ### `transaction` | Campo | O que é | | --------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | | `id` | Id da venda. Use para ligar ao seu pedido e para consultar `GET /sales/{identifier}`. | | `identifier` | Código da venda: `PPO` seguido de 10 dígitos, que podem começar com zero, como `PPO0087103960`. É o mesmo código das respostas da API e do painel. | | `status` | Status da venda [no momento do envio](#estado-no-envio). Veja [Ciclo de vida da venda](/docs/guias/conceitos/ciclo-de-vida-da-venda). | | `type` | Tipo da venda. `BILLING` é a cobrança ao cliente. | | `payment_method` | Meio de pagamento, como `PIX`, `BOLETO` ou `CREDIT_CARD`. | | `total_amount` | Valor pago pelo cliente, **com** juros do parcelamento. | | `net_amount` | Valor da venda **sem** juros do parcelamento (`total_amount` menos `installment_tax`). É o valor indicado para conciliação. | | `effective_value` | Valor que fica para você, depois da taxa da plataforma. | | `base_tax` | Taxa da plataforma (`base_fixed_tax` mais `base_percentage_tax`). | | `installment_tax` | Juros do parcelamento. | | `base_fixed_tax` | Parte fixa da taxa da plataforma. | | `base_percentage_tax` | Parte percentual da taxa, já em reais. | | `installments` | Número de parcelas. `1` à vista. | | `cycle` | Ciclo da assinatura. `1` na primeira cobrança e em vendas avulsas. | | `paid_at` | Data e hora do pagamento. `null` enquanto não pago. | | `created_at` | Data e hora da criação. | Campos extras em alguns eventos: | Evento | Campos a mais em `transaction` | | ---------------------------------------------------- | ------------------------------------------------------------------------------------------------- | | `TRANSACTION_ASK_REFUNDING` e `TRANSACTION_REFUNDED` | `refund_reason` (motivo do pedido) e `refund_at` (data do estorno, `null` enquanto só foi pedido) | | `TRANSACTION_CHARGEBACK_APPROVED` | `chargeback_approved_at` (data da aprovação do chargeback) | ### `items` | Campo | O que é | | ----------------- | --------------------------------------------------------------------------------------------------------------------------------------------- | | `id` | Id do item. | | `quantity` | Quantidade. | | `amount` | Valor cobrado pelo item, já com desconto. | | `original_amount` | Valor antes do desconto. | | `discount_value` | Desconto aplicado. | | `product` | Produto do item: `id`, `name` e `type`. Pode vir `null`. | | `price` | Oferta do item: `id`, `title`, `price` e `identifier` (código da oferta: `PPP` seguido de 10 dígitos, como `PPP1234567890`). Pode vir `null`. | > **Os códigos chegam com prefixo, iguais aos da API** > > `transaction.identifier` chega com `PPO` e `items[].price.identifier` com `PPP`, iguais aos das respostas da API e do painel. Isso vale também dentro de `order_bumps` e `reference_transaction`. ### `payment_details` | Campo | O que é | | ------------------------- | ----------------------------------------------------------------------------------------------------------------------- | | `origin` | Papel desta venda na compra: `DIRECT` é a venda principal; `ORDERBUMP` e `UPSELL` são ofertas adicionais ligadas a ela. | | `qr_code` | Código PIX "copia e cola". `null` fora do PIX. | | `billet_barcode` | Código do boleto como o gateway devolveu. `null` fora do boleto. | | `billet_link` | Link do PDF do boleto. `null` fora do boleto. | | `last_credit_card_digits` | Últimos dígitos do cartão. `null` fora do cartão. | | `shipping_value` | Valor do frete. Pode vir `null`. | ### `subscription` dentro de um evento de venda | Campo | O que é | | ----------------- | --------------------- | | `id` | Id da assinatura. | | `status` | Status da assinatura. | | `start_at` | Início da assinatura. | | `next_billing_at` | Próxima cobrança. | ### `coupon` | Campo | O que é | | -------------------- | ----------------------------------------------------------------------- | | `id`, `code`, `name` | Id, código e nome do cupom. | | `fixed_value` | Desconto fixo em reais, como número. `null` se o cupom é percentual. | | `percentage_value` | Desconto percentual, como número. `10` = 10%. `null` se o cupom é fixo. | ### `affiliate` | Campo | O que é | | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------ | | `identifier` | Código do afiliado: `PAO` seguido de 10 dígitos. | | `name` | Nome da conta do afiliado: o nome fantasia ou, sem ele, a razão social. | | `commission_type` | `COMMISSION` para comissão em dinheiro; `PRODUCT` para comissão em unidades do produto. | | `commission_value` | Comissão da venda em reais, como número. `null` na comissão em produto e enquanto o repasse ao afiliado ainda não foi gerado, como antes do pagamento. | | `product_quantity` | Unidades do produto por recompensa, na comissão em produto. `null` na comissão em dinheiro. | Order bumps e upsells trazem o afiliado da venda principal. ## Order bumps e upsells Uma compra no checkout pode ter itens extras (order bumps e upsells). Cada um vira uma venda separada, ligada à venda principal. | Bloco | Quando aparece | O que traz | | ----------------------- | --------------------------------------------------------------- | ------------------------------------------------------------------------------- | | `order_bumps` | O evento é da venda principal e ela tem order bumps ou upsells. | Uma lista. Cada item tem `transaction`, `items` e `product` de uma venda extra. | | `reference_transaction` | O evento é de um order bump ou upsell. | `transaction`, `items` e `product` da venda principal. | Quando não se aplicam, os dois blocos **não aparecem** no JSON. ## Blocos dos eventos de assinatura Os eventos que começam com `SUBSCRIPTION_` trazem: | Bloco | O que traz | | -------------- | ------------------------------------------- | | `subscription` | A assinatura. | | `product` | O plano. Pode vir `null`. | | `buyer` | O cliente. Pode vir `null`. | | `source` | O canal da primeira cobrança da assinatura. | ### `subscription` | Campo | O que é | | ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `id` | Id da assinatura. Use em `GET /subscriptions/{id}`. | | `external_id` | Id da assinatura no gateway. `null` em PIX ou boleto e antes da confirmação no cartão. | | `status` | Status da assinatura [no momento do envio](#estado-no-envio). Veja [Ciclo de vida da assinatura](/docs/guias/conceitos/ciclo-de-vida-da-assinatura). | | `start_at` | Início da assinatura. | | `end_at` | Fim da assinatura. Preenchido no cancelamento. No `SUBSCRIPTION_CANCELED` de cartão, pode chegar `null`, porque a data é gravada logo depois do envio. Confira em `GET /subscriptions/{id}`. | | `next_billing_at` | Próxima cobrança. | | `payment_method` | Meio de pagamento da assinatura. | | `total_amount` | Valor de cada ciclo. | | `created_at` | Data e hora da criação. | ## Canal da venda: `source` > **Não é só o que a sua integração criou** > > O webhook da credencial recebe os eventos de **todas** as vendas e assinaturas da conta: as criadas pela API, as do checkout da PagPolar e as vendas manuais. Se o seu sistema só deve tratar o que ele mesmo criou, filtre pelo campo `source`. | `source.channel` | De onde veio a venda | | ---------------- | -------------------------------------------------------------------------- | | `API` | Criada pela API. | | `CHECKOUT` | Checkout da PagPolar. | | `MANUAL` | Venda cortesia gerada pelo vendedor, como um ingresso emitido manualmente. | | `AWARD` | Prêmio de afiliado. | `source.api_credential_id` é o id da credencial que criou a venda, quando `channel` é `API`. Nos outros canais, chega `null`. Regras: * Order bumps, upsells e renovações herdam o canal da venda original. * Nos eventos de assinatura, `source` é o canal da primeira cobrança da assinatura. Exemplo de filtro em Node.js: ```js const isFromMyIntegration = (payload) => payload.data?.source?.channel === 'API' && payload.data.source.api_credential_id === process.env.PAGPOLAR_CREDENTIAL_ID; ``` O `PAGPOLAR_CREDENTIAL_ID` é o `credential_id` que [`GET /me`](/docs/referencia/autenticacao/get-current-credential) devolve. ## O payload traz o estado do momento do envio O `data` é montado na hora de cada tentativa, não na hora em que o evento aconteceu. Por isso: * um `TRANSACTION_CREATED` pode chegar com `status: PAID`, se a venda foi paga antes da entrega; * uma nova tentativa ou um reenvio pode trazer um status diferente do primeiro envio; * um evento atrasado pode trazer um status mais antigo do que o que você já gravou, ou mais novo do que o nome do evento sugere. Para decidir, olhe o `status` que chegou. Como tratar status atrasado está em [Não deixe o status voltar para trás](/docs/webhooks/processar-sem-duplicar#status-para-tras). Se a venda ou a assinatura não for encontrada no momento do envio, `data` chega vazio: `{}`. ## Valores em dinheiro Todos os valores estão em **reais**. Mas o tipo muda conforme o campo: | Chega como **texto** | Chega como **número** | | --------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------- | | `transaction.total_amount`, `effective_value`, `base_tax`, `installment_tax`, `base_fixed_tax`, `base_percentage_tax` | `transaction.net_amount` | | `items[].amount`, `original_amount`, `discount_value`, `price.price` | `coupon.fixed_value`, `coupon.percentage_value` | | `payment_details.shipping_value` | | | `subscription.total_amount` nos eventos de assinatura | | Os textos têm casas decimais fixas, como `"97.0000"`. Converta para número antes de fazer contas: ```js const totalAmount = Number(payload.data.transaction.total_amount); ``` > **Na API REST é diferente** > > Nas respostas da API, os valores chegam como número. Veja [Valores nas respostas](/docs/guias/fundamentos/valores-datas-e-identificadores#valores-nas-respostas). ## Datas Datas chegam como texto em ISO 8601, como `2026-09-15T14:35:00.000Z`. Datas vazias chegam `null`. ## Dados pessoais Diferente das [respostas da API](/docs/guias/fundamentos/valores-datas-e-identificadores#dados-pessoais-mascarados), o webhook envia `buyer.document` e `buyer.phone` **sem máscara**. Não grave o corpo completo dos avisos em logs abertos e restrinja o acesso aos dados guardados. --- # Visão geral dos webhooks URL: https://staging.pagpolar.com/docs/webhooks > Entenda como a PagPolar avisa o seu servidor quando uma venda ou assinatura muda e por que o webhook também recebe as vendas do checkout. ## O que é e por que usar Webhook é uma requisição `POST` que a PagPolar envia para uma URL do seu servidor quando algo acontece: uma venda foi paga, um boleto venceu, uma assinatura foi cancelada. Sem webhook, o seu sistema teria que perguntar à API, sem parar, se algo mudou. Com webhook, a PagPolar avisa na hora. ## O webhook da sua credencial Cada credencial da API nasce com um webhook próprio, com a URL, os eventos e o token escolhidos na criação. Veja [O webhook da credencial](/docs/webhooks/configurar#webhook-da-credencial). ## O webhook da chave de Homologação Ao criar a chave de Homologação, a PagPolar copia o webhook dela para o [ambiente de testes](/docs/guias/fundamentos/ambientes#ambiente-de-testes), com a mesma URL, os mesmos eventos e o mesmo token. Os avisos das vendas e assinaturas de teste chegam nessa URL, no mesmo formato dos avisos reais. Leve em conta duas regras: * **A cópia não acompanha as edições.** Mudar a URL, os eventos ou o token em **Configurações → Webhooks** não muda o webhook do ambiente de testes. Os avisos de teste continuam indo para a URL, os eventos e o token escolhidos na criação da chave. Para trocar, revogue a chave de Homologação e crie outra. * **Na sua conta real, o webhook continua existindo** e recebe os avisos reais. A mesma URL pode receber avisos reais e avisos de teste. Para reconhecer um aviso de teste de uma venda criada pela API, compare [`data.source.api_credential_id`](/docs/webhooks/formato-do-evento#source) com o `credential_id` que [`GET /me`](/docs/referencia/autenticacao/get-current-credential) devolve para a chave de Homologação. ## Ele recebe também as vendas do checkout O webhook da credencial recebe os eventos de **todas** as vendas e assinaturas da conta, não só as que a sua integração criou. Para separar, use o campo `source`. Veja [Canal da venda](/docs/webhooks/formato-do-evento#source). ## Como um evento chega até você O diagrama mostra o caminho de um evento, da mudança na PagPolar até o seu servidor. ```mermaid sequenceDiagram autonumber participant P as PagPolar participant F as Fila de entrega participant W as Seu servidor de webhook P->>F: venda ou assinatura mudou, um envio por webhook F->>W: POST com Authorization Bearer alt resposta 2xx em até 10 segundos W-->>F: entrega concluída else 404, 410, domínio inexistente ou certificado inválido W-->>F: falha, webhook desativado else outro erro ou 10 segundos sem resposta W-->>F: falha F->>W: nova tentativa cerca de 30 segundos depois, até esgotar e desativar end ``` Quantas tentativas são feitas e o que conta como entrega está em [Entregas e retentativas](/docs/webhooks/entregas-e-retentativas). ## Próximos passos - [Playground](/docs/webhooks/playground) — Monte um evento, edite o JSON e envie um teste para o seu webhook. - [Autenticar as requisições recebidas](/docs/webhooks/autenticar-requisicoes) — Confirme que o aviso veio da PagPolar. - [Formato do evento](/docs/webhooks/formato-do-evento) — Leia o envelope e os campos de cada bloco. - [Processar eventos sem duplicar](/docs/webhooks/processar-sem-duplicar) — Trate repetições e eventos fora de ordem. - [Catálogo de eventos](/docs/webhooks/eventos) — Os 15 eventos e quando cada um é enviado. --- # Playground URL: https://staging.pagpolar.com/docs/webhooks/playground > Monte um evento de webhook de teste, veja o JSON e envie para um webhook ativo da sua conta. Escolha o evento e o cenário, confira ou edite o JSON e envie para um dos seus webhooks ativos. O playground só funciona com você logado no painel. Todo envio leva `test: true` no corpo e o header `X-PagPolar-Test: true`, não entra no histórico de envios do webhook e uma falha não desativa o webhook. Ferramenta interativa disponível somente no site, nesta página. --- # Processar eventos sem duplicar URL: https://staging.pagpolar.com/docs/webhooks/processar-sem-duplicar > Processe cada evento uma única vez, mesmo quando ele chega repetido, fora de ordem ou com um status posterior. ## Por que o mesmo evento chega mais de uma vez Conte com repetições. O mesmo evento pode chegar de novo quando: * **o seu servidor demorou mais de 10 segundos para responder.** A PagPolar conta como falha e tenta de novo, mesmo que você já tenha processado; * **alguém reenviou os avisos pelo painel**, um por um ou em massa; * **o gateway avisou o pagamento por mais de um caminho.** `TRANSACTION_PAID` pode ser gerado mais de uma vez para a mesma venda. ## Envios de teste Os envios do [Playground](/docs/webhooks/playground) chegam com `"test": true` no envelope e o header `X-PagPolar-Test: true`. Os eventos reais nunca trazem esses dois. Em produção, descarte o evento de teste antes de processar. ## A ordem não é garantida Cada evento é entregue separado, e vários são entregues ao mesmo tempo. Uma entrega que falha volta cerca de 30 segundos depois. Por isso, um `TRANSACTION_CREATED` que falhou pode chegar **depois** do `TRANSACTION_PAID` da mesma venda. ## O `id` do envelope não serve para descartar repetidos Cada tentativa chega com um `id` novo e uma `creation_date` nova. Duas tentativas do mesmo evento têm `id` diferentes. Não use o `id` para descobrir se o evento é repetido. ## Monte a sua chave de duplicidade Use o nome do evento e o id da venda ou da assinatura: | Evento | Chave sugerida | | ----------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `TRANSACTION_ASK_REFUNDING` | Não descarte pela chave. A mesma venda pode receber um novo pedido depois que o anterior terminou. Confira em `GET /refunds?sale_identifier=` se o pedido é novo. | | Outros eventos de venda (`TRANSACTION_*`) | `event` + `data.transaction.id` | | `SUBSCRIPTION_RENEWED` | `event` + `data.subscription.id` + `data.subscription.next_billing_at` | | Outros eventos de assinatura (`SUBSCRIPTION_*`) | `event` + `data.subscription.id` | Cuidados com esta tabela: * **Renovações têm id próprio.** Cada cobrança de renovação é uma venda nova, com outro `transaction.id`. A chave dos eventos de venda funciona para todos os ciclos. * **`SUBSCRIPTION_RENEWED` chega uma vez por ciclo pago**, sempre com o mesmo `subscription.id`. Por isso a chave inclui `next_billing_at`, que muda a cada ciclo. * **`SUBSCRIPTION_DELAYED` pode chegar uma vez por dia** enquanto a assinatura está atrasada. Com a chave sugerida, você trata só o primeiro aviso. Se quiser lembrar o cliente todo dia, não descarte esse evento. Grave a chave numa tabela com **índice único**. Se a gravação falhar por chave repetida, o evento já foi processado. ## Responda rápido e processe depois A resposta precisa chegar dentro do [tempo limite](/docs/webhooks/entregas-e-retentativas#sucesso). Não faça trabalho pesado antes de responder: 1. Confira o header `Authorization`. 2. Grave o evento recebido. 3. Responda `200`. 4. Processe o evento em seguida, fora da requisição. ## Não deixe o status voltar para trás O `data` traz o [estado do momento do envio](/docs/webhooks/formato-do-evento#estado-no-envio), que pode ser mais antigo do que o que você já gravou. Antes de mudar o seu pedido: * compare o `status` recebido com o que você já gravou; * não volte um pedido pago para pendente por causa de um evento que chegou atrasado; * se tiver dúvida, consulte o estado atual em [`GET /sales/{identifier}`](/docs/referencia/vendas/get-sale) ou [`GET /subscriptions/{id}`](/docs/referencia/assinaturas/get-subscription). ## O fluxo completo Este fluxograma é uma recomendação para o seu servidor. ```mermaid flowchart TD A[Evento recebido] --> B{Authorization confere?} B -->|Não| C[Responda 401 e descarte] B -->|Sim| D{data vazio?} D -->|Sim| E[Responda 200 e ignore] D -->|Não| F[Grave o evento e responda 200] F --> G{Chave de duplicidade já existe?} G -->|Sim| H[Ignore] G -->|Não| I{Status recebido é mais antigo que o gravado?} I -->|Sim| J[Consulte a API antes de mudar o pedido] I -->|Não| K[Aplique o efeito pelo event] ``` ## Exemplo em Node.js O exemplo usa um `Set` na memória para ficar curto. Em produção, troque o `Set` por uma tabela no banco com índice único na chave. ```js import express from 'express'; import { timingSafeEqual } from 'node:crypto'; const app = express(); app.use(express.json()); const expectedAuthorization = Buffer.from( `Bearer ${process.env.PAGPOLAR_WEBHOOK_TOKEN}`, ); const processedKeys = new Set(); const isFromPagPolar = (request) => { const receivedAuthorization = Buffer.from(request.get('authorization') ?? ''); return ( receivedAuthorization.length === expectedAuthorization.length && timingSafeEqual(receivedAuthorization, expectedAuthorization) ); }; const buildDeduplicationKey = ({ event, data }) => { if (event === 'TRANSACTION_ASK_REFUNDING') { return null; } if (data.transaction) { return `${event}:${data.transaction.id}`; } if (event === 'SUBSCRIPTION_RENEWED') { return `${event}:${data.subscription.id}:${data.subscription.next_billing_at}`; } return `${event}:${data.subscription.id}`; }; const handleEvent = async (payload) => { console.log('Processando', payload.event, payload.data.transaction?.status); }; app.post('/webhooks/pagpolar', (request, response) => { if (!isFromPagPolar(request)) { return response.sendStatus(401); } const payload = request.body; response.sendStatus(200); if (!payload.data?.transaction && !payload.data?.subscription) { return; } const deduplicationKey = buildDeduplicationKey(payload); if (!deduplicationKey) { handleEvent(payload).catch((error) => { console.error('Falha ao processar', payload.event, error); }); return; } if (processedKeys.has(deduplicationKey)) { return; } processedKeys.add(deduplicationKey); handleEvent(payload).catch((error) => { processedKeys.delete(deduplicationKey); console.error('Falha ao processar', deduplicationKey, error); }); }); app.listen(3000); ``` Se o processamento falhar, o exemplo apaga a chave. Assim, um reenvio do mesmo evento é processado de novo. `TRANSACTION_ASK_REFUNDING` fica sem chave no exemplo. Dentro de `handleEvent`, confira em `GET /refunds` se o pedido é novo. ## Próximos passos - [Catálogo de eventos](/docs/webhooks/eventos) — Veja quando cada evento é enviado. - [Entregas e retentativas](/docs/webhooks/entregas-e-retentativas) — Reenvie avisos pelo painel. --- # Ciclo de vida da assinatura URL: https://staging.pagpolar.com/docs/guias/conceitos/ciclo-de-vida-da-assinatura > Entenda por que a assinatura nasce DRAFT, quando fica ACTIVE, como renova e como termina. ## O problema: a confirmação chega depois Quando você chama [`POST /plans/offer/{id}/subscribe`](/docs/referencia/assinaturas/create-subscription), a resposta `201` chega **antes** de o gateway criar a assinatura. Por isso a resposta sempre traz `status: DRAFT`, mesmo quando tudo vai dar certo. O resultado chega depois, pelos webhooks ou por [`GET /subscriptions/{id}`](/docs/referencia/assinaturas/get-subscription). Existem dois tipos de assinatura, com caminhos diferentes: | Tipo | Como nasce | Quem cobra a cada ciclo | | ----------------- | ------------------------------------- | ---------------------------------------------- | | Cartão de crédito | Pela API ou pelo checkout da PagPolar | O gateway | | PIX ou boleto | Só pelo checkout da PagPolar | A PagPolar gera uma cobrança nova a cada ciclo | Pela API, a assinatura é sempre no cartão. As assinaturas em PIX ou boleto aparecem nos webhooks e nas consultas porque o webhook da sua credencial também [recebe as vendas do checkout](/docs/webhooks/formato-do-evento#source). ## Assinatura no cartão O diagrama mostra os status de uma assinatura no cartão. ```mermaid stateDiagram-v2 [*] --> DRAFT: POST /plans/offer/ID/subscribe DRAFT --> FAILED: gateway recusa a criação DRAFT --> ACTIVE: gateway informa a cobrança paga ACTIVE --> PROCESSING: gateway informa cobrança pendente PROCESSING --> ACTIVE: gateway informa a cobrança paga ACTIVE --> CANCELING: DELETE /subscriptions/ID CANCELING --> CANCELED: gateway confirma o cancelamento ACTIVE --> CANCELED: gateway informa cancelamento ou recusa ACTIVE --> EXPIRED: gateway informa assinatura expirada ACTIVE --> FAILED: gateway informa falha ``` | Status | Significado | Evento | O que fazer | | ------------ | -------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------- | | `DRAFT` | Assinatura registrada. O gateway ainda não confirmou. | [`SUBSCRIPTION_CREATED`](/docs/webhooks/eventos/subscription-created) e depois [`SUBSCRIPTION_CONFIRMED`](/docs/webhooks/eventos/subscription-confirmed) | Registre. Não libere o acesso. | | `ACTIVE` | A cobrança do ciclo foi paga. | Primeiro ciclo: nenhum evento de assinatura próprio; confira `status` em [`GET /subscriptions/{id}`](/docs/referencia/assinaturas/get-subscription). A partir do segundo ciclo: [`SUBSCRIPTION_RENEWED`](/docs/webhooks/eventos/subscription-renewed). | Libere ou mantenha o acesso. | | `PROCESSING` | O gateway informou uma cobrança pendente. | Nenhum evento próprio | Consulte a assinatura se precisar do detalhe. | | `CANCELING` | Você pediu o cancelamento e o gateway ainda não confirmou. | Nenhum evento | Espere `SUBSCRIPTION_CANCELED`. | | `CANCELED` | O cancelamento foi efetivado, a pedido seu ou por aviso do gateway (cancelamento ou recusa). | [`SUBSCRIPTION_CANCELED`](/docs/webhooks/eventos/subscription-canceled) | Revogue o acesso. Se precisar da data, confira `end_at` em [`GET /subscriptions/{id}`](/docs/referencia/assinaturas/get-subscription). | | `FAILED` | O gateway recusou a criação ou informou falha. | [`SUBSCRIPTION_FAILED`](/docs/webhooks/eventos/subscription-failed) na criação | Não libere o acesso. | | `EXPIRED` | O gateway informou que a assinatura expirou. | Nenhum evento próprio no cartão | Revogue o acesso. | > **SUBSCRIPTION_CONFIRMED não ativa a assinatura** > > Depois de `SUBSCRIPTION_CONFIRMED`, o status **continua `DRAFT`**. Ele só vira `ACTIVE` quando o gateway informa a cobrança paga. O status segue sempre o último aviso do gateway. Por isso a assinatura pode sair de qualquer status para outro, conforme o gateway informa. `DELETE /subscriptions/{id}` só muda o status de uma assinatura no cartão quando ela está `ACTIVE`. Em outro status, a resposta é `200` e nada muda. ## Assinatura em PIX ou boleto O diagrama mostra os status de uma assinatura em PIX ou boleto. ```mermaid stateDiagram-v2 [*] --> PENDING_PAYMENT: venda no checkout PENDING_PAYMENT --> ACTIVE: primeira cobrança paga ACTIVE --> PENDING_RENEWAL: chegou a data da próxima cobrança PENDING_RENEWAL --> ACTIVE: cobrança de renovação paga PENDING_RENEWAL --> EXPIRED: prazo de carência acabou sem pagamento PENDING_PAYMENT --> CANCELED: pedido de cancelamento ACTIVE --> CANCELED: pedido de cancelamento PENDING_RENEWAL --> CANCELED: pedido de cancelamento ``` | Status | Significado | Evento | O que fazer | | ----------------- | ---------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------- | | `PENDING_PAYMENT` | Espera o pagamento da primeira cobrança. | [`SUBSCRIPTION_CREATED`](/docs/webhooks/eventos/subscription-created) | Não libere o acesso. | | `ACTIVE` | O ciclo está pago. | [`TRANSACTION_PAID`](/docs/webhooks/eventos/transaction-paid) da cobrança do ciclo | Libere ou mantenha o acesso. | | `PENDING_RENEWAL` | Chegou a data da renovação e ela ainda não foi paga. | [`TRANSACTION_PENDING`](/docs/webhooks/eventos/transaction-pending) quando a nova cobrança é gerada e [`SUBSCRIPTION_DELAYED`](/docs/webhooks/eventos/subscription-delayed) enquanto está atrasada | Lembre o cliente de pagar. | | `EXPIRED` | O prazo de carência acabou sem pagamento. | [`SUBSCRIPTION_EXPIRED`](/docs/webhooks/eventos/subscription-expired) | Revogue o acesso. | | `CANCELED` | Cancelada na hora do pedido. | [`SUBSCRIPTION_CANCELED`](/docs/webhooks/eventos/subscription-canceled) | Revogue o acesso. | Diferenças em relação ao cartão: * A renovação paga chega como `TRANSACTION_PAID`. **Não** existe `SUBSCRIPTION_RENEWED` para PIX ou boleto. * O cancelamento vale na hora, sem passar por `CANCELING`. * A PagPolar confere atrasos e expirações uma vez por dia. ## Reembolso, estorno e chargeback cancelam a assinatura Pedidos de reembolso, estornos e chargebacks de vendas da assinatura também pedem o cancelamento dela. Veja em quais casos em [Reembolso e chargeback também cancelam](/docs/guias/jornadas/cancelar-assinatura#reembolso-e-chargeback). ## Guias relacionados - [Catálogo de eventos](/docs/webhooks/eventos) — Todos os eventos de assinatura. - [Ciclo de vida da venda](/docs/guias/conceitos/ciclo-de-vida-da-venda) — Cada cobrança da assinatura é uma venda com status próprio. --- # Ciclo de vida da venda URL: https://staging.pagpolar.com/docs/guias/conceitos/ciclo-de-vida-da-venda > Entenda cada status da venda, o que leva a venda até ele e qual evento de webhook avisa a mudança. ## O problema: a venda muda depois da resposta Quando você cria uma cobrança, a resposta chega na hora. Mas o pagamento acontece depois: o cliente paga o PIX minutos mais tarde, o boleto vence, o cartão é contestado. Por isso a venda tem um **status** (`status`), que muda com o tempo. Você fica sabendo da mudança pelos [webhooks](/docs/webhooks) ou consultando [`GET /sales/{identifier}`](/docs/referencia/vendas/get-sale). ## Diagrama de status O diagrama mostra os caminhos mais comuns de uma venda. ```mermaid stateDiagram-v2 [*] --> DRAFT: cobrança recebida DRAFT --> PROCESSING: cobrança criada no gateway DRAFT --> FAILED: gateway recusa na criação PROCESSING --> FAILED: gateway recusa depois PROCESSING --> PAID: pagamento confirmado PROCESSING --> EXPIRED: PIX ou boleto venceu PROCESSING --> CANCELED: cancelada antes de pagar PAID --> ASK_REFUND: cliente pede reembolso total PAID --> ASK_PARTIAL_REFUND: cliente pede reembolso de parte dos itens ASK_REFUND --> REFUNDED: estorno concluído ASK_REFUND --> PAID: pedido cancelado ou recusado ASK_PARTIAL_REFUND --> PAID: item estornado com itens restantes, ou pedido cancelado ou recusado ASK_PARTIAL_REFUND --> REFUNDED: último item estornado PAID --> REFUNDED: estorno concluído PAID --> CHARGEBACK_APPROVED: contestação aprovada pelo banco ``` ## O que cada status significa | Status | Significado | Evento que avisa | O que fazer | | --------------------- | ------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------ | | `DRAFT` | A venda foi registrada e a cobrança ainda está sendo criada no gateway. | [`TRANSACTION_CREATED`](/docs/webhooks/eventos/transaction-created) | Registre a venda. Não libere nada. | | `PROCESSING` | A cobrança existe no gateway e espera o pagamento. | [`TRANSACTION_CREATED`](/docs/webhooks/eventos/transaction-created) | Mostre o PIX ou o boleto ao cliente. | | `FAILED` | O gateway recusou a cobrança, na criação ou depois. Acontece no cartão. | Nenhum evento avisa a recusa. Se ela veio na criação, o `TRANSACTION_CREATED` já chega com `FAILED`. Se veio depois, a venda passa para `FAILED` sem evento: confira em [`GET /sales/{identifier}`](/docs/referencia/vendas/get-sale). | Peça outro cartão e crie uma nova cobrança. | | `PAID` | O pagamento foi confirmado. | [`TRANSACTION_PAID`](/docs/webhooks/eventos/transaction-paid) | Libere o que foi vendido. | | `EXPIRED` | O PIX ou o boleto venceu sem pagamento. | [`TRANSACTION_EXPIRED`](/docs/webhooks/eventos/transaction-expired) | Não libere. Crie outra cobrança se o cliente ainda quiser comprar. | | `CANCELED` | A venda foi cancelada sem ter sido paga. | [`TRANSACTION_CANCELED`](/docs/webhooks/eventos/transaction-canceled) | Cancele o pedido. | | `ASK_REFUND` | O cliente pediu reembolso da venda inteira. O dinheiro ainda não voltou. | [`TRANSACTION_ASK_REFUNDING`](/docs/webhooks/eventos/transaction-ask-refunding) | Registre o pedido. Espere o estorno. | | `ASK_PARTIAL_REFUND` | O cliente pediu reembolso de parte dos itens. | [`TRANSACTION_ASK_REFUNDING`](/docs/webhooks/eventos/transaction-ask-refunding) | Registre o pedido. Espere o estorno. | | `REFUNDED` | O estorno foi concluído. | [`TRANSACTION_REFUNDED`](/docs/webhooks/eventos/transaction-refunded) | Revogue o acesso. | | `CHARGEBACK_APPROVED` | O banco do cliente aprovou a contestação da compra. | [`TRANSACTION_CHARGEBACK_APPROVED`](/docs/webhooks/eventos/transaction-chargeback-approved) | Revogue o acesso. | ## Detalhes que mudam a sua integração **Vencimento do PIX e do boleto.** O PIX vence quando o QR Code expira. O boleto vence 5 dias depois de criado. A PagPolar confere os vencimentos a cada 3 horas, então o status `EXPIRED` e o evento podem chegar algumas horas depois. **Pagamento atrasado não desfaz reembolso.** Se a confirmação do pagamento chega quando a venda já está em reembolso, cancelada ou contestada, o status não volta para `PAID`. **Estorno de venda não paga.** Se o gateway estorna uma venda que não contava como paga, ela vai para `CANCELED`, e o evento é `TRANSACTION_CANCELED`. **Volta para `PAID` sem evento.** Acontece quando o pedido de reembolso em `ASK_REFUND` ou `ASK_PARTIAL_REFUND` é cancelado ou recusado, e quando um item é estornado e ainda restam itens na venda. `TRANSACTION_REFUNDED` só chega quando o último item é estornado. Para saber o que aconteceu com o pedido, consulte [`GET /refunds`](/docs/referencia/reembolsos/list-refunds). Veja [Acompanhar reembolsos e chargebacks](/docs/guias/jornadas/acompanhar-reembolsos-e-chargebacks#situacoes-do-pedido). **Contestação de venda já reembolsada.** Se a venda já estava `REFUNDED` ou `CANCELED`, o status não muda, mas o evento `TRANSACTION_CHARGEBACK_APPROVED` é enviado assim mesmo. ## Status sem descrição nesta página O campo `status` também pode trazer `OPEN`, `REFUNDING`, `PARTIALLY_REFUNDED`, `ABANDONED` e `CHARGEBACK_REQUESTED`. Esta documentação ainda não descreve quando esses status aparecem. Se receber um deles, não libere o que foi vendido. ## Guias relacionados - [Catálogo de eventos](/docs/webhooks/eventos) — Todos os eventos de venda e de assinatura. - [Ciclo de vida da assinatura](/docs/guias/conceitos/ciclo-de-vida-da-assinatura) — Os status de uma assinatura. --- # Ofertas, planos e ofertas ocultas URL: https://staging.pagpolar.com/docs/guias/conceitos/ofertas-planos-e-ofertas-ocultas > Escolha entre usar o código de uma oferta e informar a oferta na hora da venda, e entenda o que é uma oferta oculta. ## Produto, oferta, plano e oferta de plano | Termo | O que é | Como criar | | --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------- | | Produto | O que você vende. Pela API, nasce como produto digital (`type: DIGITAL`). Veja [produtos físicos](/docs/guias/jornadas/criar-produto-e-oferta#crie-o-produto). | [`POST /products`](/docs/referencia/produtos/create-product) | | Oferta | Um preço de venda do produto, com os meios de pagamento e o máximo de parcelas. Um produto pode ter várias ofertas. O preço vai em `price`. | [`POST /offers`](/docs/referencia/ofertas/create-offer) | | Plano | Um produto de assinatura (`type: SUBSCRIPTION`). | [`POST /plans`](/docs/referencia/planos/create-plan) | | Oferta de plano | O preço recorrente do plano, com o ciclo de cobrança: `WEEKLY`, `MONTHLY` ou `YEARLY`. O preço vai em `price`. | [`POST /plans/{id}/offers`](/docs/referencia/planos/create-plan-offer) | A venda sempre aponta para uma **oferta**, não para o produto. Em `price` e em `offer.value`, o valor que você envia é sempre em **centavos**, como número inteiro: `4990` = R$ 49,90. Nas respostas, o valor volta em reais. Veja [Valores que você envia](/docs/guias/fundamentos/valores-datas-e-identificadores#valores-que-voce-envia). ## Meios de pagamento e valor mínimo Na oferta e na oferta de plano, cada meio de pagamento tem um valor mínimo e é desligado ao salvar quando o preço fica abaixo dele. Veja a regra, os exemplos e a edição em [Meios de pagamento e valor mínimo](/docs/guias/jornadas/criar-produto-e-oferta#meios-de-pagamento). ## Duas formas de apontar a oferta numa cobrança Nas rotas `POST /payments/pix`, `POST /payments/boleto` e `POST /payments/credit-card`, envie **uma** destas duas formas: | Campo | Quando usar | | ------------------ | --------------------------------------------------------------------------- | | `offer_identifier` | Você já criou a oferta e tem o código dela. | | `offer` | Você quer informar produto, nome e valor na hora, sem criar a oferta antes. | Enviar as duas ou nenhuma responde `400`: | Situação | `message` | | -------------- | ------------------------------------------------ | | As duas juntas | `Envie offer_identifier ou offer, nunca os dois` | | Nenhuma | `Envie offer_identifier ou offer` | A resposta da cobrança traz `offer_identifier` com o código da oferta usada. Os exemplos abaixo mostram só o campo da oferta. O restante do corpo, como `customer`, é o mesmo nas duas formas. Veja o corpo completo em [Vender com PIX ou boleto](/docs/guias/jornadas/vender-com-pix-ou-boleto). ### Com `offer_identifier` ```json { "offer_identifier": "" } ``` ### Com `offer` ```json { "offer": { "product_id": "c4d5e6f7-a8b9-4c0d-1e2f-3a4b5c6d7e8f", "name": "Consultoria avulsa", "value": 4990, "createOffer": false } } ``` | Campo de `offer` | Obrigatório | O que é | | ---------------- | ----------- | ----------------------------------------------------------------------- | | `product_id` | Sim | `id` do produto. Precisa ser da sua conta, senão a resposta é `404`. | | `name` | Sim | Nome da oferta, até 255 caracteres. | | `value` | Sim | Valor da oferta, na mesma unidade de `price`. Mínimo padrão: `500`. | | `createOffer` | Sim | `true` ou `false`, como booleano. Veja [Oferta oculta](#oferta-oculta). | ## Oferta informada na hora: reaproveitar ou criar Com `offer`, a API procura uma oferta **ativa** que tenha exatamente: * o mesmo produto; * o mesmo nome; * o mesmo valor; * a mesma visibilidade (oculta ou não). Se encontrar, usa essa oferta. Se não encontrar, cria uma nova. Enviar a mesma `offer` várias vezes não cria ofertas repetidas. Na criação, a oferta aceita cartão com o maior número de parcelas que respeita a parcela mínima, até 12. Veja [A regra da parcela mínima](/docs/guias/jornadas/criar-produto-e-oferta#parcela-minima). ## Oferta oculta `createOffer` decide se a oferta criada fica visível: | `createOffer` | Resultado | | ------------- | -------------------------------------------------------------- | | `true` | Oferta comum, visível. Funciona com `offer_identifier` depois. | | `false` | Oferta **oculta**. | A oferta oculta só funciona pela API, enviando `offer` de novo. Ela não aparece nem funciona nestes lugares: | Onde | O que acontece | | ---------------------------------- | ------------------------------------- | | `GET /offers/by-product/{id}` | Não aparece na lista. | | `GET /offers/{identifier}` | `404 Oferta não encontrada`. | | `offer_identifier` numa cobrança | `404 Oferta não encontrada`. | | `POST /plans/offer/{id}/subscribe` | `404 Oferta de plano não encontrada`. | Use a oferta oculta quando o preço é decidido pelo seu sistema na hora, por exemplo um orçamento, e você não quer essa oferta nas listagens. ## Regras conferidas em toda cobrança Com qualquer uma das duas formas, a API confere a oferta antes de cobrar: | Regra | Resposta quando falha | | ------------------------------------------- | ------------------------------------------------------------------------------------------------ | | A oferta está ativa. | `409` com `Oferta inativa` | | A oferta não expirou. | `409` com `Oferta expirada` | | O meio de pagamento está ligado na oferta. | `409` com `Método de pagamento PIX não habilitado para esta oferta` (ou `BOLETO`, `CREDIT_CARD`) | | As parcelas não passam do máximo da oferta. | `400` com `Número de parcelas acima do permitido para esta oferta (máximo N)` | | A quantidade respeita a oferta. | `400` | ## Parcelas: oferta avulsa e oferta de plano Na oferta avulsa, o máximo é o valor que você definir em `max_credit_card_installments`, respeitando a [parcela mínima](/docs/guias/jornadas/criar-produto-e-oferta#parcela-minima). Sem o campo, a API usa 12. Na oferta de plano, só `1`: valor maior responde `400`. A assinatura pela API só aceita cartão. A oferta de plano precisa estar com o cartão ligado, senão a resposta é `409`. ## Guias relacionados - [Valores, datas e identificadores](/docs/guias/fundamentos/valores-datas-e-identificadores) — Centavos no envio, reais na resposta, e a parcela mínima. - [Início rápido](/docs/guias/inicio-rapido) — Crie produto, oferta e a primeira venda PIX. --- # Ambientes e URL base URL: https://staging.pagpolar.com/docs/guias/fundamentos/ambientes > Descubra o endereço da API, o que muda entre Produção e Homologação e por que a chamada deve partir do seu servidor. ## URL base A URL base da API da PagPolar é: ```text https://api.pagpolar.com/v1 ``` Nesta documentação, as rotas aparecem sem a URL base, como `GET /me` ou `POST /payments/pix`. Para chamar uma rota, junte a URL base com o caminho da rota: `https://api.pagpolar.com/v1` + `/me` = `https://api.pagpolar.com/v1/me`. Não repita o `/v1` no caminho nem deixe barra dupla. Os exemplos em cURL e Node.js já usam a URL completa. Existe um único endereço. Produção e Homologação usam o mesmo. ## Produção e Homologação Toda credencial pertence a um ambiente. Você escolhe o ambiente ao criar a credencial e **não consegue mudar depois**. | Ambiente no painel | Valor em `environment` | A chave começa com | Onde a chamada é processada | | ------------------ | ---------------------- | ------------------ | ----------------------------------------------- | | Produção | `PRODUCTION` | `pgp_live_` | Na sua conta. As cobranças são reais. | | Homologação | `STAGING` | `pgp_test_` | No ambiente de testes. Nenhuma cobrança é real. | As duas chaves usam a **mesma URL base**. A PagPolar reconhece a chave de Homologação, ou o token obtido com ela, e leva a chamada para o ambiente de testes. Para saber o ambiente de uma chave, chame [`GET /me`](/docs/referencia/autenticacao/get-current-credential) e leia `environment`. ## Ambiente de testes Ao criar a chave de Homologação, a PagPolar prepara uma **conta de testes** separada da sua conta real, com dados fictícios e cadastro já aprovado. A chave só funciona depois que a conta de testes fica pronta. Veja as etiquetas e as regras da chave em [A chave de Homologação](/docs/guias/fundamentos/credenciais#homologacao). * Produtos, ofertas, vendas e clientes criados com a chave de Homologação ficam **só** no ambiente de testes. Eles não aparecem na sua conta real. * PIX, boleto e cartão criados com a chave de Homologação **não cobram ninguém**. * Se o ambiente de testes estiver fora do ar, veja [Ambiente de testes indisponível](/docs/guias/fundamentos/erros#sandbox-indisponivel). ## Comprar no ambiente de testes No ambiente de testes, o pagamento passa por um **gateway de testes**. Ele aceita dados fictícios e aprova o pagamento sozinho: | Dado | O que usar | | --------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- | | CPF do cliente e do titular | Qualquer CPF **válido** (com dígitos verificadores corretos). Não precisa ser de uma pessoa real. Geradores de CPF de teste servem. | | Cartão de crédito | Número `4000 0000 0000 0010`, CVV `123`, qualquer nome de titular e qualquer validade futura. | | PIX | Crie a cobrança normalmente. O QR Code é gerado, mas não precisa ser pago. | PIX e cartão são **aprovados cerca de 30 segundos** depois da criação da cobrança. Nesse momento a venda passa a `PAID`, `paid_at` é preenchido e o evento [`TRANSACTION_PAID`](/docs/webhooks/eventos/transaction-paid) chega na URL do webhook da chave de Homologação. Use esse intervalo para testar o fluxo de espera do webhook e a consulta da venda. > **Outros números de cartão** > > Qualquer outro número de cartão pode ser recusado ou ficar sem resposta no gateway de testes. Para simular o caminho feliz, use o cartão acima. ## Onde o ambiente faz diferença Com a chave de Homologação, **todas** as rotas rodam no ambiente de testes. Estas cobram, mudam, cancelam ou devolvem dinheiro de verdade quando a chamada usa a chave de Produção: * `POST /payments/pix` * `POST /payments/boleto` * `POST /payments/credit-card` * `POST /plans/offer/{id}/subscribe` * `POST /subscriptions/{id}/plan-change` * `PATCH /subscriptions/{id}/card` * `DELETE /subscriptions/{id}` (cancela a assinatura; não pode ser desfeito) * `POST /refunds` ## Testar pelo portal As páginas da [Referência da API](/docs/referencia) têm o botão **Send**, que executa a operação no ambiente de testes sem você digitar chave nem token: 1. Entre no painel da PagPolar **neste navegador**. 2. Tenha uma chave de Homologação **pronta**. IPs autorizados da chave não valem para o playground: ele passa por essa restrição. 3. Abra uma operação na referência, preencha os campos e clique em **Send**. O portal gera sozinho um token de 15 minutos para a sua chave de Homologação e envia a requisição por ele. Por isso o playground não tem campo `Authorization`: a autenticação é feita pelo portal. O playground só alcança o ambiente de testes: não há como criar uma cobrança real por ele. | Resposta do playground | O que fazer | | --------------------------------------------------------- | --------------------------------------------------------- | | `401` com `playground_login_required` | Entre no painel neste navegador e tente de novo. | | `404` com a mensagem "Nenhuma chave de Homologação ativa" | Crie uma chave de Homologação em **Configurações → API**. | | `409` avisando que o ambiente ainda não está pronto | Espere a chave aparecer como pronta. | `POST /auth/token` não roda pelo playground, porque ele recebe a chave de API. Teste essa rota pelo seu servidor. ## Chame a API do seu servidor Faça as chamadas a partir do seu servidor (back-end), nunca a partir do navegador do cliente: * **A chave é secreta.** Quem tiver a chave consegue criar cobranças e ler os dados dos seus clientes. * **O navegador bloqueia a chamada.** A API só libera chamadas de navegador de uma lista fixa de origens da PagPolar. Uma chamada feita pelo JavaScript do seu site é bloqueada pelo navegador. O fluxo certo é: o seu site chama o seu servidor, e o seu servidor chama a API com o token de acesso. A chave e o token ficam no servidor, nunca no navegador. ```mermaid sequenceDiagram autonumber participant N as Navegador do cliente participant S as Seu servidor participant A as API PagPolar N->>S: pedido de compra S->>A: POST /payments/pix com o token no header Authorization A-->>S: 201 com pix.qr_code S-->>N: QR Code para o cliente pagar ``` ## Próximos passos - [Autenticação](/docs/guias/fundamentos/autenticacao) — Troque a chave pelo token e entenda os erros 401 e 403. - [Início rápido](/docs/guias/inicio-rapido) — Faça a primeira venda PIX. --- # Autenticação URL: https://staging.pagpolar.com/docs/guias/fundamentos/autenticacao > Troque a chave de API pelo token de acesso, envie o token em Authorization e entenda cada resposta 401 e 403. A API usa dois valores diferentes, e cada um tem um lugar só: | Valor | Onde vai | Para que serve | | --------------- | ---------------------------------------------------------------- | ------------------------ | | Chave de API | Header `X-API-Key`, apenas em `POST /auth/token` | Obter o token de acesso. | | Token de acesso | Header `Authorization: Bearer `, em todas as outras rotas | Autenticar cada chamada. | A chave é criada junto com a credencial, no painel. Veja [Credenciais da API](/docs/guias/fundamentos/credenciais). > **A chave só entra em POST /auth/token** > > As demais rotas, como `GET /me` e `POST /payments/pix`, esperam o token de acesso no header `Authorization`. Enviar `X-API-Key` nelas responde `401`. ## Troque a chave pelo token `POST /auth/token` é a única rota que recebe a chave. Ela não tem corpo e não usa `Idempotency-Key`. #### cURL ```bash curl -X POST "https://api.pagpolar.com/v1/auth/token" \ -H "X-API-Key: " ``` #### Node.js ```js const response = await fetch('https://api.pagpolar.com/v1/auth/token', { method: 'POST', headers: { 'X-API-Key': '' }, }); console.log(response.status, await response.json()); ``` A URL base da API é `https://api.pagpolar.com/v1`. Veja [Ambientes e URL base](/docs/guias/fundamentos/ambientes#url-base). Resposta `200`: ```json { "data": { "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", "token_type": "Bearer", "expires_in": 86400 } } ``` | Campo | O que significa | | -------------- | --------------------------------------------------------------------------------- | | `access_token` | O token que vai no header `Authorization` das outras rotas. | | `token_type` | Sempre `Bearer`. | | `expires_in` | Segundos até o token expirar, contados a partir da emissão. `86400` são 24 horas. | Contrato completo: [`POST /auth/token`](/docs/referencia/autenticacao/create-access-token). ## Envie o token em `Authorization` Todas as outras rotas exigem o header `Authorization` no formato `Bearer `. O jeito mais rápido de testar é chamar `GET /me`: #### cURL ```bash curl "https://api.pagpolar.com/v1/me" \ -H "Authorization: Bearer " ``` #### Node.js ```js const response = await fetch('https://api.pagpolar.com/v1/me', { headers: { Authorization: `Bearer ${accessToken}` }, }); console.log(response.status, await response.json()); ``` Se o token estiver certo, a resposta é `200`: ```json { "data": { "credential_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7", "environment": "PRODUCTION", "rate_limit_per_minute": 120 } } ``` | Campo | O que significa | | ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `credential_id` | Id da credencial dona da chave que gerou o token. | | `environment` | Ambiente da credencial: `PRODUCTION` ou `STAGING`. Com `STAGING`, as chamadas vão para o [ambiente de testes](/docs/guias/fundamentos/ambientes#ambiente-de-testes). | | `rate_limit_per_minute` | Quantas requisições por minuto a credencial pode fazer. | Contrato completo: [`GET /me`](/docs/referencia/autenticacao/get-current-credential). ## Validade do token O token vale **24 horas** a partir da emissão (`expires_in: 86400`). **Não existe rota de renovação nem refresh token.** Quando o token expirar, chame `POST /auth/token` de novo com a mesma chave. Guarde o token em memória e reaproveite enquanto ele valer. Pedir um token por requisição gasta uma chamada a mais em cada operação, sem nenhum ganho. ## Renove o token quando receber `401` O padrão que funciona: guardar o token em memória, usar enquanto valer e, ao receber `401`, pegar um token novo e repetir a chamada uma vez. ```js const apiUrl = 'https://api.pagpolar.com/v1'; let accessToken = null; async function obterToken() { const response = await fetch(`${apiUrl}/auth/token`, { method: 'POST', headers: { 'X-API-Key': process.env.PAGPOLAR_API_KEY }, }); if (!response.ok) { throw new Error(`Falha ao obter o token: ${response.status}`); } const { data } = await response.json(); accessToken = data.access_token; return accessToken; } export async function chamarApi(caminho, options = {}) { if (!accessToken) await obterToken(); const enviar = () => fetch(`${apiUrl}${caminho}`, { ...options, headers: { ...options.headers, Authorization: `Bearer ${accessToken}`, }, }); let response = await enviar(); if (response.status === 401) { await obterToken(); response = await enviar(); } return response; } ``` > **Repita a chamada no máximo uma vez** > > Um `401` que continua depois do token novo não é expiração: é credencial revogada, credencial expirada ou chave errada. Repetir de novo devolve o mesmo `401`. Confira a tabela de erros abaixo. Em rotas com `Idempotency-Key`, mantenha a **mesma** chave de idempotência ao repetir a chamada. Assim a cobrança não acontece duas vezes. Veja [Idempotência](/docs/guias/fundamentos/idempotencia). ## A credencial é conferida em toda requisição O token não é uma autorização isolada. A cada requisição, a API lê a credencial que está por trás do token e confere status, data de expiração e IPs autorizados. Na prática: * Revogar a credencial, editar a credencial ou mudar os IPs autorizados vale a partir da próxima requisição, inclusive para os tokens já emitidos, mesmo dentro das 24 horas. * Uma credencial revogada ou expirada responde `401` mesmo com um token dentro da validade. Não existe como revogar um token específico. Para cortar o acesso, revogue a credencial. Como fazer no painel: [Revogar uma credencial](/docs/guias/fundamentos/credenciais#revogar) e [Restringir por IP](/docs/guias/fundamentos/credenciais#ips). ## Formato da chave A chave tem quatro partes separadas por `_`. O segundo pedaço mostra o ambiente: | Ambiente | Começa com | Formato | | ----------- | ----------- | ------------------------------------------------ | | Produção | `pgp_live_` | `pgp_live_` + 8 caracteres + `_` + 48 caracteres | | Homologação | `pgp_test_` | `pgp_test_` + 8 caracteres + `_` + 48 caracteres | Os caracteres são hexadecimais: números de 0 a 9 e letras de `a` a `f`. Chaves antigas não têm o pedaço `live` ou `test` (`pgp_` + 8 caracteres + `_` + segredo). Elas continuam válidas em `POST /auth/token`. ## Restrição por IP Se a credencial tem **IPs autorizados**, a API só aceita requisições que venham de um desses IPs. A conferência acontece nos dois lugares: em `POST /auth/token` e em todas as rotas que usam o token. A API descobre o IP da requisição nesta ordem: 1. o valor do header `X-Real-IP`; 2. o **último** IP do header `X-Forwarded-For`; 3. o IP da conexão. Cadastre cada IP de saída do seu servidor, um por um. A comparação é exata: o IP da requisição precisa ser igual a um item da lista. Uma faixa de IPs, como `203.0.113.0/24`, não é tratada como faixa. ## Respostas de erro de autenticação Todas seguem o [formato de erro](/docs/guias/fundamentos/erros). O `code` é `unauthorized` no 401 e `forbidden` no 403. | Status | `message` | Onde | Causa | O que fazer | | ------ | --------------------------------------------------------------------------------------------------------------- | ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- | | 401 | `Header X-API-Key é obrigatório` | `POST /auth/token` | O header não foi enviado ou está vazio. | Envie a chave no header `X-API-Key`. | | 401 | `Header Authorization é obrigatório. Envie "Authorization: Bearer " com o token de POST /v1/auth/token.` | Demais rotas | O header não foi enviado ou está vazio. | Envie o token em `Authorization`. | | 401 | `Header Authorization inválido. Use o formato "Bearer ".` | Demais rotas | O header veio sem `Bearer`, com outro esquema ou sem o token depois do espaço. | Monte o header como `Bearer ` seguido do `access_token`. | | 401 | `Token expirado` | Demais rotas | Passaram-se mais de 24 horas desde a emissão. | Chame `POST /auth/token` de novo com a mesma chave. | | 401 | `Token inválido` | Demais rotas | O token foi adulterado, foi assinado por outra origem ou não é um token desta API. | Descarte o token e peça outro em `POST /auth/token`. | | 401 | `Credencial de API inválida` | Todas | Em `POST /auth/token`, a chave tem formato errado, não existe ou foi copiada errado. Nas demais rotas, a credencial do token não corresponde mais à chave que o gerou. | Copie a chave de novo, sem espaços, e peça um token novo. Se perdeu a chave, revogue a credencial e crie outra. | | 401 | `Credencial de API revogada ou inativa` | Todas | A credencial foi revogada, inclusive depois da emissão do token. | Crie uma credencial nova e peça um token com a chave dela. | | 401 | `Credencial de API expirada` | Todas | A data de expiração da credencial passou, inclusive depois da emissão do token. | Crie uma credencial nova e peça um token com a chave dela. | | 403 | `IP não autorizado para esta credencial` | Todas | O IP da requisição não está na lista da credencial. | Adicione o IP na credencial ou chame a partir de um IP autorizado. | ## Guarde a chave com segurança * A chave aparece **uma única vez**, na criação. Veja [O que aparece uma única vez](/docs/guias/fundamentos/credenciais#uma-unica-vez). * Guarde a chave em uma variável de ambiente ou em um cofre de segredos do seu servidor. * Nunca coloque a chave nem o token no código do navegador, em aplicativo de celular ou no repositório. * O token também é um segredo: quem tem o token chama a API no seu lugar até ele expirar. * Se a chave vazar, [revogue a credencial](/docs/guias/fundamentos/credenciais#revogar) na hora e crie outra. ## Próximos passos - [Erros](/docs/guias/fundamentos/erros) — Leia o formato de erro e decida o que repetir. - [Limites de requisição](/docs/guias/fundamentos/limites-de-requisicao) — Saiba quantas chamadas por minuto você pode fazer. --- # Credenciais da API URL: https://staging.pagpolar.com/docs/guias/fundamentos/credenciais > Crie, restrinja por IP e revogue credenciais e veja o histórico de requisições. ## O que é uma credencial A credencial é o acesso do seu sistema à API. Cada credencial tem: | Item | O que é | | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Chave de API | Valor secreto trocado pelo token de acesso em [`POST /auth/token`](/docs/referencia/autenticacao/create-access-token). Veja [Autenticação](/docs/guias/fundamentos/autenticacao). | | Ambiente | Produção ou Homologação. Com a chave de Produção, as chamadas agem na sua conta e cobram de verdade. Com a chave de Homologação, vão para o ambiente de testes. Veja [Ambientes e URL base](/docs/guias/fundamentos/ambientes). | | IPs autorizados | Lista opcional de IPs que podem usar a chave. | | Webhook próprio | Criado automaticamente junto com a credencial. Não cadastre outro para a mesma URL. Veja [Configurar o webhook](/docs/webhooks/configurar#webhook-da-credencial). | | Limite por minuto | 120 requisições por minuto, por padrão. Veja [Limites de requisição](/docs/guias/fundamentos/limites-de-requisicao). | Toda credencial ativa acessa todas as rotas da API. ## Criar uma credencial 1. No painel, abra **Configurações → API**. 2. Clique em **Nova chave**. Abre o painel lateral **Criar nova chave de integração**. 3. Preencha os campos e clique em **Salvar**. A tela **Configurações → API** lista as credenciais da conta, com a situação, o nome, o começo da chave, o ambiente e o último uso: Ao clicar em **Nova chave**, o painel lateral pede a identificação, o webhook e os IPs autorizados: | Campo | Obrigatório | Regras | | ---------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | **Nome da integração** | Sim | Até 255 caracteres. | | **Ambiente** | Sim | **Produção** ou **Homologação**. Vem com **Produção**. Não dá para mudar depois. Com uma chave de Homologação ativa na conta, a opção **Homologação** fica desabilitada. | | **URL do webhook** | Sim | Uma URL válida. É para onde vão os avisos. | | **Eventos** | Não | Em branco, o webhook recebe todos os eventos. | | **IPs autorizados** | Não | Em branco, qualquer IP é aceito. | ## A chave de Homologação A chave de Homologação começa com `pgp_test_`. Toda chamada feita com ela, ou com o token obtido por ela, vai para o [ambiente de testes](/docs/guias/fundamentos/ambientes#ambiente-de-testes). Ao salvar a chave, a conta de testes começa a ser preparada. Na lista de **Configurações → API**, uma etiqueta ao lado do ambiente mostra em que ponto está: | Etiqueta | O que significa | O que fazer | | ---------------------------------------- | -------------------------------------------------------------------------------------------- | ------------------------------------------------------------ | | **Preparando ambiente de testes** | A PagPolar está criando a conta de testes. A chave ainda não funciona. | Atualize a tela depois de alguns instantes. | | **Ambiente de testes pronto** | A chave já funciona no ambiente de testes. | Troque a chave pelo token em `POST /auth/token` e use a API. | | **Falha ao preparar ambiente de testes** | A PagPolar tentou 5 vezes e não conseguiu. Passe o mouse sobre a etiqueta para ver o motivo. | Revogue a chave e crie outra. | Regras da chave de Homologação: * **Uma por conta.** Cada conta pode ter uma chave de Homologação ativa. Para criar outra, revogue a atual. Ela conta no [limite de 5 credenciais ativas](#limite-de-5-credenciais-ativas). * **Editar e revogar valem no ambiente de testes.** O nome e os IPs autorizados que você muda no painel passam para o ambiente de testes. Revogar a chave também. A conta de testes não é apagada: a próxima chave de Homologação usa a mesma conta, com os dados de teste que já estavam lá. ## O que aparece uma única vez Depois de salvar, a janela **Chave criada com sucesso** mostra a chave de API e o token do webhook. O próprio painel avisa: > Esta é a única vez que a chave e o token do webhook serão exibidos. Se perdê-los, será necessário revogar esta chave e criar outra. A PagPolar guarda só um resumo (hash) da chave. Nem o suporte consegue recuperar a chave depois. ## O webhook criado junto Ao criar a credencial, a PagPolar cria um webhook com a URL e os eventos que você escolheu. Veja o que ele recebe e como alterá-lo em [Configurar o webhook](/docs/webhooks/configurar#webhook-da-credencial). ## Restringir por IP Com IPs autorizados, a API recusa com `403` qualquer requisição que venha de outro IP. 1. Em **Configurações → API**, abra o menu da credencial (três pontos) e clique em **Editar**. 2. Em **IPs autorizados**, informe cada IP de saída do seu servidor. 3. Salve. O menu da credencial reúne as três ações — histórico de requisições, edição e revogação: A mudança vale a partir da próxima requisição, inclusive para os tokens já emitidos. Veja [A credencial é conferida em toda requisição](/docs/guias/fundamentos/autenticacao#credencial-por-tras-do-token) e como a API descobre o IP em [Restrição por IP](/docs/guias/fundamentos/autenticacao#restricao-por-ip). Na edição, só dá para mudar o nome e os IPs autorizados. O ambiente e o webhook não aparecem no formulário de edição. ## Expiração Uma credencial pode ter data de expiração. Depois dessa data, `POST /auth/token` e as chamadas com os tokens já emitidos respondem `401` com `Credencial de API expirada`. O formulário do painel não tem campo para essa data. ## Revogar uma credencial Revogue a credencial quando a chave vazar, quando a integração deixar de existir ou quando você perder a chave. 1. Em **Configurações → API**, abra o menu da credencial (três pontos). 2. Clique em **Revogar**. 3. Confirme em **Revogar esta chave?**. O que acontece: * A partir da próxima requisição, `POST /auth/token` e as chamadas com os tokens já emitidos respondem `401` com `Credencial de API revogada ou inativa`. Veja [A credencial é conferida em toda requisição](/docs/guias/fundamentos/autenticacao#credencial-por-tras-do-token). * O webhook da credencial é desativado. Os envios que ainda estavam na fila são descartados. * A revogação não pode ser desfeita. Para voltar a integrar, crie outra credencial. ## Limite de 5 credenciais ativas Cada conta pode ter até 5 credenciais ativas ao mesmo tempo. Ao tentar criar a sexta, o painel mostra: ```text Limite de 5 credenciais ativas atingido. Revogue uma credencial antes de criar outra. ``` Credenciais revogadas não contam no limite. Chaves de Produção e de Homologação contam juntas. ## Ver o histórico de requisições 1. Em **Configurações → API**, abra o menu da credencial (três pontos). 2. Clique em **Requisições da API**. Abre a tela **Histórico de requisições**. 3. Abra uma requisição para ver os detalhes. | Detalhe | O que mostra | | ---------------- | ------------------------------------------------------------------- | | Resultado | Se a requisição deu certo ou qual tipo de erro teve. | | Data | Quando a requisição chegou. | | Endpoint | Método e rota chamados. | | Status HTTP | Status da resposta. | | Duração | Tempo de resposta. | | ID da requisição | O mesmo `request_id` do corpo do erro e do header `X-Request-Id`. | | IP de origem | O IP que a API considerou. Útil para configurar os IPs autorizados. | | Idempotency-Key | A chave enviada, quando houver. | | Mensagem de erro | A `message` do erro, quando houver. | Na lista de credenciais, o ícone **Ver histórico** abre os envios do webhook da credencial. Veja [Entregas e retentativas](/docs/webhooks/entregas-e-retentativas). ## Próximos passos - [Autenticação](/docs/guias/fundamentos/autenticacao) — Troque a chave pelo token e entenda os erros 401 e 403. - [Configurar o webhook](/docs/webhooks/configurar) — Troque a URL e escolha os eventos. --- # Erros URL: https://staging.pagpolar.com/docs/guias/fundamentos/erros > Leia o formato de erro da API, decida o que pode ser repetido e informe o request_id ao suporte. ## Formato do erro Todo erro da API tem o mesmo formato: ```json { "error": { "code": "conflict", "message": "Oferta inativa", "request_id": "3f6c1a52-8e1d-4c0b-9a7e-2d5b6c7e8f90" } } ``` | Campo | O que significa | | ------------ | ------------------------------------------------------------------------ | | `code` | Categoria do erro. Sai do status HTTP. | | `message` | Texto que explica o caso. Use para diferenciar erros com o mesmo `code`. | | `request_id` | Id único da requisição. Informe ao suporte. | > **O code não diz o caso exato** > > Dois erros diferentes com o mesmo status têm o mesmo `code`. Exemplo: "Oferta inativa" e "Oferta expirada" são os dois `409 conflict`. Leia `message` para saber qual aconteceu. ## Códigos | `code` | Status | Quando acontece | Pode repetir? | | --------------------- | ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- | | `invalid_request` | 400 | Corpo, parâmetro ou header inválido. Regra de negócio, como parcelas acima do limite da oferta. | Não. Corrija a requisição: repetir igual dá o mesmo erro. | | `unauthorized` | 401 | A autenticação falhou. Veja [Erros comuns a todas as rotas](#erros-comuns). | Uma vez, com um token novo. | | `forbidden` | 403 | IP não autorizado na credencial, ou uma regra da conta impede a operação: produto que não permite troca de plano, ou oferta que não está marcada como selecionável pelo cliente. | Não. Corrija a causa. Veja [Trocar de plano](/docs/guias/jornadas/trocar-de-plano). | | `not_found` | 404 | O recurso não existe na sua conta: produto, oferta, venda, assinatura ou cliente. Ou a rota não existe na API. | Não. Confira o id ou o código enviado. Se a mensagem for de rota, veja [Rota inexistente](#rota-inexistente). | | `conflict` | 409 | O estado do recurso impede a operação: oferta inativa ou expirada, meio de pagamento desligado na oferta, ou requisição com a mesma `Idempotency-Key` ainda em processamento. | Só o da `Idempotency-Key`: espere alguns segundos e repita com a mesma chave. Nos outros, ajuste a oferta antes. | | `rate_limit_exceeded` | 429 | Você passou do limite de requisições. | Sim, depois do tempo do header `Retry-After`. | | `sandbox_unavailable` | 502 | Só com a chave de Homologação ou o token dela: o ambiente de testes está fora do ar ou não respondeu em 30 segundos. | Sim, depois de alguns segundos. Veja [Ambiente de testes indisponível](#sandbox-indisponivel). | | `service_unavailable` | 503 | O controle de limite está fora do ar numa rota que movimenta dinheiro. A requisição não foi processada. | Sim, depois de alguns segundos. Nas rotas com `Idempotency-Key`, use a mesma chave. | | `internal_error` | 500 ou maior | Erro inesperado na API. | Com cuidado. Numa rota de cobrança, [procure a venda antes](/docs/guias/fundamentos/idempotencia#erro-nao-garante). | Qualquer `GET` pode ser repetido igual. O `code` é escolhido só pelo status HTTP, com uma exceção: o `502 sandbox_unavailable`. Um status 422 vira `unprocessable_entity`, e qualquer outro status 4xx sem nome na tabela vira `error`. ## Erros comuns a todas as rotas Estes erros podem aparecer em qualquer rota que recebe os dados ou o header citados. Os erros próprios de cada fluxo ficam na página da jornada. | Status | `code` | Quando | O que fazer | | ------ | --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | 400 | `invalid_request` | Campo obrigatório ausente. `message`: `O campo é obrigatório`, como `O campo customer.email é obrigatório`. | Envie o campo indicado. A mensagem mostra um campo por vez. | | 400 | `invalid_request` | Texto menor ou maior que o permitido. `message`: `O campo deve ter no mínimo 7 caracteres` ou `O campo deve ter no máximo 20 caracteres`. Os números 7 e 20 aparecem qualquer que seja o limite real. Exemplo: `credit_card.cvv`, que aceita 3 ou 4 caracteres, responde com essas mensagens. | Confira o limite do campo na [Referência da API](/docs/referencia), não na mensagem. | | 400 | `invalid_request` | Número fora do limite (como `per_page` acima de 100), URL ou IP inválido, data em formato errado. `message` vazia. | Confira o campo na [Referência da API](/docs/referencia). | | 400 | `invalid_request` | `customer.document` fora do formato. `message`: `Documento inválido: envie o CPF com 11 dígitos ou o CNPJ com 14 dígitos, com ou sem pontuação.` | Veja [Documento e telefone](/docs/guias/fundamentos/valores-datas-e-identificadores#documento-e-telefone). | | 400 | `invalid_request` | `holder_document` fora do formato. `message`: `Documento do titular do cartão inválido: envie o CPF com 11 dígitos ou o CNPJ com 14 dígitos, com ou sem pontuação.` | Veja [Documento e telefone](/docs/guias/fundamentos/valores-datas-e-identificadores#documento-e-telefone). | | 400 | `invalid_request` | `customer.phone` fora do formato. `message`: `Telefone inválido: envie o DDD e o número, com 10 ou 11 dígitos (12 ou 13 com o código do país 55), com ou sem pontuação.` | Veja [Documento e telefone](/docs/guias/fundamentos/valores-datas-e-identificadores#documento-e-telefone). | | 400 | `invalid_request` | `Header Idempotency-Key é obrigatório` ou `Header Idempotency-Key excede 255 caracteres`. | Envie um UUID no header. Veja [Idempotência](/docs/guias/fundamentos/idempotencia). | | 401 | `unauthorized` | Token ausente, fora do formato `Bearer `, expirado ou inválido. Credencial revogada ou expirada. Em `POST /auth/token`, chave ausente ou inválida. | Peça um token novo e repita uma vez, como em [Renove o token no 401](/docs/guias/fundamentos/autenticacao#renovar-no-401). Se continuar, leia a `message` em [Respostas de erro de autenticação](/docs/guias/fundamentos/autenticacao#erros). | | 403 | `forbidden` | `IP não autorizado para esta credencial`. | Autorize o IP na credencial. Veja [Restrição por IP](/docs/guias/fundamentos/autenticacao#restricao-por-ip). | | 409 | `conflict` | `Uma requisição com este Idempotency-Key já está em processamento`. | Espere alguns segundos e repita com a mesma chave. | | 429 | `rate_limit_exceeded` | `Limite de requisições excedido. Aguarde e tente novamente.` | Espere os segundos de `Retry-After` e repita. Veja [Limites de requisição](/docs/guias/fundamentos/limites-de-requisicao). | | 500 | `internal_error` | `Erro interno. Tente novamente ou contate o suporte informando o request_id.` A mensagem é sempre essa em erro 500 ou maior, fora o `502 sandbox_unavailable`. | Guarde o `request_id`. Numa rota de cobrança, [procure a venda antes de repetir](/docs/guias/fundamentos/idempotencia#erro-nao-garante). Se o erro continuar, fale com o suporte. | | 502 | `sandbox_unavailable` | Só com a chave de Homologação ou o token dela: o ambiente de testes está fora do ar ou não respondeu em 30 segundos. | Veja [Ambiente de testes indisponível](#sandbox-indisponivel). | | 503 | `service_unavailable` | `Serviço temporariamente indisponível. Tente novamente em instantes.` Só nas [rotas que movimentam dinheiro](/docs/guias/fundamentos/limites-de-requisicao#resposta-503). | Espere alguns segundos e repita. Nas rotas com `Idempotency-Key`, use a mesma chave. | ## Erros de validação Quando o corpo ou os parâmetros estão errados, a resposta é `400 invalid_request` com **uma** mensagem. Exemplo, ao enviar `offer_identifier` e `offer` juntos: ```json { "error": { "code": "invalid_request", "message": "Envie offer_identifier ou offer, nunca os dois", "request_id": "9b2e7c14-3a5d-4f60-8e1b-7c9d0a2b3c4d" } } ``` A mensagem mostra um problema por vez. Corrija, envie de novo e veja se aparece outro. As mensagens genéricas, inclusive as vazias, estão em [Erros comuns a todas as rotas](#erros-comuns). ## Rota inexistente Quando o método e o caminho não correspondem a nenhuma rota da API, a resposta é `404 not_found`: ```json { "error": { "code": "not_found", "message": "Rota não encontrada na API pública.", "request_id": "5d8e2a61-4b3c-4f7d-9e0a-1c2b3d4e5f60" } } ``` Confira o método e o caminho, e se a URL começa com a URL base `https://api.pagpolar.com/v1`, sem repetir o `/v1`. As rotas existentes estão na [Referência da API](/docs/referencia). O token é conferido antes da rota. Sem token válido, a resposta é `401 unauthorized`, mesmo numa rota que não existe. Com IP fora da lista autorizada, é `403 forbidden`. ## Ambiente de testes indisponível Quando o [ambiente de testes](/docs/guias/fundamentos/ambientes#ambiente-de-testes) está fora do ar ou demora mais de 30 segundos para responder, as chamadas feitas com a chave de Homologação, ou com o token dela, recebem `502`: ```json { "error": { "code": "sandbox_unavailable", "message": "Ambiente de testes indisponível no momento. Tente novamente em instantes.", "request_id": null } } ``` * `request_id` chega `null`, e a resposta não traz o header `X-Request-Id`. * As chaves de Produção não são afetadas. * Quando o ambiente de testes responde, o status e o corpo da resposta dele chegam sem mudança. Os erros seguem o mesmo formato desta página. Espere alguns segundos e repita. Se o erro veio depois de 30 segundos numa rota de cobrança, a venda de teste pode ter sido criada: [procure a venda antes de repetir](/docs/guias/fundamentos/idempotencia#erro-nao-garante). ## Onde encontrar o `request_id` O mesmo valor aparece em dois lugares: * no campo `error.request_id` do corpo do erro; * no header `X-Request-Id` de **toda** resposta da API, inclusive as de sucesso. A única exceção é o `502 sandbox_unavailable`. Grave o `X-Request-Id` nos logs do seu sistema. No painel, o **Histórico de requisições** da credencial mostra o mesmo valor em **ID da requisição**. Veja [Credenciais](/docs/guias/fundamentos/credenciais#historico). ## Próximos passos - [Idempotência](/docs/guias/fundamentos/idempotencia) — Repita uma cobrança sem cobrar duas vezes. - [Limites de requisição](/docs/guias/fundamentos/limites-de-requisicao) — Evite o erro 429. --- # Glossário URL: https://staging.pagpolar.com/docs/guias/fundamentos/glossario > Consulte o significado de cada termo usado nesta documentação e o campo da API que corresponde a ele. Cada termo tem um único significado em toda a documentação. ## Termos da venda | Termo | Significado | Campo na API | | ------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------- | | **Cliente** | Pessoa que compra de você. | `customer` na API; `buyer` no webhook | | **Produto** | O que você vende. Veja [Criar produto e oferta](/docs/guias/jornadas/criar-produto-e-oferta). | `Product` | | **Oferta** | Preço de venda de um produto, com meios de pagamento e parcelas. | `Offer`, `price` | | **Valor mínimo do meio de pagamento** | Menor preço de oferta com que um meio de pagamento fica ligado. Não é a parcela mínima. Veja [Meios de pagamento](/docs/guias/jornadas/criar-produto-e-oferta#meios-de-pagamento). | `payment_methods` | | **Parcela mínima** | Menor valor de cada parcela no cartão numa oferta avulsa. Veja [A regra da parcela mínima](/docs/guias/jornadas/criar-produto-e-oferta#parcela-minima). | `max_credit_card_installments` | | **Código da oferta** | Código que identifica a oferta, como `PPP1234567890`. Veja [Códigos com prefixo](/docs/guias/fundamentos/valores-datas-e-identificadores#codigos-com-prefixo). | `identifier`, `offer_identifier` | | **Oferta oculta** | Oferta criada na hora da cobrança que não aparece nas listagens. Veja [Oferta oculta](/docs/guias/conceitos/ofertas-planos-e-ofertas-ocultas#oferta-oculta). | `offer.createOffer` | | **Venda** | Registro de uma cobrança a um cliente, com status próprio. | `Sale` na API; `transaction` no webhook | | **Código da venda** | Código que identifica a venda, como `PPO0087103960`. Veja [Códigos com prefixo](/docs/guias/fundamentos/valores-datas-e-identificadores#codigos-com-prefixo). | `identifier`, `sale_identifier` | | **Referência externa** | Código do seu pedido, enviado por você na cobrança. | `external_reference` | | **Cobrança** | O pedido de pagamento feito ao cliente por PIX, boleto ou cartão. | — | | **Gateway** | Empresa que processa o pagamento e confirma, recusa ou estorna. No ambiente de testes é um [gateway de testes](/docs/guias/fundamentos/ambientes#dados-de-teste). | — | | **Afiliado** | Pessoa que divulga o produto e recebe comissão pela venda. | `affiliate_identifier` | | **Pedido de reembolso** | Pedido do cliente para devolver o dinheiro, total ou parcial. | `GET /refunds` | | **Estorno** | Devolução efetiva do dinheiro pelo gateway. A venda fica `REFUNDED`. | `TRANSACTION_REFUNDED` | | **Chargeback** | Contestação da compra pelo banco do cliente. | `TRANSACTION_CHARGEBACK_APPROVED` | | **Canal da venda** | Por onde a venda nasceu: checkout, API, venda manual ou prêmio de afiliado. | `source.channel` no webhook | ## Termos da assinatura | Termo | Significado | Campo na API | | --------------------- | ---------------------------------------------------------------------------------------------------------- | -------------------------------------- | | **Plano** | Produto do tipo assinatura. | `type: SUBSCRIPTION` | | **Oferta de plano** | Preço recorrente de um plano, com ciclo. | `cycle`: `WEEKLY`, `MONTHLY`, `YEARLY` | | **Ciclo** | Intervalo entre duas cobranças da assinatura. Na venda, `cycle` é a posição do ciclo: 1, 2, 3... | `cycle`, `cycle_interval` | | **Assinatura** | Vínculo de um cliente com uma oferta de plano, cobrado a cada ciclo. | `Subscription` | | **Prazo de carência** | Dias depois da data de renovação em que uma assinatura PIX ou boleto ainda pode ser paga antes de expirar. | — | ## Termos da integração | Termo | Significado | Campo na API | | ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------- | | **Vendedor** | Dono da conta PagPolar que integra o próprio sistema. Nesta documentação, "você". | — | | **Credencial** | Registro que dá acesso à API, com ambiente, IPs autorizados e webhook próprio. | `credential_id` em `GET /me` | | **Chave de API** | Valor secreto trocado pelo token de acesso. Veja [Autenticação](/docs/guias/fundamentos/autenticacao). | `X-API-Key` | | **Token de acesso** | Valor devolvido por `POST /auth/token` que autentica as outras rotas. Veja [Validade do token](/docs/guias/fundamentos/autenticacao#validade). | `access_token` | | **Token do webhook** | Valor que a PagPolar envia em cada aviso para você confirmar que ele é dela. Veja [Autenticar as requisições](/docs/webhooks/autenticar-requisicoes). | `webhook_authorization` | | **Ambiente** | Produção ou Homologação. Define onde a chamada é processada: na sua conta ou no ambiente de testes. | `environment`: `PRODUCTION`, `STAGING` | | **Produção** | Ambiente da chave `pgp_live_`. As chamadas agem na sua conta e as cobranças são reais. | `environment: PRODUCTION` | | **Homologação** | Ambiente da chave `pgp_test_`. As chamadas, com a chave ou com o token dela, vão para o ambiente de testes. | `environment: STAGING` | | **Ambiente de testes** | Ambiente separado da sua conta real, onde nada é cobrado de verdade. Veja [Ambiente de testes](/docs/guias/fundamentos/ambientes#ambiente-de-testes). | — | | **Conta de testes** | Conta criada pela PagPolar no ambiente de testes quando você cria a chave de Homologação. | — | | **Playground** | Botão **Send** das páginas da Referência da API, que executa a operação no ambiente de testes. Veja [Testar pelo portal](/docs/guias/fundamentos/ambientes#playground). | — | | **Webhook** | Requisição `POST` que a PagPolar envia ao seu servidor quando algo acontece. | — | | **Evento** | Tipo do acontecimento avisado pelo webhook. | `event` | | **Entrega** | Cada envio de um evento para a sua URL, com sucesso ou falha. | — | | **Chave de idempotência** | Valor único por operação que evita cobrar duas vezes. Veja [Idempotência](/docs/guias/fundamentos/idempotencia). | `Idempotency-Key` | | **Limite de requisições** | Máximo de chamadas por minuto da credencial. Veja [Limites de requisição](/docs/guias/fundamentos/limites-de-requisicao). | `rate_limit_per_minute`, `X-RateLimit-Limit` | --- # Idempotência URL: https://staging.pagpolar.com/docs/guias/fundamentos/idempotencia > Envie o header Idempotency-Key e repita uma cobrança sem cobrar o cliente duas vezes. ## Por que usar A rede falha. Às vezes você envia uma cobrança e não recebe a resposta. Você não sabe se a cobrança foi criada. Se você repetir a requisição com a **mesma** `Idempotency-Key`, a API reconhece a repetição e devolve a resposta da primeira vez, sem criar outra cobrança. ## Rotas que exigem `Idempotency-Key` | Rota | O que faz | | -------------------------------------- | -------------------------------- | | `POST /payments/pix` | Cria uma venda PIX. | | `POST /payments/boleto` | Cria uma venda por boleto. | | `POST /payments/credit-card` | Cria uma venda no cartão. | | `POST /plans/offer/{id}/subscribe` | Cria uma assinatura. | | `POST /subscriptions/{id}/plan-change` | Troca o plano de uma assinatura. | | `POST /refunds` | Reembolsa uma venda. | Nessas rotas, o header é **obrigatório** e aceita até 255 caracteres. Sem ele, vazio ou maior que isso, a resposta é `400`. As mensagens estão em [Erros comuns a todas as rotas](/docs/guias/fundamentos/erros#erros-comuns). As outras rotas ignoram o header. `POST /products`, `POST /offers`, `POST /plans` e `POST /plans/{id}/offers` criam um registro novo a cada chamada. ## Como gerar a chave * Gere **uma chave por operação de negócio**. Exemplo: uma chave para "cobrar o pedido 1234 no PIX". * Use um UUID. Em Node.js: `crypto.randomUUID()`. * **Grave a chave no seu banco antes de enviar a requisição.** Assim, depois de uma queda, você repete com a mesma chave. * Não gere uma chave nova para repetir a mesma cobrança. Chave nova é cobrança nova. A chave vale por credencial. A mesma chave usada em outra credencial é tratada como outra chave. Os exemplos abaixo usam um token de acesso. Veja [Autenticação](/docs/guias/fundamentos/autenticacao#obter-o-token). #### cURL ```bash curl -X POST "https://api.pagpolar.com/v1/payments/pix" \ -H "Authorization: Bearer " \ -H "Idempotency-Key: 2f1c4d2e-8f4a-4c1e-9f7b-6a2d1e3c5b90" \ -H "Content-Type: application/json" \ -d '{ "offer_identifier": "", "external_reference": "PEDIDO-1234", "customer": { "name": "Maria Silva", "email": "cliente@exemplo.com", "document": "", "phone": "" } }' ``` #### Node.js ```js import { randomUUID } from 'node:crypto'; const idempotencyKey = randomUUID(); const response = await fetch('https://api.pagpolar.com/v1/payments/pix', { method: 'POST', headers: { Authorization: `Bearer ${accessToken}`, 'Idempotency-Key': idempotencyKey, 'Content-Type': 'application/json', }, body: JSON.stringify({ offer_identifier: '', external_reference: 'PEDIDO-1234', customer: { name: 'Maria Silva', email: 'cliente@exemplo.com', document: '', phone: '', }, }), }); console.log( response.status, response.headers.get('Idempotency-Replayed'), await response.json(), ); ``` ## O que acontece ao repetir O diagrama mostra o caminho de uma requisição com `Idempotency-Key`. ```mermaid flowchart TD A[Requisição com Idempotency-Key] --> B{Dentro do limite de requisições?} B -->|Não| B1[429 e a chave não é registrada] B -->|Sim| C{Corpo válido e header presente?} C -->|Não| C1[400 e a chave não é registrada] C -->|Sim| D{Esta credencial já usou a chave?} D -->|Não| E[A API processa a requisição] D -->|Sim, ainda em processamento| F[409 conflict] D -->|Sim, terminou com 2xx nas últimas 24 horas| G[Mesma resposta de antes com o header Idempotency-Replayed] E --> H{A resposta foi 2xx?} H -->|Sim| I[Resposta guardada por 24 horas] H -->|Não| J[Chave liberada para uma nova tentativa] ``` | Situação da chave | O que a API faz | | ----------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ | | Nunca usada | Processa normalmente. | | Em processamento | Responde `409` com `Uma requisição com este Idempotency-Key já está em processamento`. Espere alguns segundos e repita. | | Terminou com sucesso (2xx) há menos de 24 horas | Devolve o mesmo status e o mesmo corpo da primeira resposta, com o header `Idempotency-Replayed: true`. Nada é criado de novo. | | Terminou com erro (qualquer status fora de 2xx) | A chave é liberada. A próxima requisição com a mesma chave é processada de novo. | | Usada há mais de 24 horas | Vira uma chave nova. A requisição é processada de novo. | A marca de "em processamento" dura até 60 segundos. > **O corpo não é comparado** > > A API olha só a chave. Se você repetir a chave com um corpo diferente dentro de 24 horas, recebe a resposta da **primeira** requisição, e o corpo novo é ignorado. Para uma cobrança diferente, use uma chave diferente. ## Erro não garante que nada foi criado Numa rota de cobrança, a venda pode ficar registrada mesmo quando a resposta é um erro, como `500`, ou quando a resposta não chega. Como a chave é liberada depois de um erro, repetir com a mesma chave processa a cobrança de novo. Antes de repetir uma cobrança que falhou, procure a venda pela sua referência: 1. Envie `external_reference` com o código do seu pedido em toda cobrança. 2. Depois de um erro, consulte `GET /sales?external_reference=PEDIDO-1234`. 3. Se a venda já existe, não repita a cobrança. ## Próximos passos - [Erros](/docs/guias/fundamentos/erros) — Saiba quais erros é seguro repetir. - [Limites de requisição](/docs/guias/fundamentos/limites-de-requisicao) — Veja o limite por minuto e o 503 das rotas que movimentam dinheiro. --- # Limites de requisição URL: https://staging.pagpolar.com/docs/guias/fundamentos/limites-de-requisicao > Saiba quantas chamadas por minuto sua credencial pode fazer, como os limites se somam e o que fazer no 429 e no 503. ## Limite geral da credencial Cada credencial pode fazer um número máximo de requisições por minuto. Não existe limite separado por rota nem por grupo de rotas: **toda** chamada conta na mesma contagem da credencial. Uma venda PIX, uma listagem e uma troca de cartão consomem exatamente o mesmo 1 de 120. * O padrão é **120 requisições por minuto**. * O valor da sua credencial aparece em `rate_limit_per_minute`, na resposta de [`GET /me`](/docs/referencia/autenticacao/get-current-credential). * Você não altera o limite. Veja [Como aumentar o limite](#aumentar-limite). A contagem usa uma janela móvel: a API conta as requisições feitas nos **últimos 60 segundos**, a cada nova requisição. O que conta e o que fica de fora: * `POST /auth/token` **não conta** no limite. * Requisições recusadas com `429` **também contam**. Repetir sem esperar mantém você bloqueado. * Requisições recusadas na autenticação (`401`) ou por IP não autorizado (`403`) não contam. ## Headers de limite As respostas trazem estes headers: | Header | O que significa | | ----------------------- | ---------------------------------------------------------- | | `X-RateLimit-Limit` | O `rate_limit_per_minute` da credencial. | | `X-RateLimit-Remaining` | Quantas requisições ainda cabem na janela de 60 segundos. | | `X-RateLimit-Reset` | Tamanho da janela, em segundos. Hoje é sempre `60`. | | `Retry-After` | Só no `429`. Quantos segundos esperar. Hoje é sempre `60`. | ## Resposta 429 Quando o limite estoura, a resposta é: ```json { "error": { "code": "rate_limit_exceeded", "message": "Limite de requisições excedido. Aguarde e tente novamente.", "request_id": "c1d2e3f4-a5b6-4c7d-8e9f-0a1b2c3d4e5f" } } ``` O que fazer: 1. Pare de enviar requisições para essa credencial. 2. Espere os segundos de `Retry-After`. 3. Repita. Numa rota de cobrança, repita com a **mesma** `Idempotency-Key`. Para não chegar no limite, veja as dicas em [Como aumentar o limite](#aumentar-limite). ## Como aumentar o limite Quem aumenta o `rate_limit_per_minute` da credencial é o suporte. Antes de pedir, reduza as chamadas: * Use `per_page=100` nas listagens, para buscar mais itens por chamada. Veja [Paginação e filtros](/docs/guias/fundamentos/paginacao-e-filtros). * Receba os webhooks em vez de consultar a API em loop para saber se algo mudou. Veja [Webhooks](/docs/webhooks). 1. **Veja o limite atual** Chame [`GET /me`](/docs/referencia/autenticacao/get-current-credential), como em [Envie o token em `Authorization`](/docs/guias/fundamentos/autenticacao#enviar-o-token), e anote `rate_limit_per_minute` e `credential_id`: o suporte precisa dele. 2. **Fale com o suporte** Informe: | O quê | Exemplo | | ------------------------------------ | ----------------------------------------------------------------------- | | O `credential_id` da credencial | `7c9e6679-7425-40de-944b-e07fc1f90ae7` | | O limite por minuto que você precisa | `300` | | O uso esperado | Picos de venda num lançamento, ou sincronização do catálogo de ofertas. | O ajuste vale para a credencial informada. Se você usa mais de uma credencial, informe cada `credential_id`. 3. **Confira o novo limite** Depois do ajuste, chame `GET /me` de novo e confira `rate_limit_per_minute`. O header `X-RateLimit-Limit` das respostas também mostra o valor. A API guarda os dados da credencial em cache por até 1 minuto: se ainda aparecer o valor antigo, espere 1 minuto e confira de novo. Não é preciso gerar outra chave nem outro token: o token que você já usa continua valendo. ## Resposta 503 Só as rotas que movimentam dinheiro respondem `503`: as três de cobrança, a criação de assinatura, o reembolso, a troca de plano e a troca de cartão. Isso acontece quando o controle de limite fica fora do ar: ```json { "error": { "code": "service_unavailable", "message": "Serviço temporariamente indisponível. Tente novamente em instantes.", "request_id": "d2e3f4a5-b6c7-4d8e-9f0a-1b2c3d4e5f60" } } ``` A requisição **não** foi processada. Espere alguns segundos e repita. Nas rotas com `Idempotency-Key`, use a mesma chave. A troca de cartão não usa `Idempotency-Key`. Nas outras rotas, se o controle de limite ficar fora do ar, a requisição segue normalmente. ## Próximos passos - [Idempotência](/docs/guias/fundamentos/idempotencia) — Repita uma cobrança sem cobrar duas vezes. - [Paginação e filtros](/docs/guias/fundamentos/paginacao-e-filtros) — Busque mais dados com menos chamadas. --- # Paginação e filtros URL: https://staging.pagpolar.com/docs/guias/fundamentos/paginacao-e-filtros > Percorra qualquer listagem da API do começo ao fim e use os filtros que cada uma realmente aplica. ## Parâmetros `page` e `per_page` Toda listagem aceita dois parâmetros na query string: | Parâmetro | O que faz | Padrão | Limites | | ---------- | --------------------------------- | ------ | -------------------- | | `page` | Número da página, começando em 1. | `1` | Inteiro, mínimo 1. | | `per_page` | Quantos itens vêm por página. | `25` | Inteiro, de 1 a 100. | `per_page` acima de 100 responde `400 invalid_request`, com `message` vazia. Uma página depois da última responde `200` com `data` vazio. ## Formato da resposta Toda listagem responde com `data` e `meta`: ```json { "data": [ { "id": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d", "identifier": "PPO9876543210", "status": "PAID", "total_amount": 97 } ], "meta": { "page": 1, "per_page": 25, "total": 58, "total_pages": 3 } } ``` Resposta resumida. Os campos de cada item estão na [Referência da API](/docs/referencia). | Campo de `meta` | O que significa | | --------------- | ------------------------------------------ | | `page` | Página devolvida. | | `per_page` | Itens por página usados. | | `total` | Total de itens que atendem aos filtros. | | `total_pages` | Total de páginas. `0` quando não há itens. | ## Ordem dos resultados Todas as listagens vêm da **mais nova para a mais antiga**, pela data de criação (`created_at`). Não há parâmetro para mudar a ordem. ## Filtros de cada listagem Cada listagem aceita só os filtros da tabela, além de `page` e `per_page`. Um filtro que a rota não aplica, ou um valor fora da lista, responde `400 invalid_request`. | Rota | Filtros aceitos | | ----------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- | | [`GET /sales`](/docs/referencia/vendas/list-sales) | `status`, `payment_method`, `created_from`, `created_to`, `external_reference`, `product_id`, `customer_email`, `customer_document` | | [`GET /subscriptions`](/docs/referencia/assinaturas/list-subscriptions) | `status`, `plan_id`, `customer_email`, `customer_document`, `created_from`, `created_to` | | [`GET /refunds`](/docs/referencia/reembolsos/list-refunds) | `status`, `sale_identifier`, `customer_email`, `customer_document`, `created_from`, `created_to` | | [`GET /customers`](/docs/referencia/clientes/list-customers) | `email`, `name`, `document` | | [`GET /products`](/docs/referencia/produtos/list-products) | `name`, `type`, `is_active` | | [`GET /plans`](/docs/referencia/planos/list-plans) | `name`, `is_active` | | [`GET /offers/by-product/{id}`](/docs/referencia/ofertas/list-product-offers) | `title`, `is_active` | | [`GET /plans/{id}/offers`](/docs/referencia/planos/list-plan-offers) | `title`, `is_active` | Os valores aceitos em `status`, `payment_method` e `type` estão em cada rota da [Referência da API](/docs/referencia). O `status` de `GET /sales` usa as situações da venda, e o de `GET /subscriptions`, as da assinatura: um não vale no outro. Você pode combinar filtros na mesma chamada. A listagem traz só os itens que atendem a **todos** eles. ### Como cada tipo de filtro compara | Tipo | Filtros | Como compara | Exemplo | | --------------- | ---------------------------------- | -------------------------------------------------------------------------------- | ------------------------------------------------------------ | | Busca por texto | `name`, `title` | Parte do texto, sem diferenciar maiúsculas de minúsculas. | `name=curso` encontra "Curso de Excel" e "Minicurso". | | E-mail | `email`, `customer_email` | E-mail completo, sem diferenciar maiúsculas de minúsculas. | `customer_email=Maria@Email.com` encontra `maria@email.com`. | | Documento | `document`, `customer_document` | CPF com 11 dígitos ou CNPJ com 14, com ou sem pontuação. Compara só os dígitos. | `123.456.789-09` e `12345678909` encontram o mesmo cliente. | | Id | `product_id`, `plan_id` | O `id` exato, no formato uuid. | `plan_id=3fa85f64-5717-4562-b3fc-2c963f66afa6` | | Situação e tipo | `status`, `payment_method`, `type` | O valor exato, em maiúsculas. | `status=PAID` | | Ativo | `is_active` | `true` traz só os ativos; `false`, só os inativos. Sem o filtro, vêm os dois. | `is_active=true` | | Data | `created_from`, `created_to` | Data e hora completas em ISO 8601, com fuso. As duas pontas entram no resultado. | `created_to=2026-09-15T23:59:59-03:00` | Detalhes que evitam surpresas: * `GET /sales?product_id=` traz as vendas que têm o produto em **algum** item. A venda vem com **todos** os itens, inclusive os de outros produtos. * `GET /products` não lista planos. Para planos, use `GET /plans`. Por isso `type` aceita só `DIGITAL`, `PHYSICAL` e `PACKAGE`. * Documento com outra quantidade de dígitos, ou com letras, responde `400 invalid_request`. * Na busca por texto, `%` e `_` valem como caracteres comuns, não como curinga. Exemplo: vendas pagas de um cliente, buscando pelo CPF. ```bash curl "https://api.pagpolar.com/v1/sales?status=PAID&customer_document=123.456.789-09" \ -H "Authorization: Bearer " ``` ## Percorra todas as páginas sem perder registros Novas vendas entram no topo da lista enquanto você pagina. Isso empurra os itens para a página seguinte, e um mesmo item pode aparecer duas vezes. Para evitar isso: 1. Fixe `created_to` com a data e hora em que você começou a busca. 2. Use `per_page=100` para fazer menos chamadas. 3. Guarde os itens pelo `id`. Se um `id` repetir, ignore. Os exemplos abaixo usam um token de acesso. Veja [Autenticação](/docs/guias/fundamentos/autenticacao#obter-o-token). #### cURL ```bash curl "https://api.pagpolar.com/v1/sales?page=1&per_page=100&created_from=2026-09-01T00:00:00-03:00&created_to=2026-09-15T23:59:59-03:00" \ -H "Authorization: Bearer " ``` #### Node.js ```js const apiUrl = 'https://api.pagpolar.com/v1'; const accessToken = ''; const createdTo = new Date().toISOString(); const salesById = new Map(); let page = 1; let totalPages = 1; do { const query = new URLSearchParams({ page: String(page), per_page: '100', created_from: '2026-09-01T00:00:00-03:00', created_to: createdTo, }); const response = await fetch(`${apiUrl}/sales?${query}`, { headers: { Authorization: `Bearer ${accessToken}` }, }); if (!response.ok) { throw new Error(`Erro ${response.status}: ${await response.text()}`); } const body = await response.json(); for (const sale of body.data) { salesById.set(sale.id, sale); } totalPages = body.meta.total_pages; page += 1; } while (page <= totalPages); console.log(`${salesById.size} vendas encontradas`); ``` ## Próximos passos - [Limites de requisição](/docs/guias/fundamentos/limites-de-requisicao) — Pagine sem estourar o limite por minuto. - [Valores, datas e identificadores](/docs/guias/fundamentos/valores-datas-e-identificadores) — Leia valores e ids das respostas sem errar. --- # Valores, datas e identificadores URL: https://staging.pagpolar.com/docs/guias/fundamentos/valores-datas-e-identificadores > Envie valores em centavos, leia valores em reais, e use datas e os tipos de identificador da API sem errar. ## Valores nas respostas: reais, como número Nas respostas da API, todo valor em dinheiro vem **em reais**, como número: ```json { "total_amount": 197.9, "items": [ { "amount": 197.9, "original_amount": 219.9, "discount_value": 22 } ] } ``` `197.9` significa R$ 197,90. > **No webhook é diferente** > > No webhook, a maioria dos valores chega como **texto**, por exemplo `"197.9000"`. Veja [Formato do evento](/docs/webhooks/formato-do-evento#valores). ## Valores que você envia: centavos, como número inteiro Todo valor em dinheiro que você envia à API vai **em centavos**, como número inteiro. A unidade é a mesma em todos os campos: | Onde | Campo | Unidade | Exemplo para R$ 49,90 | | ----------------------------------------------------- | ------------- | ------------------------ | --------------------- | | `POST /offers` e `PATCH /offers/{id}` | `price` | Centavos, número inteiro | `4990` | | `POST /plans/{id}/offers` e `PATCH /plan-offers/{id}` | `price` | Centavos, número inteiro | `4990` | | Oferta informada na venda (`offer`) | `offer.value` | Centavos, número inteiro | `4990` | Nas quatro rotas, um `price` com casas decimais, como `49.9`, é recusado com `400`. Envie `4990`. `offer.value` tem um valor mínimo. Por padrão, é `500` (R$ 5,00). Abaixo disso, a resposta é `400`. `price` não tem esse mínimo: a validação aceita qualquer inteiro a partir de `0`. Quem limita na prática, na oferta avulsa, é a [parcela mínima](/docs/guias/jornadas/criar-produto-e-oferta#parcela-minima). A API converte para reais ao gravar. Por isso a oferta volta com `price` em reais na resposta. ## Parcela mínima Numa oferta avulsa, cada parcela no cartão precisa valer pelo menos R$ 5,00 (valor padrão), senão a criação responde `400` com o valor da parcela e o mínimo em vigor na `message`. A conta, os exemplos e o que fazer estão em [A regra da parcela mínima](/docs/guias/jornadas/criar-produto-e-oferta#parcela-minima). ## Datas * Datas nas respostas vêm em ISO 8601, em UTC: `2026-09-15T14:30:00.000Z`. * Datas que ficam vazias vêm `null`. Exemplo: `paid_at` enquanto a venda não foi paga. * Nos filtros, envie data e hora em ISO 8601 com fuso: `2026-09-15T23:59:59-03:00`. ## Identificadores A API usa quatro tipos de identificador: | Nome | Exemplo | O que é | | ---------------------- | -------------------------------------- | ------------------------------------------------------------------------------------------ | | `id` | `a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d` | Id interno (uuid). Todo recurso tem. | | `identifier` | `PPO9876543210` | Código da venda ou da oferta. Veja [Códigos com prefixo](#codigos-com-prefixo). | | `external_reference` | `PEDIDO-1234` | Código do **seu** pedido. Você envia na cobrança ou na assinatura, com até 255 caracteres. | | `affiliate_identifier` | `PAO0123456789` | Código do afiliado, o mesmo do link de divulgação. | ### Códigos com prefixo Todo código é um prefixo seguido de 10 dígitos, que podem começar com zero. Nas respostas da API e no webhook, o código sai **sempre com o prefixo**. É o mesmo código que aparece no painel. | Código | Prefixo | Exemplo | Onde aparece | | -------- | ----------------- | --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Venda | `PPO` | `PPO9876543210` | `identifier` da venda em `GET /sales`, `GET /sales/{identifier}` e `GET /payments/{identifier}`; `sale.identifier` do reembolso; `data.transaction.identifier` do webhook. | | Oferta | `PPP` | `PPP1234567890` | `identifier` da oferta; `data.offer_identifier` da resposta `201` das cobranças; `items[].offer.identifier` da venda; `items[].price.identifier` do webhook. | | Afiliado | `PAO`, por padrão | `PAO0123456789` | `affiliate_identifier`, que você envia na cobrança e na assinatura. | Guarde e compare o código como ele chega, com o prefixo. Para ligar registros entre a API e o webhook, prefira o `id`. Na entrada, o código funciona **com ou sem o prefixo**, em toda rota da tabela abaixo que aceita código. `PPP1234567890` e `1234567890` encontram a mesma oferta, e `PPO9876543210` e `9876543210` encontram a mesma venda. ### Qual identificador cada rota aceita | Rota | Aceita | | -------------------------------------------------------- | ------------------------------------------------------ | | `GET /sales/{identifier}` e `GET /payments/{identifier}` | `id` da venda, código da venda ou `external_reference` | | `GET /offers/{identifier}` | `id` ou código da oferta | | `POST /plans/offer/{id}/subscribe` | `id` ou código da oferta de plano | | `offer_identifier` no corpo das cobranças | Código da oferta | | `sale_identifier` em `POST /refunds` e `GET /refunds` | Código da venda | | Demais rotas com `{id}` no caminho | Só o `id` (uuid) | Onde a rota aceita código, você também pode enviar o link com o código no final: a API lê o último pedaço do link. Para a oferta, ela usa só os números desse pedaço. Para a venda, o pedaço precisa ter o formato do código da venda, descrito abaixo. ### Como a venda é encontrada `GET /sales/{identifier}` testa o valor nesta ordem e para no primeiro que encontrar: 1. Se o valor é um uuid, procura pelo `id` da venda. 2. Se o valor tem o formato do código da venda, procura pelo código. O formato é: o prefixo seguido de 10 dígitos, sem diferenciar maiúsculas de minúsculas (`PPO0087103960` ou `ppo0087103960`); exatamente 10 dígitos, sem o prefixo (`0087103960`); ou um link cujo último pedaço é um desses dois. 3. Procura pela `external_reference`, com o texto exato. Se a mesma referência foi usada em mais de uma venda, devolve a mais recente. Se nada for encontrado, a resposta é `404` com `Venda não encontrada`. > **Não use o formato do código da venda na external_reference** > > Um valor fora desses formatos, como `PED-2026-09-15-01`, vai direto para o passo 3. Já uma `external_reference` com o formato do código da venda, como `0087103960` ou `PPO0087103960`, é procurada antes como código. Se ela bater com o código de **outra** venda, a API devolve essa outra venda. > > Para achar todas as vendas de uma referência, use [`GET /sales?external_reference=`](/docs/referencia/vendas/list-sales), que procura só pela referência. ## Documento e telefone A API confere o formato do documento e do telefone antes de criar qualquer coisa. A regra vale para estes campos: * `customer.document` e `customer.phone`, nas cobranças (`POST /payments/pix`, `POST /payments/boleto` e `POST /payments/credit-card`) e na criação de assinatura (`POST /plans/offer/{id}/subscribe`); * `holder_document`, o documento do titular do cartão, na cobrança no cartão, na criação de assinatura, na troca de cartão (`PATCH /subscriptions/{id}/card`) e em `card` na troca de plano (`POST /subscriptions/{id}/plan-change`). | Campo | Formato | Exemplos fictícios | | --------------------------------------- | ---------------------------------------------------------------------------------------- | ------------------------------------------------------- | | `customer.document` e `holder_document` | CPF com 11 dígitos ou CNPJ com 14 dígitos. | `12345678909` ou `123.456.789-09` | | `customer.phone` | DDD e número, com 10 ou 11 dígitos. Com o código do país 55 na frente, 12 ou 13 dígitos. | `11999998888`, `(11) 99999-8888` ou `+55 11 99999-8888` | Pontuação é aceita e removida: no telefone, isso inclui espaços, parênteses e `+`. A API grava só os dígitos. `123.456.789-09` é gravado como `12345678909`, e `+55 11 99999-8888` como `5511999998888`. Fora do formato, a resposta é `400 invalid_request` e nada é criado: | Campo | `message` | | ------------------- | --------------------------------------------------------------------------------------------------------------------------- | | `customer.document` | `Documento inválido: envie o CPF com 11 dígitos ou o CNPJ com 14 dígitos, com ou sem pontuação.` | | `holder_document` | `Documento do titular do cartão inválido: envie o CPF com 11 dígitos ou o CNPJ com 14 dígitos, com ou sem pontuação.` | | `customer.phone` | `Telefone inválido: envie o DDD e o número, com 10 ou 11 dígitos (12 ou 13 com o código do país 55), com ou sem pontuação.` | ## Dados pessoais mascarados Nas respostas da API, o documento e o telefone do cliente vêm mascarados: | Campo | Valor guardado | Valor na resposta | | -------------------------------- | ---------------- | -------------------- | | `document` com 11 dígitos (CPF) | `12345678909` | `***.456.***-**` | | `document` com 14 dígitos (CNPJ) | `12345678000195` | `**.345.***/****-**` | | `document` com outro tamanho | qualquer | `***` | | `phone` | `11987654321` | `****4321` | O nome e o e-mail vêm completos. Nos eventos de webhook esses dados chegam sem máscara. Veja [Dados pessoais no webhook](/docs/webhooks/formato-do-evento#dados-pessoais). --- # Acompanhar reembolsos e chargebacks URL: https://staging.pagpolar.com/docs/guias/jornadas/acompanhar-reembolsos-e-chargebacks > Receba o pedido de reembolso, consulte os pedidos na API e reaja ao estorno e ao chargeback de uma venda. Use este guia depois que a venda foi paga. Ele mostra como saber quando o cliente pede o dinheiro de volta, quando o estorno acontece e quando o banco do cliente contesta a compra. A API **consulta** os pedidos de reembolso e **reembolsa** uma venda sua. Aceitar, recusar ou cancelar um pedido aberto pelo cliente é feito no painel da PagPolar. Se a venda é de uma assinatura, o reembolso, o estorno e o chargeback também cancelam a assinatura. Veja [em quais casos](/docs/guias/jornadas/cancelar-assinatura#reembolso-e-chargeback). ## Visão geral O diagrama mostra o caminho mais comum: o cliente pede o reembolso, você aceita no painel e o gateway devolve o dinheiro. ```mermaid sequenceDiagram autonumber participant C as Cliente participant V as Você no painel participant A as API PagPolar participant G as Gateway participant S as Seu servidor C->>A: pede reembolso da venda inteira ou de parte dos itens A-)S: TRANSACTION_ASK_REFUNDING S->>A: GET /refunds?sale_identifier=CODIGO_DA_VENDA A-->>S: 200 com o pedido em PENDING V->>A: aceita o pedido A->>G: pede o estorno alt gateway confirma o estorno G-)A: estorno concluído A-)S: TRANSACTION_REFUNDED else gateway recusa o estorno A-->>V: pedido fica FAILED end ``` ## Antes de começar * Um token de acesso. Veja [Autenticação](/docs/guias/fundamentos/autenticacao#obter-o-token). * Uma credencial com webhook. Veja [Configurar o webhook](/docs/webhooks/configurar). * Os eventos `TRANSACTION_ASK_REFUNDING`, `TRANSACTION_REFUNDED`, `TRANSACTION_CANCELED` e `TRANSACTION_CHARGEBACK_APPROVED` escolhidos no webhook da credencial. * Um servidor de webhook que [autentica as requisições](/docs/webhooks/autenticar-requisicoes) e [descarta eventos repetidos](/docs/webhooks/processar-sem-duplicar). ## O que fica na API e o que fica no painel | O que acontece | Onde | | ---------------------------------------------------------- | ---------------------------------------------------------------- | | O cliente pede reembolso. | Na PagPolar. Você recebe `TRANSACTION_ASK_REFUNDING`. | | Aceitar, recusar ou cancelar o pedido do cliente. | Tela **Reembolsos** do painel. Não existe rota na API para isso. | | Reembolsar uma venda por sua conta, sem pedido do cliente. | [`POST /refunds`](/docs/referencia/reembolsos/create-refund) | | Ver os pedidos e a situação de cada um. | [`GET /refunds`](/docs/referencia/reembolsos/list-refunds) | | Saber que o dinheiro voltou para o cliente. | `TRANSACTION_REFUNDED` | | Saber que o banco do cliente contestou a compra. | `TRANSACTION_CHARGEBACK_APPROVED` | ## Passo a passo 1. **Receba o pedido de reembolso** Quando o cliente pede reembolso, a PagPolar envia `TRANSACTION_ASK_REFUNDING` para o seu webhook. O dinheiro **ainda não** voltou para o cliente. Exemplo resumido do que chega. O payload completo está em [`TRANSACTION_ASK_REFUNDING`](/docs/webhooks/eventos/transaction-ask-refunding). ```json { "event": "TRANSACTION_ASK_REFUNDING", "data": { "transaction": { "id": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d", "identifier": "PPO9876543210", "status": "ASK_REFUND", "payment_method": "PIX", "total_amount": "197.0000", "refund_reason": "Produto não atendeu às expectativas", "refund_at": null } } } ``` O `status` da venda diz o tipo de pedido: | `data.transaction.status` | Tipo de pedido | | ------------------------- | -------------------------------------- | | `ASK_REFUND` | Reembolso da venda inteira. | | `ASK_PARTIAL_REFUND` | Reembolso de parte dos itens da venda. | Faça assim: 1. Grave o pedido junto da venda, usando o `data.transaction.id`. 2. Guarde o `data.transaction.identifier`. É o código da venda, com o prefixo, como `PPO9876543210`: o mesmo valor da API. Use esse valor no filtro `sale_identifier` do próximo passo. 3. Não revogue o acesso ainda. Espere `TRANSACTION_REFUNDED`. > **Pedido aberto pelo vendedor não envia este evento** > > Quando o pedido é aberto pelo próprio vendedor, pela PagPolar ou por [`POST /refunds`](#reembolsar-pela-api), ele já nasce aceito e `TRANSACTION_ASK_REFUNDING` não é enviado. Para ver esses pedidos, consulte `GET /refunds`. Os abertos no painel aparecem com `requested_by: "SELLER"`. 2. **Consulte o pedido na API** Chame [`GET /refunds`](/docs/referencia/reembolsos/list-refunds) com o código da venda no filtro `sale_identifier`. #### cURL ```bash curl "https://api.pagpolar.com/v1/refunds?sale_identifier=PPO9876543210" \ -H "Authorization: Bearer " ``` #### Node.js ```js const apiUrl = 'https://api.pagpolar.com/v1'; const accessToken = ''; const saleIdentifier = 'PPO9876543210'; const query = new URLSearchParams({ sale_identifier: saleIdentifier }); const response = await fetch(`${apiUrl}/refunds?${query}`, { headers: { Authorization: `Bearer ${accessToken}` }, }); if (!response.ok) { throw new Error(`Erro ${response.status}: ${await response.text()}`); } const body = await response.json(); for (const refund of body.data) { console.log(refund.id, refund.status, refund.is_partial, refund.refund_amount); } ``` Resposta resumida. Todos os campos estão na [referência de `GET /refunds`](/docs/referencia/reembolsos/list-refunds). ```json { "data": [ { "id": "3c9f1e2a-7b4d-4e8a-9f10-2b3c4d5e6f70", "status": "PENDING", "requested_by": "CLIENT", "is_partial": false, "refund_amount": null, "reason": "Não atendeu às expectativas", "refused_reason": null, "canceled_reason": null, "return_tracking": null, "sale": { "identifier": "PPO9876543210", "status": "ASK_REFUND", "payment_method": "PIX", "total_amount": 197 }, "created_at": "2026-09-15T13:00:00.000Z", "updated_at": "2026-09-15T13:00:00.000Z" } ], "meta": { "page": 1, "per_page": 25, "total": 1, "total_pages": 1 } } ``` | Campo | O que significa | | ----------------- | --------------------------------------------------------------------------------------------------------------- | | `id` | Id do pedido de reembolso. | | `status` | Situação do pedido. Veja [Situações do pedido de reembolso](#situacoes-do-pedido). | | `requested_by` | Quem abriu o pedido: `CLIENT` (o cliente) ou `SELLER` (o vendedor ou a PagPolar). | | `is_partial` | `true` quando o pedido é de parte dos itens. | | `refund_amount` | Valor do reembolso parcial, **em reais**, como número: `49.9` = R$ 49,90. `null` no reembolso da venda inteira. | | `reason` | Motivo informado no pedido. | | `refused_reason` | Motivo da recusa. `null` se não foi recusado. | | `canceled_reason` | Motivo do cancelamento. `null` se não foi cancelado. | | `return_tracking` | Rastreio da devolução de produto físico. `null` quando não há rastreio. | | `sale` | Resumo da venda: `identifier`, `status`, `payment_method` e `total_amount`. | | `customer` | O cliente, com documento e telefone mascarados. | Filtros aceitos: | Parâmetro | O que faz | | ----------------------------- | ----------------------------------------------------------------------------------- | | `sale_identifier` | Traz só os pedidos da venda com esse código. | | `status` | Traz só os pedidos nessa situação. | | `created_from` e `created_to` | Traz os pedidos criados nesse intervalo. Data e hora em ISO 8601 com fuso. | | `page` e `per_page` | Paginação. Veja [Paginação e filtros](/docs/guias/fundamentos/paginacao-e-filtros). | > **Use o código da venda, não o id** > > `sale_identifier` procura pelo código da venda (`identifier`). Ele aceita o código como chega no webhook e na API, com o prefixo, como `PPO9876543210`, o código só com os 10 dígitos, como `9876543210`, e um link que termina no código. Um `id` (uuid) nesse filtro não encontra nada. O bloco `sale` não traz o `id` da venda. Para ver a venda completa, chame [`GET /sales/{identifier}`](/docs/referencia/vendas/get-sale) com o `sale.identifier`. Uma venda só tem **um** pedido em andamento por vez. Depois que um pedido termina (reembolsado, recusado, cancelado ou com falha), um novo pedido pode ser aberto. Por isso, o filtro pode trazer mais de um pedido para a mesma venda. 3. **Acompanhe a decisão no painel** O vendedor responde ao pedido no painel. Nenhuma decisão envia evento de webhook, nem quando a venda volta para `PAID`. Consulte `GET /refunds` para saber o que aconteceu. Veja [Ciclo de vida da venda](/docs/guias/conceitos/ciclo-de-vida-da-venda). No painel, os pedidos ficam em **Vendas → Reembolsos**, com os indicadores por situação e a lista de pedidos: Ao abrir um pedido, a tela **Detalhes do reembolso** mostra a situação, a compra, o motivo informado pelo cliente e, quando houver, a data e o motivo da recusa. É nessa tela que o vendedor aceita ou recusa um pedido pendente: | Decisão | Situação do pedido | Status da venda | Evento | | --------------------------- | --------------------------------------- | ------------------------------------------------------------------------------------------------ | ------ | | Aceito pelo vendedor | `ACCEPTED`, depois `REFUNDING` | Venda inteira: `REFUNDING`. Parte dos itens: não muda. | Nenhum | | Aceito pela PagPolar | `ACCEPTED_BY_ADMIN`, depois `REFUNDING` | Igual ao aceito pelo vendedor. | Nenhum | | Recusado | `REFUSED` ou `REFUSED_BY_ADMIN` | Se estava em `ASK_REFUND` ou `ASK_PARTIAL_REFUND`, volta para `PAID`. Em outro status, não muda. | Nenhum | | Cancelado | `CANCELED` | Volta para `PAID`. | Nenhum | | O gateway recusou o estorno | `FAILED` | Venda inteira: volta para `ASK_REFUND`. | Nenhum | Na recusa do pedido da venda inteira, o repasse e a taxa ligados à venda também voltam para `PAID`. Para saber se o pedido foi recusado ou cancelado, use o `status` do pedido em `GET /refunds`, e não o status da venda. A API não mostra o motivo da falha do estorno. O pedido aparece só como `FAILED`. 4. **Reaja ao estorno** Quando o gateway confirma o estorno, a venda muda de status e a PagPolar envia um evento. O que chega depende do caso: | Situação | Evento | Status da venda | | ---------------------------------------------------- | --------------------------------------------------------------------- | ----------------- | | Estorno da venda inteira | [`TRANSACTION_REFUNDED`](/docs/webhooks/eventos/transaction-refunded) | `REFUNDED` | | Estorno de um item, e ainda restam itens na venda | Nenhum | Volta para `PAID` | | Estorno do último item da venda | [`TRANSACTION_REFUNDED`](/docs/webhooks/eventos/transaction-refunded) | `REFUNDED` | | Estorno de uma venda que ainda não contava como paga | [`TRANSACTION_CANCELED`](/docs/webhooks/eventos/transaction-canceled) | `CANCELED` | "Contava como paga" quer dizer: a venda estava em `PAID`, `REFUNDING`, `ASK_REFUND`, `ASK_PARTIAL_REFUND` ou `CHARGEBACK_APPROVED`. Exemplo resumido de `TRANSACTION_REFUNDED`: ```json { "event": "TRANSACTION_REFUNDED", "data": { "transaction": { "id": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d", "identifier": "PPO9876543210", "status": "REFUNDED", "payment_method": "PIX", "total_amount": "197.0000", "refund_reason": "Produto não atendeu às expectativas" } } } ``` Ao receber `TRANSACTION_REFUNDED`: 1. Confira o `data.transaction.status`. Se já for `REFUNDED` no seu sistema, ignore. 2. Revogue o acesso ao que foi vendido. 3. Marque o pedido como reembolsado. Ao receber `TRANSACTION_CANCELED` depois de um pedido de reembolso, cancele o pedido no seu sistema. Para saber o valor devolvido no estorno de um item sem evento, consulte `GET /refunds?status=REFUNDED` e leia `is_partial` e `refund_amount`. > **O estorno pode chegar sem pedido antes** > > `TRANSACTION_REFUNDED` também chega quando o estorno acontece sem pedido de reembolso aberto. Trate o evento mesmo sem ter recebido `TRANSACTION_ASK_REFUNDING` antes. ## Reembolsar uma venda pela API Use [`POST /refunds`](/docs/referencia/reembolsos/create-refund) quando **você** decide devolver o dinheiro — por acordo com o cliente, por engano na cobrança ou por uma regra do seu sistema. > **O estorno é imediato e não tem volta** > > O pedido criado por esta rota já nasce aceito: o estorno vai para o gateway na hora e os acessos do cliente são revogados. Não existe rota para desfazer. O reembolso é sempre **da venda inteira**. Reembolsar só alguns itens continua no painel. #### cURL ```bash curl -X POST "https://api.pagpolar.com/v1/refunds" \ -H "Authorization: Bearer " \ -H "Idempotency-Key: 2f1c4d2e-8f4a-4c1e-9f7b-6a2d1e3c5b90" \ -H "Content-Type: application/json" \ -d '{ "sale_identifier": "PPO9876543210", "requested_by": "CLIENT", "reason": "Cliente desistiu da compra" }' ``` #### Node.js ```js const apiUrl = 'https://api.pagpolar.com/v1'; const accessToken = ''; const response = await fetch(`${apiUrl}/refunds`, { method: 'POST', headers: { Authorization: `Bearer ${accessToken}`, 'Idempotency-Key': crypto.randomUUID(), 'Content-Type': 'application/json', }, body: JSON.stringify({ sale_identifier: 'PPO9876543210', requested_by: 'CLIENT', reason: 'Cliente desistiu da compra', }), }); if (!response.ok) { throw new Error(`Erro ${response.status}: ${await response.text()}`); } const { data } = await response.json(); console.log(data.id, data.status); ``` | Campo | Obrigatório | O que é | | ---------------------- | ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `sale_identifier` | Sim | Código público da venda — o `identifier` que vem em [`GET /sales`](/docs/referencia/vendas/list-sales), na resposta da cobrança e no payload dos webhooks. Vem com o prefixo, como `PPO9876543210`. O código sem o prefixo ou a URL do checkout com o código também servem. **Não** é o `id` (uuid) da venda: com o uuid a resposta é `404`. | | `requested_by` | Não | Quem pediu o reembolso: `SELLER` (padrão) quando a decisão foi sua, `CLIENT` quando o comprador pediu por fora, por e-mail ou atendimento. Só muda o registro, que volta em `requested_by` na consulta — o estorno é imediato nos dois casos. | | `reason` | Não | Motivo, em texto livre, que fica gravado no pedido. | | `customer_observation` | Não | Observação do comprador, quando houver. | A resposta é `201` com o pedido já criado: ```json { "data": { "id": "3f1c4d2e-8f4a-4c1e-9f7b-6a2d1e3c5b90", "status": "REFUNDING", "requested_by": "CLIENT", "is_partial": false, "refund_amount": null, "reason": "Cliente desistiu da compra", "sale": { "identifier": "PPO9876543210", "status": "REFUNDING", "payment_method": "PIX", "total_amount": 197 } } } ``` | Situação na resposta | O que aconteceu | O que fazer | | -------------------- | ------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------- | | `REFUNDING` | O gateway aceitou o pedido de estorno e ainda não confirmou. | Espere [`TRANSACTION_REFUNDED`](/docs/webhooks/eventos/transaction-refunded) para revogar o acesso. | | `FAILED` | O gateway recusou o estorno, por exemplo por falta de saldo de um co-produtor. | Consulte `GET /refunds` e peça o reprocessamento pelo painel. | ### Quando a venda não pode ser reembolsada | Situação | Resposta | Como resolver | | ---------------------------------------------------------------------------------------------- | --------------------- | ---------------------------------------------------------------------------------------------------------------- | | Código de uma venda de outra conta, ou código inexistente. | `404 not_found` | Confira o `identifier` da venda em `GET /sales`. | | Venda que não está paga nem com pedido aberto (por exemplo, `OPEN`, `REFUNDED` ou `CANCELED`). | `409 conflict` | Só venda paga pode ser reembolsada. Veja [Ciclo de vida da venda](/docs/guias/conceitos/ciclo-de-vida-da-venda). | | A venda já tem um pedido de reembolso em andamento. | `400 invalid_request` | Consulte `GET /refunds?sale_identifier=CODIGO_DA_VENDA` e acompanhe o pedido existente. | ## Chargeback Chargeback é a contestação da compra pelo banco do cliente. Não existe pedido nem decisão no painel: o gateway avisa a PagPolar quando o chargeback é aprovado. O diagrama mostra o que acontece quando o aviso chega. ```mermaid sequenceDiagram autonumber participant G as Gateway participant A as API PagPolar participant S as Seu servidor G-)A: chargeback aprovado A-)S: TRANSACTION_CHARGEBACK_APPROVED S->>A: GET /sales/CODIGO_DA_VENDA A-->>S: 200 com o status atual da venda ``` O que muda na PagPolar: | O quê | Efeito | | ----------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Status da venda | Vira `CHARGEBACK_APPROVED`. Se a venda já estava `REFUNDED` ou `CANCELED`, o status não muda. | | Evento | `TRANSACTION_CHARGEBACK_APPROVED` é enviado nos dois casos acima. | | Pedidos de reembolso da venda | Os que estavam `PENDING`, `ACCEPTED` ou `ACCEPTED_BY_ADMIN` viram `CANCELED`. | | Assinatura da venda | A PagPolar pede o cancelamento, se a venda não estava `REFUNDED` nem `CANCELED`. Veja [Cancelar assinatura](/docs/guias/jornadas/cancelar-assinatura#reembolso-e-chargeback). | Exemplo resumido: ```json { "event": "TRANSACTION_CHARGEBACK_APPROVED", "data": { "transaction": { "id": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d", "identifier": "PPO9876543210", "status": "CHARGEBACK_APPROVED", "payment_method": "CREDIT_CARD", "total_amount": "197.0000", "chargeback_approved_at": "2026-09-15T12:00:00.000Z" } } } ``` Ao receber `TRANSACTION_CHARGEBACK_APPROVED`: 1. Revogue o acesso ao que foi vendido. 2. Guarde `data.transaction.chargeback_approved_at`. A venda na API não traz essa data. 3. Se a venda tinha um pedido de reembolso aberto no seu sistema, marque o pedido como cancelado. Para listar as vendas com chargeback aprovado, filtre a listagem de vendas pelo status: #### cURL ```bash curl "https://api.pagpolar.com/v1/sales?status=CHARGEBACK_APPROVED&per_page=100" \ -H "Authorization: Bearer " ``` #### Node.js ```js const apiUrl = 'https://api.pagpolar.com/v1'; const accessToken = ''; const query = new URLSearchParams({ status: 'CHARGEBACK_APPROVED', per_page: '100', }); const response = await fetch(`${apiUrl}/sales?${query}`, { headers: { Authorization: `Bearer ${accessToken}` }, }); if (!response.ok) { throw new Error(`Erro ${response.status}: ${await response.text()}`); } const body = await response.json(); console.log(body.data.map((sale) => sale.identifier)); ``` > **Não há aviso antes da aprovação** > > A PagPolar só envia evento quando o chargeback é **aprovado**. Não existe evento para chargeback aberto ou em análise. ## Situações do pedido de reembolso O diagrama mostra os caminhos principais de um pedido de reembolso. ```mermaid stateDiagram-v2 [*] --> PENDING: pedido aberto PENDING --> ACCEPTED: vendedor aceita PENDING --> ACCEPTED_BY_ADMIN: PagPolar aceita PENDING --> REFUSED: vendedor recusa, venda volta para PAID PENDING --> REFUSED_BY_ADMIN: PagPolar recusa, venda volta para PAID PENDING --> CANCELED: pedido cancelado ou chargeback aprovado ACCEPTED --> CANCELED: chargeback aprovado ACCEPTED_BY_ADMIN --> CANCELED: chargeback aprovado ACCEPTED --> REFUNDING: estorno pedido ao gateway ACCEPTED_BY_ADMIN --> REFUNDING: estorno pedido ao gateway ACCEPTED --> FAILED: gateway recusa o estorno ACCEPTED_BY_ADMIN --> FAILED: gateway recusa o estorno REFUNDING --> REFUNDED: gateway confirma o estorno ``` | Situação | Significado | O que fazer | | -------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------ | | `PENDING` | O pedido espera a resposta do vendedor. | Aguarde. | | `ACCEPTED` | O vendedor aceitou. O estorno vai ser pedido ao gateway. | Aguarde o estorno. | | `ACCEPTED_BY_ADMIN` | A PagPolar aceitou, inclusive por aprovação automática de pedido sem resposta. | Aguarde o estorno. | | `REFUSED` | O vendedor recusou. A venda volta para `PAID`, se estava em pedido de reembolso. | Mantenha o acesso. | | `REFUSED_BY_ADMIN` | A PagPolar recusou. A venda volta para `PAID`, se estava em pedido de reembolso. | Mantenha o acesso. | | `WAITING_SEND`, `WAITING_TRACK_CODE`, `SENT` | Etapas da devolução de um produto físico. `WAITING_TRACK_CODE` aparece quando o vendedor exige a devolução do produto físico. | Aguarde. | | `REFUNDING` | O estorno foi pedido ao gateway e ainda não foi confirmado. | Aguarde `TRANSACTION_REFUNDED`. | | `REFUNDED` | O estorno foi concluído. | Revogue o acesso, se ainda não fez. | | `CANCELED` | O pedido foi desfeito, ou a venda teve chargeback aprovado. | Mantenha o acesso se a venda voltou para `PAID`. | | `FAILED` | O gateway recusou o estorno. | Aguarde. O vendedor resolve pelo painel. | ## Eventos de webhook deste fluxo * [`TRANSACTION_ASK_REFUNDING`](/docs/webhooks/eventos/transaction-ask-refunding): veja [Receba o pedido de reembolso](#receba-o-pedido-de-reembolso). * [`TRANSACTION_REFUNDED`](/docs/webhooks/eventos/transaction-refunded) e [`TRANSACTION_CANCELED`](/docs/webhooks/eventos/transaction-canceled): veja [Reaja ao estorno](#reaja-ao-estorno). * [`TRANSACTION_CHARGEBACK_APPROVED`](/docs/webhooks/eventos/transaction-chargeback-approved): veja [Chargeback](#chargeback). ## Quando algo dá errado Além dos [erros comuns a todas as rotas](/docs/guias/fundamentos/erros#erros-comuns), este fluxo pode responder: | Passo | Situação | Resposta | Como resolver | | ------------------ | ------------------------------------------------------------------------------------------ | ---------------------- | --------------------------------------------------------------------------------------- | | Consultar o pedido | `status` com um valor fora da lista de situações. | `400 invalid_request` | Use um valor da tabela [Situações do pedido de reembolso](#situacoes-do-pedido). | | Consultar o pedido | `created_from` ou `created_to` fora do formato ISO 8601. | `400 invalid_request` | Envie data e hora completas: `2026-09-15T23:59:59-03:00`. | | Consultar o pedido | `sale_identifier` com o `id` (uuid) da venda, ou com o código de uma venda de outra conta. | `200` com `data` vazio | Envie o código da venda (`identifier`), com ou sem o prefixo. | | Chargeback | `TRANSACTION_CHARGEBACK_APPROVED` chegou com `status: "REFUNDED"` ou `"CANCELED"`. | Status mantido | A venda já tinha sido estornada ou cancelada. Registre o chargeback sem mudar o status. | ## Próximos passos - [Ciclo de vida da venda](/docs/guias/conceitos/ciclo-de-vida-da-venda) — Veja todos os status da venda e o evento de cada um. - [Processar eventos sem duplicar](/docs/webhooks/processar-sem-duplicar) — Trate cada evento de reembolso uma única vez. - [Conciliar vendas e assinaturas](/docs/guias/jornadas/conciliar-vendas) — Confira no fim do período se nenhum estorno ficou de fora. --- # Assinar um plano URL: https://staging.pagpolar.com/docs/guias/jornadas/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](/docs/guias/conceitos/ciclo-de-vida-da-assinatura). Para uma cobrança única, use [Vender com cartão de crédito](/docs/guias/jornadas/vender-com-cartao). ## Visão geral O diagrama mostra o caminho completo, da criação do plano até a renovação. ```mermaid sequenceDiagram autonumber participant I as Seu sistema participant A as API PagPolar participant G as Gateway participant W as Seu servidor de webhook I->>A: POST /plans e POST /plans/ID/offers A-->>I: 201 com o id do plano e o código da oferta de plano I->>A: POST /plans/offer/CODIGO/subscribe com Idempotency-Key A-->>I: 201 com a assinatura em DRAFT A-)W: TRANSACTION_CREATED e SUBSCRIPTION_CREATED A->>G: cria a assinatura depois da resposta alt gateway aceita A-)W: SUBSCRIPTION_CONFIRMED com status DRAFT G-)A: primeira cobrança paga, assinatura fica ACTIVE else gateway recusa A-)W: SUBSCRIPTION_FAILED com status FAILED end G-)A: cobrança de um ciclo seguinte paga A-)W: SUBSCRIPTION_RENEWED ``` ## Antes de começar * Um token de acesso. Veja [Autenticação](/docs/guias/fundamentos/autenticacao#obter-o-token); os exemplos assumem a variável `accessToken`. * A URL do webhook cadastrada na credencial. Veja [Configurar o webhook](/docs/webhooks/configurar). Para testar sem cobrança real, use a chave de Homologação. Veja [Ambiente de testes](/docs/guias/fundamentos/ambientes#ambiente-de-testes). Com a chave de Produção, o cartão é cobrado a cada ciclo: [cancele a assinatura](/docs/guias/jornadas/cancelar-assinatura) no final do teste. ## Passo a passo 1. **Crie o plano** O plano é o produto de assinatura. Ele guarda só o nome e a descrição. #### cURL ```bash curl -X POST "https://api.pagpolar.com/v1/plans" \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "name": "Clube de exemplo", "description": "Plano criado pelo guia de assinatura" }' ``` #### Node.js ```js 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): ```json { "data": { "id": "b7e1c2d3-4f5a-4b6c-8d7e-9f0a1b2c3d4e", "name": "Clube de exemplo", "type": "SUBSCRIPTION", "is_active": true } } ``` Guarde `data.id`. Ele é o `` do próximo passo. Contrato completo: [`POST /plans`](/docs/referencia/planos/create-plan). 2. **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 ```bash curl -X POST "https://api.pagpolar.com/v1/plans//offers" \ -H "Authorization: Bearer " \ -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 }' ``` #### Node.js ```js const response = await fetch('https://api.pagpolar.com/v1/plans//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}`](/docs/referencia/planos/update-plan-offer), a mesma regra recalcula os meios. Veja [Meios de pagamento e valor mínimo](/docs/guias/jornadas/criar-produto-e-oferta#meios-de-pagamento). > **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): ```json { "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](/docs/guias/fundamentos/valores-datas-e-identificadores#valores-nas-respostas). Guarde `data.identifier`. Ele é o `` do próximo passo. O `data.id` também funciona no lugar do código. Contrato completo: [`POST /plans/{id}/offers`](/docs/referencia/planos/create-plan-offer). 3. **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](/docs/guias/fundamentos/idempotencia). #### cURL ```bash curl -X POST "https://api.pagpolar.com/v1/plans/offer//subscribe" \ -H "Authorization: Bearer " \ -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": "", "phone": "" }, "credit_card": { "holder_name": "MARIA SILVA", "holder_document": "", "number": "", "expiration_month": 12, "expiration_year": 2030, "cvv": "" } }' ``` #### Node.js ```js import { randomUUID } from 'node:crypto'; const response = await fetch( 'https://api.pagpolar.com/v1/plans/offer//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: '', phone: '', }, credit_card: { holder_name: 'MARIA SILVA', holder_document: '', number: '', expiration_month: 12, expiration_year: 2030, 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](/docs/guias/fundamentos/valores-datas-e-identificadores#documento-e-telefone). | | `customer.phone` | Sim | Telefone do cliente com DDD, com ou sem pontuação. Veja o formato em [Documento e telefone](/docs/guias/fundamentos/valores-datas-e-identificadores#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](/docs/guias/fundamentos/valores-datas-e-identificadores#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](/docs/guias/fundamentos/ambientes#dados-de-teste). 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](/docs/guias/jornadas/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): ```json { "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`](/docs/referencia/assinaturas/create-subscription). > **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](/docs/guias/fundamentos/idempotencia#o-que-acontece-ao-repetir). Para achar a venda do primeiro ciclo pela sua referência, use `GET /sales?external_reference=ASSINATURA-0001`. 4. **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`](/docs/webhooks/eventos/transaction-created) | Logo depois da criação, com a venda do primeiro ciclo (`transaction.cycle: 1`). | Registre a venda. | | [`SUBSCRIPTION_CREATED`](/docs/webhooks/eventos/subscription-created) | Logo depois da criação, com a assinatura em `DRAFT`. | Registre a assinatura. Não libere o acesso. | | [`SUBSCRIPTION_CONFIRMED`](/docs/webhooks/eventos/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`](/docs/webhooks/eventos/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): ```json { "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](/docs/webhooks/formato-do-evento#source). Um `SUBSCRIPTION_CONFIRMED` pode chegar antes do `SUBSCRIPTION_CREATED`: use o `status` que veio no evento. Veja [Processar eventos sem duplicar](/docs/webhooks/processar-sem-duplicar). 5. **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 ```bash curl "https://api.pagpolar.com/v1/subscriptions/e1f2a3b4-c5d6-4e7f-8a9b-0c1d2e3f4a5b" \ -H "Authorization: Bearer " ``` #### Node.js ```js 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): ```json { "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](#ciclo-de-vida). Consulte com moderação: a consulta conta no limite geral da credencial. Veja [Limites de requisição](/docs/guias/fundamentos/limites-de-requisicao). Contrato completo: [`GET /subscriptions/{id}`](/docs/referencia/assinaturas/get-subscription). 6. **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`](/docs/webhooks/eventos/subscription-renewed): * a assinatura continua `ACTIVE`; * `subscription.next_billing_at` traz a data da próxima cobrança; * cada ciclo gera uma venda nova, com `id` pró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](/docs/guias/conceitos/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](#confirmacao-do-gateway). Nas renovações chega [`SUBSCRIPTION_RENEWED`](/docs/webhooks/eventos/subscription-renewed): veja [Acompanhe as renovações](#renovacoes). ## Quando algo dá errado Além dos [erros comuns a todas as rotas](/docs/guias/fundamentos/erros#erros-comuns), 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 | `` 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](/docs/guias/jornadas/criar-produto-e-oferta#meios-de-pagamento). | | 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: ## Próximos passos - [Cancelar assinatura](/docs/guias/jornadas/cancelar-assinatura) — Encerre a assinatura e saiba quando o cancelamento vale. - [Ciclo de vida da assinatura](/docs/guias/conceitos/ciclo-de-vida-da-assinatura) — Todos os status, no cartão, no PIX e no boleto. - [Processar eventos sem duplicar](/docs/webhooks/processar-sem-duplicar) — Trate repetições e eventos fora de ordem. --- # Cancelar assinatura URL: https://staging.pagpolar.com/docs/guias/jornadas/cancelar-assinatura > Cancele uma assinatura pela API e saiba, pelo meio de pagamento e pelo status, quando o cancelamento vale. Use este guia para encerrar a assinatura de um cliente. Depois do cancelamento, o cliente não é mais cobrado. O momento em que o cancelamento vale depende do meio de pagamento e do status da assinatura. Veja [O que acontece em cada caso](#o-que-acontece-em-cada-caso). ## Visão geral O diagrama mostra o que acontece em cada caso depois do pedido. ```mermaid sequenceDiagram autonumber participant I as Seu sistema participant A as API PagPolar participant G as Gateway participant W as Seu servidor de webhook I->>A: DELETE /subscriptions/ID alt cartão com status ACTIVE A->>G: pede o cancelamento Note over A: assinatura fica CANCELING A-->>I: 200 com success true G-)A: assinatura cancelada A-)W: SUBSCRIPTION_CANCELED, status CANCELED else PIX ou boleto em ACTIVE, PENDING_PAYMENT ou PENDING_RENEWAL A-)W: SUBSCRIPTION_CANCELED, status CANCELED A-->>I: 200 com success true else qualquer outro caso A-->>I: 200 com success true, nada muda end ``` ## Antes de começar * Um token de acesso. Veja [Autenticação](/docs/guias/fundamentos/autenticacao#obter-o-token); os exemplos assumem a variável `accessToken`. * O `id` da assinatura, um uuid. Ele vem em: * `data.id` da resposta de [`POST /plans/offer/{id}/subscribe`](/docs/referencia/assinaturas/create-subscription); * `data.subscription.id` dos eventos `SUBSCRIPTION_*`; * [`GET /subscriptions`](/docs/referencia/assinaturas/list-subscriptions), que lista as assinaturas da sua conta. > **Assinaturas do checkout também** > > A rota cancela qualquer assinatura da sua conta, inclusive as vendidas no checkout da PagPolar em PIX ou boleto. ## Passo a passo 1. **Confira o meio de pagamento e o status** O resultado do cancelamento depende de `payment_method` e de `status`. Consulte os dois em [`GET /subscriptions/{id}`](/docs/referencia/assinaturas/get-subscription) e compare com [O que acontece em cada caso](#o-que-acontece-em-cada-caso). 2. **Peça o cancelamento** Chame `DELETE /subscriptions/{id}`. A rota não tem corpo e não usa `Idempotency-Key`. #### cURL ```bash curl -X DELETE "https://api.pagpolar.com/v1/subscriptions/e1f2a3b4-c5d6-4e7f-8a9b-0c1d2e3f4a5b" \ -H "Authorization: Bearer " ``` #### Node.js ```js const response = await fetch( 'https://api.pagpolar.com/v1/subscriptions/e1f2a3b4-c5d6-4e7f-8a9b-0c1d2e3f4a5b', { method: 'DELETE', headers: { Authorization: `Bearer ${accessToken}` }, }, ); console.log(response.status, await response.json()); ``` Resposta `200`: ```json { "data": { "success": true } } ``` Contrato completo: [`DELETE /subscriptions/{id}`](/docs/referencia/assinaturas/cancel-subscription). > **success: true não quer dizer cancelada** > > A resposta é sempre `200` com `success: true`, tenha a assinatura mudado ou não. Quando o status não aceita cancelamento, como um cartão em `DRAFT` ou `PROCESSING`, nada muda, e repetir o pedido também não muda nada. Espere a assinatura ficar `ACTIVE` e peça de novo. Confirme o resultado no próximo passo. 3. **Confirme o resultado** Você confirma pelo webhook ou pela consulta. **Pelo webhook.** Quando o cancelamento é efetivado, a sua URL recebe [`SUBSCRIPTION_CANCELED`](/docs/webhooks/eventos/subscription-canceled). Exemplo (resumido): ```json { "id": "4d5e6f7a-8b9c-4d0e-1f2a-3b4c5d6e7f8a", "event": "SUBSCRIPTION_CANCELED", "creation_date": "2026-09-20T13:00:05.000Z", "version": "1.0.0", "data": { "subscription": { "id": "e1f2a3b4-c5d6-4e7f-8a9b-0c1d2e3f4a5b", "status": "CANCELED", "payment_method": "CREDIT_CARD" }, "source": { "channel": "API", "api_credential_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7" } } } ``` **Pela consulta.** Chame [`GET /subscriptions/{id}`](/docs/referencia/assinaturas/get-subscription): | `status` | O que significa | O que fazer | | ------------------- | ----------------------------------------------------------------- | ----------------------------------------------------------------- | | `CANCELING` | Cartão: o pedido foi enviado ao gateway, que ainda não confirmou. | Espere o `SUBSCRIPTION_CANCELED`. | | `CANCELED` | O cancelamento foi efetivado. | Encerre a assinatura no seu sistema e revogue o acesso. | | Igual ao do passo 1 | O status não aceitava cancelamento. Nada mudou. | Veja [O que acontece em cada caso](#o-que-acontece-em-cada-caso). | Depois do cancelamento efetivado, a consulta mostra: | Campo | PIX ou boleto | Cartão de crédito | | ----------------- | ---------------------- | ----------------------------------------------------------------- | | `canceled_at` | Data e hora do pedido. | Data e hora em que a PagPolar recebeu a confirmação do gateway. | | `end_at` | Data e hora do pedido. | A data de próxima cobrança informada pelo gateway na confirmação. | | `next_billing_at` | `null` | `null` | > **Confira end_at pela consulta** > > No cartão, `canceled_at` e `end_at` são gravados logo depois de o `SUBSCRIPTION_CANCELED` ser disparado. O evento pode chegar com esses campos ainda vazios. Para decidir até quando manter o acesso, use `GET /subscriptions/{id}`. ## O que acontece em cada caso | Meio de pagamento | Status no pedido | Status logo depois | Status final | Evento | | ----------------- | --------------------------------------------------------- | ------------------ | ------------------------------------- | -------------------------------------- | | PIX ou boleto | `ACTIVE`, `PENDING_PAYMENT` ou `PENDING_RENEWAL` | `CANCELED` | `CANCELED` | `SUBSCRIPTION_CANCELED` na hora | | PIX ou boleto | Qualquer outro | Não muda | Não muda | Nenhum | | Cartão de crédito | `ACTIVE` | `CANCELING` | `CANCELED`, quando o gateway confirma | `SUBSCRIPTION_CANCELED` na confirmação | | Cartão de crédito | Qualquer outro, como `DRAFT`, `PROCESSING` ou `CANCELING` | Não muda | Não muda | Nenhum | Qualquer meio de pagamento que não seja PIX ou boleto segue as regras do cartão. ## Reembolso e chargeback também cancelam A PagPolar também pede o cancelamento da assinatura, sem você chamar a API, nestes casos: * o cliente pede reembolso **total** de uma venda da assinatura; * um pedido de reembolso de uma venda da assinatura é aceito, seja total ou parcial, inclusive o aberto pelo vendedor; * uma venda da assinatura é estornada; * o chargeback de uma venda da assinatura é aprovado. O pedido segue as mesmas regras desta página. Veja [O que acontece em cada caso](#o-que-acontece-em-cada-caso). ## Eventos de webhook deste fluxo | Evento | Quando é enviado | O que fazer | | ----------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- | ---------------------------------------- | | [`SUBSCRIPTION_CANCELED`](/docs/webhooks/eventos/subscription-canceled) | Quando o cancelamento é efetivado. Veja [O que acontece em cada caso](#o-que-acontece-em-cada-caso). | Encerre a assinatura e revogue o acesso. | Para descartar repetidos, use a chave `event` + `data.subscription.id`. Veja [Processar eventos sem duplicar](/docs/webhooks/processar-sem-duplicar). ## Quando algo dá errado Além dos [erros comuns a todas as rotas](/docs/guias/fundamentos/erros#erros-comuns), este fluxo pode responder: | Passo | Situação | Resposta | Como resolver | | ----- | --------------------------------------------------------- | -------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | | 1 e 2 | O `id` não é um uuid. Por exemplo, o código de uma venda. | `400 invalid_request` | Envie o `id` da assinatura. | | 1 e 2 | Assinatura inexistente ou de outra conta. | `404` com `Assinatura não encontrada` | Confira o `id`. | | 2 | Cartão em `ACTIVE` e o gateway não aceitou o pedido. | `400` com a mensagem do gateway, ou `500 internal_error` | O status continua `ACTIVE`. Consulte a assinatura e peça de novo mais tarde. Se o erro continuar, fale com o suporte e informe o `request_id`. | ## Próximos passos - [Assinar um plano](/docs/guias/jornadas/assinar-um-plano) — Crie o plano, a oferta de plano e a assinatura no cartão. - [Ciclo de vida da assinatura](/docs/guias/conceitos/ciclo-de-vida-da-assinatura) — Todos os status, no cartão, no PIX e no boleto. - [Processar eventos sem duplicar](/docs/webhooks/processar-sem-duplicar) — Trate repetições e eventos fora de ordem. --- # Conciliar vendas e assinaturas URL: https://staging.pagpolar.com/docs/guias/jornadas/conciliar-vendas > Baixe todas as vendas e assinaturas de um período e compare com o seu sistema, sem perder nem duplicar registros. Use este guia para conferir, no fim de um período, se o seu sistema tem todas as vendas e assinaturas da PagPolar, com os valores e status certos. Durante o período, os [webhooks](/docs/webhooks) avisam cada mudança. A conciliação usa a API para achar o que ficou de fora. ## Visão geral O diagrama mostra as duas fontes de dados e a comparação no fim do período. ```mermaid sequenceDiagram autonumber participant A as API PagPolar participant W as Seu servidor de webhook participant S as Seu sistema A-)W: TRANSACTION_CREATED e TRANSACTION_PAID com transaction.id e net_amount W->>S: grava a venda pelo id loop cada página até total_pages S->>A: GET /sales com created_from, created_to e per_page=100 A-->>S: 200 com data e meta end loop cada página até total_pages S->>A: GET /subscriptions com created_from, created_to e per_page=100 A-->>S: 200 com data e meta end S->>S: junta pelo id e marca o que falta ou está diferente ``` ## Antes de começar * Um token de acesso. Veja [Autenticação](/docs/guias/fundamentos/autenticacao#obter-o-token). * Um webhook que recebe os eventos de venda. Veja [Configurar o webhook](/docs/webhooks/configurar). * Leia [Percorra todas as páginas sem perder registros](/docs/guias/fundamentos/paginacao-e-filtros#percorrer). Este guia usa as mesmas regras. ## Quais identificadores usar | Campo | Onde aparece | Use para | | ---------------------- | --------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `id` | Venda na API (`data[].id`) e no webhook (`data.transaction.id`) | **Chave principal** da conciliação. Não muda e não se repete. | | `identifier` | Venda na API e no webhook, com o mesmo valor | Código da venda: `PPO` seguido de 10 dígitos, como `PPO9876543210`. Mostrar o código ao cliente e filtrar `GET /refunds` por `sale_identifier`. Para juntar os registros, prefira o `id`. | | `external_reference` | Só na venda da API | Ligar a venda ao código do **seu** pedido. Não chega no webhook. | | `cycle` | Venda na API e no webhook | Posição da cobrança na assinatura: `1`, `2`, `3`... | | `data.subscription.id` | Só no webhook de venda | Ligar a venda à assinatura. A venda da API não traz a assinatura. | ## Passo a passo 1. **Guarde o id e a sua referência na criação** Na cobrança, envie `external_reference` com o código do seu pedido, como `PEDIDO-1234`. Não use o formato do código da venda. Veja [Como a venda é encontrada](/docs/guias/fundamentos/valores-datas-e-identificadores#como-a-venda-e-encontrada). A resposta `201` de [`POST /payments/pix`](/docs/referencia/vendas/create-pix-payment), [`POST /payments/boleto`](/docs/referencia/vendas/create-boleto-payment) e [`POST /payments/credit-card`](/docs/referencia/vendas/create-credit-card-payment) traz `data.transactions`: a lista de `id` das vendas criadas. Grave esses `id` junto do seu pedido. Resposta resumida: ```json { "data": { "offer_identifier": "PPP1234567890", "transactions": ["a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d"] } } ``` Na assinatura, a resposta `201` de [`POST /plans/offer/{id}/subscribe`](/docs/referencia/assinaturas/create-subscription) traz a assinatura em `data`. Grave o `data.id`. > **A referência fica só na primeira venda da assinatura** > > Na assinatura, `external_reference` é gravada na venda do primeiro ciclo. As vendas das renovações não têm referência. Por isso, `GET /sales?external_reference=PEDIDO-1234` traz só a primeira venda. 2. **Grave o net\_amount que chega no webhook** Cada evento de venda traz os valores da venda. `net_amount` é o valor da venda **sem** os juros do parcelamento: `total_amount` menos `installment_tax`. Ele só existe no webhook. Os valores em dinheiro estão em reais. O tipo muda entre o webhook e a API (veja [Valores nas respostas](/docs/guias/fundamentos/valores-datas-e-identificadores#valores-nas-respostas)): | Campo | No webhook | Na API | | ----------------- | ------------------- | ------------- | | `total_amount` | Texto: `"197.0000"` | Número: `197` | | `net_amount` | Número: `197` | Não existe | | `installment_tax` | Texto: `"0.0000"` | Não existe | Converta o texto para número antes de gravar e de comparar: no JavaScript, `"197.0000" === 197` é `false`. O `identifier` chega igual ao da API, com o prefixo, e pode ser gravado como chegou: ```js const buildSaleRecord = (payload) => { const { transaction, subscription, source } = payload.data; return { id: transaction.id, identifier: transaction.identifier, status: transaction.status, type: transaction.type, cycle: transaction.cycle, total_amount: Number(transaction.total_amount), installment_tax: Number(transaction.installment_tax), net_amount: transaction.net_amount, subscription_id: subscription?.id ?? null, channel: source?.channel ?? null, }; }; ``` Grave um registro por `id`. Se o mesmo `id` chegar de novo, atualize o registro em vez de criar outro. Veja [Processar eventos sem duplicar](/docs/webhooks/processar-sem-duplicar). 3. **Baixe as vendas do período** Chame [`GET /sales`](/docs/referencia/vendas/list-sales) página por página, com o período em `created_from` e `created_to`. Siga as regras de [Percorra todas as páginas sem perder registros](/docs/guias/fundamentos/paginacao-e-filtros#percorrer) e pare quando `page` passar de `meta.total_pages`. A listagem ordena só por `created_at`, sem critério de desempate: vendas com o mesmo `created_at` podem mudar de posição entre uma página e outra, por isso guarde as vendas num mapa pelo `id`. #### cURL ```bash curl "https://api.pagpolar.com/v1/sales?page=1&per_page=100&created_from=2026-09-01T00:00:00-03:00&created_to=2026-09-15T23:59:59-03:00" \ -H "Authorization: Bearer " ``` Repita a chamada com `page=2`, `page=3` e assim por diante, até o valor de `meta.total_pages`. #### Node.js ```js const apiUrl = 'https://api.pagpolar.com/v1'; const accessToken = ''; const createdFrom = '2026-09-01T00:00:00-03:00'; const createdTo = '2026-09-15T23:59:59-03:00'; const wait = (seconds) => new Promise((resolve) => setTimeout(resolve, seconds * 1000)); const fetchPage = async (path, query) => { const response = await fetch(`${apiUrl}${path}?${new URLSearchParams(query)}`, { headers: { Authorization: `Bearer ${accessToken}` }, }); if (response.status === 429) { await wait(Number(response.headers.get('Retry-After') ?? 60)); return fetchPage(path, query); } if (!response.ok) { throw new Error(`Erro ${response.status}: ${await response.text()}`); } return response.json(); }; const salesById = new Map(); let page = 1; let totalPages = 1; do { const body = await fetchPage('/sales', { page: String(page), per_page: '100', created_from: createdFrom, created_to: createdTo, }); for (const sale of body.data) { salesById.set(sale.id, sale); } totalPages = body.meta.total_pages; page += 1; } while (page <= totalPages); console.log(`${salesById.size} vendas no período`); ``` Resposta resumida de uma venda. Todos os campos estão na [referência de `GET /sales`](/docs/referencia/vendas/list-sales). ```json { "id": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d", "identifier": "PPO9876543210", "external_reference": "PEDIDO-1234", "status": "PAID", "type": "BILLING", "payment_method": "PIX", "installments": 1, "total_amount": 197, "cycle": 1, "paid_at": "2026-09-15T14:35:00.000Z", "created_at": "2026-09-15T14:00:00.000Z" } ``` A listagem traz **todas** as vendas da sua conta: as criadas pela API e as do checkout da PagPolar. A venda da API não diz o canal. Para separar, use o `data.source.channel` que chegou no webhook. Veja [Formato do evento](/docs/webhooks/formato-do-evento#source). 4. **Separe as vendas pelo type** A listagem traz mais de um tipo de venda. Não some os tipos como se fossem a mesma coisa. | `type` | O que é | | ---------- | ----------------------------------------------------------------------------------------------------- | | `BILLING` | A cobrança feita ao cliente numa venda sua. | | `TRANSFER` | Um repasse: a sua parte numa venda de **outra** conta, que divide o valor com você. Não gera webhook. | ```js const allSales = [...salesById.values()]; const billingSales = allSales.filter((sale) => sale.type === 'BILLING'); const transferSales = allSales.filter((sale) => sale.type === 'TRANSFER'); ``` 5. **Compare com o seu sistema** Junte as vendas da API com os registros do seu sistema pelo `id`. Para cada diferença: | Situação | O que fazer | | -------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Uma venda `TRANSFER` ou `FEE` está na API e não está no seu sistema. | É esperado: essas vendas não geram webhook. Grave a venda com os dados da API. | | Uma venda `BILLING` está na API e não está no seu sistema. | Um evento não chegou. Grave a venda com os dados da API. Para ter o `net_amount`, reenvie o evento pelo painel. Veja [Entregas e retentativas](/docs/webhooks/entregas-e-retentativas). | | A venda está no seu sistema e não está na API. | Confira a data de criação: o filtro usa `created_at`. Depois consulte [`GET /sales/{identifier}`](/docs/referencia/vendas/get-sale) com o `id`. `404` quer dizer que a venda não existe na sua conta. | | O `status` é diferente. | Vale o da API, que é o atual. Atualize o seu registro. | | `total_amount` é maior que o `net_amount` gravado. | São os juros do parcelamento (`installment_tax`). Não é erro. | | Uma venda do seu pedido não tem `id` gravado. | Procure pela sua referência: `GET /sales?external_reference=PEDIDO-1234`. | #### cURL ```bash curl "https://api.pagpolar.com/v1/sales?external_reference=PEDIDO-1234" \ -H "Authorization: Bearer " ``` #### Node.js ```js const apiUrl = 'https://api.pagpolar.com/v1'; const accessToken = ''; const query = new URLSearchParams({ external_reference: 'PEDIDO-1234' }); const response = await fetch(`${apiUrl}/sales?${query}`, { headers: { Authorization: `Bearer ${accessToken}` }, }); if (!response.ok) { throw new Error(`Erro ${response.status}: ${await response.text()}`); } const body = await response.json(); console.log(body.data.map((sale) => ({ id: sale.id, status: sale.status }))); ``` A mesma `external_reference` pode estar em mais de uma venda. O filtro traz todas, da mais nova para a mais antiga. > **O período pega a criação, não a mudança de status** > > `created_from` e `created_to` filtram pela data de criação da venda. Uma venda de agosto estornada em setembro não aparece na busca de setembro. Para achar mudanças recentes em vendas antigas, confira os pedidos em [`GET /refunds`](/docs/referencia/reembolsos/list-refunds), que filtra pela data do pedido, e os eventos recebidos no período. 6. **Baixe as assinaturas** [`GET /subscriptions`](/docs/referencia/assinaturas/list-subscriptions) aceita `created_from` e `created_to`, como a listagem de vendas. Pagine do mesmo jeito do passo 3. O exemplo em Node.js reaproveita `fetchPage`, `createdFrom` e `createdTo` do passo 3. #### cURL ```bash curl "https://api.pagpolar.com/v1/subscriptions?page=1&per_page=100&created_from=2026-09-01T00:00:00-03:00&created_to=2026-09-15T23:59:59-03:00" \ -H "Authorization: Bearer " ``` Repita a chamada com `page=2`, `page=3` e assim por diante, até o valor de `meta.total_pages`. #### Node.js ```js const subscriptionsById = new Map(); let subscriptionPage = 1; let subscriptionTotalPages = 1; do { const body = await fetchPage('/subscriptions', { page: String(subscriptionPage), per_page: '100', created_from: createdFrom, created_to: createdTo, }); for (const subscription of body.data) { subscriptionsById.set(subscription.id, subscription); } subscriptionTotalPages = body.meta.total_pages; subscriptionPage += 1; } while (subscriptionPage <= subscriptionTotalPages); console.log(`${subscriptionsById.size} assinaturas criadas no período`); ``` Resposta resumida de uma assinatura. Todos os campos estão na [referência de `GET /subscriptions`](/docs/referencia/assinaturas/list-subscriptions). ```json { "id": "5f6e7d8c-9b0a-4c1d-8e2f-3a4b5c6d7e8f", "status": "ACTIVE", "payment_method": "CREDIT_CARD", "start_at": "2026-09-02T10:00:00.000Z", "next_billing_at": "2026-10-02T10:00:00.000Z", "next_billing_amount": 97, "total_amount": 97, "canceled_at": null, "created_at": "2026-09-02T10:00:00.000Z" } ``` Cada cobrança da assinatura é uma venda própria, com `id` próprio e o `cycle` da cobrança. Para ligar a venda à assinatura, use o `data.subscription.id` do webhook: a venda da API **não** informa a assinatura. ## Eventos de webhook deste fluxo | Evento | O que traz para a conciliação | | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------- | | [`TRANSACTION_CREATED`](/docs/webhooks/eventos/transaction-created) | O `id` da venda, os valores e o `net_amount`. | | [`TRANSACTION_PAID`](/docs/webhooks/eventos/transaction-paid) | O `paid_at` e o status `PAID`. | | [`TRANSACTION_REFUNDED`](/docs/webhooks/eventos/transaction-refunded), [`TRANSACTION_CANCELED`](/docs/webhooks/eventos/transaction-canceled), [`TRANSACTION_EXPIRED`](/docs/webhooks/eventos/transaction-expired), [`TRANSACTION_CHARGEBACK_APPROVED`](/docs/webhooks/eventos/transaction-chargeback-approved) | A mudança de status de uma venda que pode ser de outro período. | | [`SUBSCRIPTION_CREATED`](/docs/webhooks/eventos/subscription-created) | O `id` da assinatura. | | [`SUBSCRIPTION_RENEWED`](/docs/webhooks/eventos/subscription-renewed) | O aviso de um novo ciclo pago no cartão. | ## Quando algo dá errado Além dos [erros comuns a todas as rotas](/docs/guias/fundamentos/erros#erros-comuns), este fluxo pode responder: | Passo | Situação | Resposta | Como resolver | | ---------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------- | | Baixar as vendas | `per_page` acima de 100. | `400 invalid_request` | Use `per_page` de 1 a 100. | | Baixar as vendas | `created_from` ou `created_to` fora do formato ISO 8601. | `400 invalid_request` | Envie data e hora completas: `2026-09-15T23:59:59-03:00`. | | Baixar as vendas | `status` ou `payment_method` com valor que não existe. | `400 invalid_request` | Use os valores da [referência de `GET /sales`](/docs/referencia/vendas/list-sales). | | Baixar as vendas | Filtro que a listagem não aceita, como `email`. | `400 invalid_request` | Veja os filtros de cada rota em [Paginação e filtros](/docs/guias/fundamentos/paginacao-e-filtros). | | Baixar as vendas | A mesma venda veio em duas páginas. | `200` | Guarde as vendas pelo `id`. | | Comparar | A soma das vendas ficou maior que o esperado. | — | Separe `BILLING` de `TRANSFER`. | | Comparar | Os valores do webhook não batem com os da API. | — | Converta os textos do webhook com `Number()` antes de comparar. | | Comparar | `GET /sales/{identifier}` com um `id` que não é da sua conta. | `404 not_found` com `Venda não encontrada` | Confira o `id` gravado. | | Comparar | `GET /sales/{identifier}` com uma `external_reference` no formato do código da venda (10 dígitos ou o prefixo seguido de 10 dígitos) devolveu outra venda. | `200` com a venda errada | A busca por código vem antes da busca por referência. Use a listagem com `?external_reference=`, que só procura pela referência. | | Comparar | `?external_reference=` não trouxe as renovações de uma assinatura. | `200` só com a primeira venda | As renovações não têm referência. Ligue as vendas pelo `data.subscription.id` do webhook. | ## Próximos passos - [Acompanhar reembolsos e chargebacks](/docs/guias/jornadas/acompanhar-reembolsos-e-chargebacks) — Trate as vendas que mudam de status depois do período. - [Formato do evento](/docs/webhooks/formato-do-evento) — Veja todos os campos de valor que chegam no webhook. - [Limites de requisição](/docs/guias/fundamentos/limites-de-requisicao) — Pagine sem estourar o limite por minuto. --- # Criar produto e oferta URL: https://staging.pagpolar.com/docs/guias/jornadas/criar-produto-e-oferta > Crie um produto, crie a oferta com preço, meios de pagamento e parcelas, e obtenha o código da oferta para vender. Toda venda pela API aponta para uma **oferta**. A oferta pertence a um **produto**. Por isso, antes da primeira cobrança, você cria os dois. Use este guia quando o preço é fixo e você quer reaproveitar a mesma oferta em muitas vendas. Se o preço é decidido na hora, como num orçamento, você pode informar a oferta direto na cobrança. Veja [Ofertas, planos e ofertas ocultas](/docs/guias/conceitos/ofertas-planos-e-ofertas-ocultas). > **Assinatura segue outro caminho** > > Para cobrança recorrente, crie um plano com `POST /plans` e a oferta de plano com `POST /plans/{id}/offers`. Este guia cobre só o produto avulso. ## Visão geral O diagrama mostra as três chamadas deste guia, na ordem. As mensagens 1 e 2 são o passo 1. As mensagens 3 e 4 são o passo 2. As mensagens 5 e 6 são o passo 3. ```mermaid sequenceDiagram autonumber participant S as Seu servidor participant A as API PagPolar S->>A: POST /products com name e description A-->>S: 201 com data.id do produto S->>A: POST /offers com product_id, price, meios e parcelas A-->>S: 201 com data.id e data.identifier da oferta S->>A: GET /offers/{identifier} A-->>S: 200 com os dados da oferta ``` ## Antes de começar * Uma chave de API. Se ainda não tem, siga o [Início rápido](/docs/guias/inicio-rapido). * Um token de acesso. Veja [Autenticação](/docs/guias/fundamentos/autenticacao#obter-o-token); os exemplos em Node.js assumem a variável `accessToken`. * Um terminal com `curl`, ou Node.js 18 ou mais novo. Salve os exemplos em Node.js em um arquivo `.mjs` e rode com `node arquivo.mjs`. > **Estas chamadas não têm proteção contra repetição** > > `POST /products` e `POST /offers` não usam `Idempotency-Key`. Cada chamada cria um registro novo. Se você repetir a chamada, fica com dois produtos ou duas ofertas. Guarde o `id` retornado antes de tentar de novo. ## Passo a passo 1. **Crie o produto** Envie o nome do produto. A descrição é opcional. #### cURL ```bash curl -X POST "https://api.pagpolar.com/v1/products" \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "name": "Curso de fotografia", "description": "Curso online com 20 aulas" }' ``` #### Node.js ```js const response = await fetch('https://api.pagpolar.com/v1/products', { method: 'POST', headers: { Authorization: `Bearer ${accessToken}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ name: 'Curso de fotografia', description: 'Curso online com 20 aulas', }), }); console.log(response.status, await response.json()); ``` | Campo | Obrigatório | O que é | | ------------- | ----------- | ------------------------------------------------------------- | | `name` | Sim | Nome do produto, até 255 caracteres. | | `description` | Não | Descrição, até 5000 caracteres. Aceita texto vazio ou `null`. | A rota aceita só esses dois campos. Qualquer outro campo responde `400`. Resposta `201` (resumida): ```json { "data": { "id": "c4d5e6f7-a8b9-4c0d-8e2f-3a4b5c6d7e8f", "name": "Curso de fotografia", "description": "Curso online com 20 aulas", "type": "DIGITAL", "is_active": true, "warranty_time": 7 } } ``` O que a API faz por você: * O produto nasce **digital** (`type: DIGITAL`) e **ativo** (`is_active: true`). * `warranty_time` é o prazo de garantia em dias. Vem da configuração da plataforma. Sem configuração, vale `7`. > **Produto físico é cadastrado no painel** > > A API cria só produtos `DIGITAL`. Produto físico é cadastrado no painel, com peso, dimensões e frete — e aí vende pela API normalmente: veja [Vender um produto físico](/docs/guias/jornadas/vender-um-produto-fisico). O envio e o código de rastreio continuam só no painel. Guarde `data.id`. Ele é o `` do próximo passo. Contrato completo: [`POST /products`](/docs/referencia/produtos/create-product). 2. **Crie a oferta** A oferta define o preço, os meios de pagamento e o máximo de parcelas no cartão. `price` vai em **centavos**, como número inteiro (`9700` = R$ 97,00), e volta em **reais** na resposta (`97`). Veja [Valores nas respostas](/docs/guias/fundamentos/valores-datas-e-identificadores#valores-nas-respostas). #### cURL ```bash curl -X POST "https://api.pagpolar.com/v1/offers" \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "product_id": "", "title": "Oferta principal", "price": 9700, "is_enabled_pix": true, "is_enabled_billet": true, "is_enabled_credit_card": true, "max_credit_card_installments": 12 }' ``` #### Node.js ```js const response = await fetch('https://api.pagpolar.com/v1/offers', { method: 'POST', headers: { Authorization: `Bearer ${accessToken}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ product_id: '', title: 'Oferta principal', price: 9700, is_enabled_pix: true, is_enabled_billet: true, is_enabled_credit_card: true, max_credit_card_installments: 12, }), }); console.log(response.status, await response.json()); ``` | Campo | Obrigatório | Se você não enviar | O que é | | ------------------------------ | ----------- | ------------------ | ------------------------------------------------------------------------------------------------------- | | `product_id` | Sim | — | `id` do produto criado no passo 1. | | `price` | Sim | — | Preço em centavos, número inteiro a partir de `0`. | | `title` | Não | Fica sem título | Nome da oferta, até 255 caracteres. | | `is_enabled_pix` | Não | `true` | Aceita PIX. Veja o [valor mínimo](#meios-de-pagamento). | | `is_enabled_billet` | Não | `true` | Aceita boleto. Veja o [valor mínimo](#meios-de-pagamento). | | `is_enabled_credit_card` | Não | `true` | Aceita cartão de crédito. Veja o [valor mínimo](#meios-de-pagamento). | | `max_credit_card_installments` | Não | `12` | Máximo de parcelas no cartão. Número inteiro a partir de `1`. Veja a [parcela mínima](#parcela-minima). | | `is_active` | Não | `true` | Oferta ativa. Uma oferta inativa não vende. | #### Meios de pagamento e valor mínimo Os três meios de pagamento começam ligados. Cada um tem um valor mínimo, conferido **ao salvar** a oferta: | Meio | Valor mínimo | Em `price` (centavos) | | ----------------- | ------------ | --------------------- | | PIX | R$ 5,00 | `500` | | Cartão de crédito | R$ 5,00 | `500` | | Boleto | R$ 10,00 | `1000` | * Abaixo do mínimo, a API desliga o meio, mesmo que você envie `true`. * A partir do mínimo, o meio fica ligado, a não ser que você envie `false`. * A resposta mostra o resultado em `payment_methods` (`pix`, `credit_card` e `billet`). Confira sempre. Exemplos, com `max_credit_card_installments: 1` e sem enviar `is_enabled_*`: | `price` | Meios ligados em `payment_methods` | | ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `300` (R$ 3,00) | Nenhum meio passa do mínimo. Na oferta avulsa, a criação responde `400` pela [parcela mínima](#parcela-minima). Numa oferta de plano, que não tem parcela mínima, a oferta é criada com os três meios desligados. | | `700` (R$ 7,00) | PIX e cartão. O boleto fica desligado. | | `1500` (R$ 15,00) | PIX, cartão e boleto. | Ao editar a oferta com [`PATCH /offers/{id}`](/docs/referencia/ofertas/update-offer), a regra roda de novo: | Você envia | O que acontece com os meios | | ------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `price` | Os três meios são recalculados com o preço novo. Um meio desligado só por causa do preço volta a ligar se o preço chegar ao mínimo. Para manter um meio desligado, envie `false` na mesma chamada. | | Só `is_enabled_*`, sem `price` | Só os campos enviados são recalculados, com o preço atual da oferta. | | Nem `price` nem `is_enabled_*` | Os meios não mudam. | #### A regra da parcela mínima A API converte `price` para reais, dividindo por 100, e divide o resultado por `max_credit_card_installments`. O que sobra é o valor de cada parcela. Por padrão, cada parcela precisa valer pelo menos **R$ 5,00**. Ao contrário do valor mínimo do meio, que só desliga o meio, a parcela mínima **recusa** a oferta: abaixo dela, a resposta é `400`, e a mensagem mostra o valor da parcela e o mínimo em vigor. A regra roda ao criar e ao editar a oferta, mesmo com o cartão desligado. | `price` | `max_credit_card_installments` | Parcela | Resultado | | ------- | ------------------------------ | ------- | --------- | | `9700` | `12` | R$ 8,08 | Criada | | `6000` | `12` | R$ 5,00 | Criada | | `4990` | não enviado (vale `12`) | R$ 4,16 | `400` | | `4990` | `9` | R$ 5,54 | Criada | | `1000` | `12` | R$ 0,83 | `400` | | `0` | qualquer | R$ 0,00 | `400` | Sem `max_credit_card_installments`, a conta usa 12 parcelas: todo `price` abaixo de `6000` recusa a criação. Para uma oferta barata, envie `max_credit_card_installments: 1`. Mesmo assim, um `price` abaixo de `500` não passa. Resposta `201` (resumida): ```json { "data": { "id": "d5e6f7a8-b9c0-4d1e-8f3a-4b5c6d7e8f9a", "identifier": "PPP1234567890", "title": "Oferta principal", "price": 97, "is_active": true, "product_id": "c4d5e6f7-a8b9-4c0d-8e2f-3a4b5c6d7e8f", "payment_methods": { "pix": true, "credit_card": true, "billet": true }, "max_credit_card_installments": 12 } } ``` | Campo | O que fazer com ele | | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | | `identifier` | É o **código da oferta**: `PPP` seguido de 10 dígitos. Guarde. É o `` que você envia em `offer_identifier` nas cobranças. | | `id` | Id interno da oferta (uuid). Use para editar a oferta com `PATCH /offers/{id}`. | | `payment_methods` | Os meios de pagamento ligados. `billet` é o boleto. | Contrato completo: [`POST /offers`](/docs/referencia/ofertas/create-offer). 3. **Consulte a oferta** Antes de vender, confira se a oferta está como você espera. Envie o código da oferta ou o `id`. #### cURL ```bash curl "https://api.pagpolar.com/v1/offers/" \ -H "Authorization: Bearer " ``` #### Node.js ```js const response = await fetch('https://api.pagpolar.com/v1/offers/', { headers: { Authorization: `Bearer ${accessToken}` }, }); console.log(response.status, await response.json()); ``` O caminho aceita três formatos: | Você envia | Como a API procura | | -------------------------------------------------------------------- | ------------------------------------------- | | Um uuid | Pelo `id` da oferta. | | O código, com ou sem o prefixo, como `PPP1234567890` ou `1234567890` | Pelo `identifier`. | | Um link que termina com o código | Usa só os números do último pedaço do link. | A resposta `200` traz os mesmos campos da criação. Confira três coisas antes de vender: | Campo | O que precisa estar certo | | ------------------------------ | ---------------------------------------------------------------------------------------------------------------------------- | | `is_active` | `true`. A consulta também devolve ofertas inativas, mas uma cobrança com oferta inativa responde `409` com `Oferta inativa`. | | `payment_methods` | O meio que você vai cobrar está `true`. | | `max_credit_card_installments` | Cobre o número de parcelas que você vai oferecer no cartão. | Contrato completo: [`GET /offers/{identifier}`](/docs/referencia/ofertas/get-offer). Para ver todas as ofertas de um produto, use [`GET /offers/by-product/{id}`](/docs/referencia/ofertas/list-product-offers). ## Confira no painel O produto e a oferta criados pela API aparecem no painel da sua conta. Em **Meus produtos → Produtos**, cada produto aparece com o tipo, o status e a quantidade de ofertas: Ao abrir o produto, a aba **Ofertas e Configurações** lista as ofertas com o código (o mesmo usado em `offer_identifier`), o preço, os meios de pagamento e as parcelas: ## Eventos de webhook deste fluxo Nenhum. Criar ou consultar produto e oferta não envia webhook. Os eventos começam quando você cria uma cobrança. ## Quando algo dá errado Além dos [erros comuns a todas as rotas](/docs/guias/fundamentos/erros#erros-comuns), este fluxo pode responder: | Passo | Situação | Resposta | Como resolver | | ----- | -------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- | | 1 | Faltou `name`. | `400 invalid_request` com `O campo nome é obrigatório` | Envie `name`. | | 1 | `name` com mais de 255 caracteres. | `400 invalid_request` com `O campo nome deve ter no máximo 20 caracteres` | O limite real é 255. Encurte o nome. | | 1 | Campo que a rota não aceita, como `type` ou `price`. | `400 invalid_request` com `O campo type não e permitido` | Envie só `name` e `description`. O preço vai na oferta. | | 2 | Faltou `product_id` ou `price`. | `400 invalid_request` com `O campo product_id é obrigatório` ou `O campo price é obrigatório` | Envie os dois campos. | | 2 | `product_id` não é um uuid. | `400 invalid_request` com `O campo product_id deve ser um UUID válido` | Use o `data.id` do passo 1, não o nome do produto. | | 2 | `price` com texto que não é número. | `400 invalid_request` com `O campo price deve ser um número` | Envie um número em centavos, como `9700`. | | 2 | `price` com casas decimais, como `49.9`. | `400 invalid_request` | `price` é em centavos e só aceita inteiro. Envie `4990`. | | 2 | `price` negativo, ou `max_credit_card_installments` igual a `0` ou com casas decimais. | `400 invalid_request` com `message` vazia | Envie `price` inteiro a partir de `0` e parcelas como número inteiro a partir de `1`. | | 2 | O produto não existe, foi removido ou é de outra conta. | `404 not_found` com `Produto não encontrado` | Confira o `product_id`. A chave precisa ser da mesma conta que criou o produto. | | 2 | Parcela abaixo do mínimo. | `400 invalid_request` com `O valor da parcela (R$ 4.16) fica abaixo do mínimo permitido (R$ 5.00). Reduza o número de parcelas ou aumente o preço da oferta.` | Diminua `max_credit_card_installments` ou aumente `price`. Veja [a regra da parcela mínima](#parcela-minima). | | 3 | Código errado, oferta removida, de outra conta ou oferta oculta. | `404 not_found` com `Oferta não encontrada` | Use o `identifier` do passo 2. Oferta oculta não abre por código. | ## Próximos passos - [Vender com PIX ou boleto](/docs/guias/jornadas/vender-com-pix-ou-boleto) — Use o código da oferta para cobrar por PIX ou boleto. - [Vender com cartão de crédito](/docs/guias/jornadas/vender-com-cartao) — Cobre no cartão, parcelado ou não. - [Vender com afiliado](/docs/guias/jornadas/vender-com-afiliado) — Credite a venda ao afiliado que trouxe o cliente. - [Ofertas, planos e ofertas ocultas](/docs/guias/conceitos/ofertas-planos-e-ofertas-ocultas) — Informe a oferta na hora da venda, sem criar antes. --- # Trocar o cartão da assinatura URL: https://staging.pagpolar.com/docs/guias/jornadas/trocar-cartao-da-assinatura > Troque o cartão de crédito de uma assinatura sem cancelar, sem mudar o plano e sem cobrar o cliente. ## Quando usar Use este guia quando o cliente quer pagar a assinatura com outro cartão. Exemplos: o cartão venceu, foi perdido ou o cliente prefere outro. A troca: * **não** cobra nada; * **não** muda o plano, o valor nem as datas da assinatura; * **não** cancela a assinatura. Só funciona em assinatura **no cartão**. Se o cliente quer mudar de plano e pagar a diferença com um cartão novo, use [Trocar de plano](/docs/guias/jornadas/trocar-de-plano) com `payment_choice: "new_card"`. ## Visão geral O diagrama mostra o caminho de uma troca de cartão. ```mermaid sequenceDiagram autonumber participant I as Seu sistema participant A as API participant G as Gateway I->>A: PATCH /subscriptions/ID/card com credit_card alt assinatura sem cadastro no gateway, como PIX ou boleto A-->>I: 400 invalid_request else permitido A->>G: cadastra o novo cartão A->>G: troca o cartão da assinatura A-->>I: 200 success true end ``` ## Antes de começar * Um token de acesso. Veja [Autenticação](/docs/guias/fundamentos/autenticacao#obter-o-token); os exemplos assumem a variável `accessToken`. * Uma assinatura no cartão e o `id` dela. Veja [Assinar um plano](/docs/guias/jornadas/assinar-um-plano). * Os dados do novo cartão, informados pelo cliente. ## Passo a passo 1. **Confira a assinatura** Confira em [`GET /subscriptions/{id}`](/docs/referencia/assinaturas/get-subscription) se `payment_method` é `CREDIT_CARD`. 2. **Envie o novo cartão** Chame [`PATCH /subscriptions/{id}/card`](/docs/referencia/assinaturas/update-subscription-card) com os dados do cartão dentro de `credit_card`. | 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](/docs/guias/fundamentos/valores-datas-e-identificadores#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](/docs/guias/fundamentos/ambientes#dados-de-teste). Esta rota **não** usa `Idempotency-Key`, porque não cobra nada. #### cURL ```bash curl -X PATCH "https://api.pagpolar.com/v1/subscriptions//card" \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "credit_card": { "holder_name": "MARIA SILVA", "holder_document": "", "number": "", "expiration_month": 12, "expiration_year": 2030, "cvv": "" } }' ``` #### Node.js ```js const response = await fetch( 'https://api.pagpolar.com/v1/subscriptions//card', { method: 'PATCH', headers: { Authorization: `Bearer ${accessToken}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ credit_card: { holder_name: 'MARIA SILVA', holder_document: '', number: '', expiration_month: 12, expiration_year: 2030, cvv: '', }, }), }, ); console.log(response.status, await response.json()); ``` Resposta `200`: ```json { "data": { "success": true } } ``` > **Os nomes dos campos são diferentes na troca de plano** > > Aqui o objeto se chama `credit_card` e a validade vai em `expiration_month` e `expiration_year`. Na [troca de plano](/docs/guias/jornadas/trocar-de-plano) com cartão novo, o objeto se chama `card` e a validade vai em `exp_month` e `exp_year`. 3. **Registre a troca no seu sistema** Com a resposta `200`, o novo cartão já está na assinatura do gateway. As próximas cobranças da assinatura usam esse cartão. * A resposta não traz os dígitos do cartão. Se quiser mostrar ao cliente, guarde os 4 últimos dígitos no seu sistema antes de enviar. * `GET /subscriptions/{id}` continua igual: plano, valor e datas não mudam. ## Eventos de webhook deste fluxo Nenhum webhook avisa a troca: use a resposta `200` como confirmação. As cobranças seguintes da assinatura continuam gerando os eventos de sempre. Veja [Ciclo de vida da assinatura](/docs/guias/conceitos/ciclo-de-vida-da-assinatura). ## Quando algo dá errado Além dos [erros comuns a todas as rotas](/docs/guias/fundamentos/erros#erros-comuns), este fluxo pode responder: | Passo | Situação | Resposta | Como resolver | | ----- | --------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | 1 e 2 | A assinatura não existe na sua conta | `404 not_found` com `Assinatura não encontrada` | Confira o `id` da assinatura. | | 2 | Valor inválido, como mês `13`, ano fora de `2000` a `2100` ou número de cartão inválido | `400 invalid_request`, em geral com `message` vazia | Confira os campos na tabela do passo 2. | | 2 | Assinatura sem cadastro no gateway. Acontece com assinatura em PIX ou boleto. | `400 invalid_request` com `Assinatura não possui ID externo no gateway.` | Não há cartão para trocar nesta assinatura. | | 2 | Assinatura sem cliente vinculado | `400 invalid_request` com `Cliente não encontrado na assinatura.` | Fale com o suporte informando o `request_id`. | | 2 | O gateway recusou o cadastro do cartão | `400 invalid_request`. Exemplos de `message`: `Dados do cartão inválidos. Verifique o número, validade e CVV e tente novamente.` ou `Não foi possível processar o cartão de crédito. Verifique os dados e tente novamente.` | Peça ao cliente para conferir os dados ou usar outro cartão. | | 2 | Erro inesperado, inclusive quando o gateway recusa a troca na assinatura | `500 internal_error` | A troca não cobra nada, então repetir é seguro: cada chamada só cadastra o cartão de novo e o coloca na assinatura. Se o erro continuar, fale com o suporte informando o `request_id`. | ## Próximos passos - [Trocar de plano](/docs/guias/jornadas/trocar-de-plano) — Mude a assinatura para outra oferta de plano. - [Ciclo de vida da assinatura](/docs/guias/conceitos/ciclo-de-vida-da-assinatura) — Entenda os status e os eventos das renovações. - [Erros](/docs/guias/fundamentos/erros) — Saiba quais erros é seguro repetir. --- # Trocar de plano URL: https://staging.pagpolar.com/docs/guias/jornadas/trocar-de-plano > Liste as ofertas disponíveis, calcule o valor e mude a assinatura para um plano mais caro ou mais barato sem cancelar. ## Quando usar Use este guia quando o cliente quer mudar a assinatura para **outra oferta de plano do mesmo produto**, sem cancelar. Exemplo: sair do plano mensal de R$ 49,90 para o de R$ 99,90. A API decide o tipo da troca pelo preço: | Tipo | Quando | O que acontece | | ----------- | ----------------------------------------------- | ---------------------------------------------------------------------------- | | `UPGRADE` | O preço da nova oferta é **maior** que o atual. | A diferença é cobrada na hora. O plano muda quando o pagamento é confirmado. | | `DOWNGRADE` | O preço da nova oferta é **menor** que o atual. | Nada é cobrado. A troca fica agendada para a próxima renovação. | Ofertas com o **mesmo preço** não podem ser trocadas entre si. A troca para uma oferta de **outro produto** também não é aceita. Para trocar só o cartão, sem mudar o plano, use [Trocar o cartão da assinatura](/docs/guias/jornadas/trocar-cartao-da-assinatura). Todos os valores deste guia estão em **reais**, como número: `99.9` = R$ 99,90. Veja [Valores nas respostas](/docs/guias/fundamentos/valores-datas-e-identificadores#valores-nas-respostas). ## Visão geral O diagrama mostra as três chamadas da troca e os resultados possíveis. ```mermaid sequenceDiagram autonumber participant I as Seu sistema participant A as API participant G as Gateway participant W as Seu servidor de webhook I->>A: GET /subscriptions/ID/plan-options A-->>I: 200 allow_client_plan_change e options I->>A: POST /subscriptions/ID/plan-change/preview A-->>I: 200 type, charge_amount e effective_at I->>A: POST /subscriptions/ID/plan-change com Idempotency-Key alt DOWNGRADE A-->>I: 200 type DOWNGRADE, troca agendada else UPGRADE pago na hora no cartão A->>G: cobra a diferença A-->>I: 200 upgrade.status paid, plano já trocado else UPGRADE pendente em PIX, boleto, cartão ou troca não concluída na hora A-->>I: 200 upgrade.status pending G-)A: pagamento confirmado A-)W: TRANSACTION_PAID e o plano é trocado end ``` ## Antes de começar * Um token de acesso. Veja [Autenticação](/docs/guias/fundamentos/autenticacao#obter-o-token); os exemplos assumem a variável `accessToken`. * Uma assinatura e o `id` dela. Veja [Assinar um plano](/docs/guias/jornadas/assinar-um-plano). * O produto com a troca de plano ligada no painel. A opção **Cliente pode trocar de plano?** fica no cadastro do produto e nasce desligada. * Cada oferta de destino com a opção **Plano selecionável pelo cliente?** ligada no painel. Ela também nasce desligada. * A oferta de destino ativa e com preço diferente do plano atual. > **As duas opções só existem no painel** > > A API não liga nem desliga **Cliente pode trocar de plano?** e **Plano selecionável pelo cliente?**. Ajuste as duas no painel antes de integrar. A opção **Cliente pode trocar de plano?** fica na aba **Geral** do plano, em **Meus produtos → Assinaturas**: ## Passo a passo 1. **Liste as ofertas disponíveis** Chame [`GET /subscriptions/{id}/plan-options`](/docs/referencia/assinaturas/list-subscription-plan-options): #### cURL ```bash curl "https://api.pagpolar.com/v1/subscriptions//plan-options" \ -H "Authorization: Bearer " ``` #### Node.js ```js const response = await fetch( 'https://api.pagpolar.com/v1/subscriptions//plan-options', { headers: { Authorization: `Bearer ${accessToken}` }, }, ); console.log(response.status, await response.json()); ``` Resposta resumida: ```json { "data": { "allow_client_plan_change": true, "current_product_price_id": "", "options": [ { "id": "", "title": "Plano Básico", "price": 49.9, "is_active": true, "cycle": "MONTHLY", "cycle_interval": 1 }, { "id": "", "title": "Plano Premium", "price": 99.9, "is_active": true, "cycle": "MONTHLY", "cycle_interval": 1 } ] } } ``` Como ler a resposta: * `allow_client_plan_change` é `false`: o produto não permite troca. A execução vai responder `403`. Pare aqui ou ligue a opção no painel. * `options` traz as ofertas do mesmo produto que estão ativas e marcadas como selecionáveis. * `options` **sempre inclui a oferta atual**, mesmo que ela não seja mais selecionável. Esconda do cliente a opção cujo `id` é igual a `current_product_price_id`. * A lista não é filtrada por preço. Uma oferta com o mesmo preço da atual aparece, mas a troca para ela responde `400`. 2. **Calcule o valor da troca** Mostre ao cliente quanto ele vai pagar antes de trocar. Chame [`POST /subscriptions/{id}/plan-change/preview`](/docs/referencia/assinaturas/preview-subscription-plan-change) com o `id` da oferta escolhida em `new_product_price_id`. Esta chamada **não** muda nada na assinatura e não cobra nada. #### cURL ```bash curl -X POST "https://api.pagpolar.com/v1/subscriptions//plan-change/preview" \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "new_product_price_id": "" }' ``` #### Node.js ```js const response = await fetch( 'https://api.pagpolar.com/v1/subscriptions//plan-change/preview', { method: 'POST', headers: { Authorization: `Bearer ${accessToken}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ new_product_price_id: '', }), }, ); console.log(response.status, await response.json()); ``` Resposta de um upgrade. Os números são ilustrativos: ```json { "data": { "type": "UPGRADE", "current_product_price_id": "", "current_price": 49.9, "new_product_price_id": "", "new_price": 99.9, "total_days": 30, "remaining_days": 15, "prorated_credit": 24.95, "charge_amount": 74.95, "effective_at": "2026-09-15T14:00:00.000Z", "current_payment_method": "CREDIT_CARD" } } ``` | Campo | O que significa | | ----------------------------- | ---------------------------------------------------------------------------------------------------- | | `type` | `UPGRADE` ou `DOWNGRADE`. | | `current_price` e `new_price` | Preço da oferta atual e da nova, em reais. | | `total_days` | Dias do ciclo atual. | | `remaining_days` | Dias que faltam até a próxima cobrança. | | `prorated_credit` | Crédito pelos dias que o cliente já pagou e não vai usar. | | `charge_amount` | No upgrade, o valor que será cobrado agora. Mostre este valor ao cliente. | | `effective_at` | Quando a troca vale. No upgrade, é o momento do cálculo. No downgrade, é a data da próxima cobrança. | | `current_payment_method` | Meio de pagamento da assinatura: `CREDIT_CARD`, `PIX` ou `BOLETO`. | A conta do upgrade está em [Como o valor do upgrade é calculado](#calculo). > **No downgrade, ignore charge_amount** > > Em `DOWNGRADE`, `charge_amount` pode vir maior que zero, mas **nada é cobrado**. Use só `type` e `effective_at` para explicar a troca ao cliente. 3. **Execute a troca** Chame [`POST /subscriptions/{id}/plan-change`](/docs/referencia/assinaturas/change-subscription-plan). Envie o header `Idempotency-Key`, uma chave por troca. Veja [Idempotência](/docs/guias/fundamentos/idempotencia). | Campo | Obrigatório | O que enviar | | ---------------------- | ----------------- | -------------------------------------------------------------------------------------------------------------------------------------- | | `new_product_price_id` | Sim | `id` da nova oferta, o mesmo do passo anterior. | | `payment_choice` | Não | Onde cobrar o upgrade. `current` (padrão): no meio de pagamento atual da assinatura. `new_card`: num cartão novo, informado em `card`. | | `card` | Só com `new_card` | Dados do cartão novo. Veja o exemplo abaixo. | Em downgrade, **não** envie `payment_choice` nem `card`: nada é cobrado. O exemplo cobra o upgrade no meio de pagamento atual. Ele também serve para downgrade: #### cURL ```bash curl -X POST "https://api.pagpolar.com/v1/subscriptions//plan-change" \ -H "Authorization: Bearer " \ -H "Idempotency-Key: 7c1e2f3a-4b5c-4d6e-8f90-1a2b3c4d5e6f" \ -H "Content-Type: application/json" \ -d '{ "new_product_price_id": "" }' ``` #### Node.js ```js import { randomUUID } from 'node:crypto'; const idempotencyKey = randomUUID(); const response = await fetch( 'https://api.pagpolar.com/v1/subscriptions//plan-change', { method: 'POST', headers: { Authorization: `Bearer ${accessToken}`, 'Idempotency-Key': idempotencyKey, 'Content-Type': 'application/json', }, body: JSON.stringify({ new_product_price_id: '', }), }, ); console.log(response.status, await response.json()); ``` #### Cobrar o upgrade num cartão novo Envie `payment_choice: "new_card"` e os dados em `card`. Todos os campos de `card` são obrigatórios. `holder_document` é o CPF (11 dígitos) ou CNPJ (14 dígitos) do titular, com ou sem pontuação. Veja [Documento e telefone](/docs/guias/fundamentos/valores-datas-e-identificadores#documento-e-telefone). ```json { "new_product_price_id": "", "payment_choice": "new_card", "card": { "number": "", "holder_name": "MARIA SILVA", "holder_document": "", "exp_month": 12, "exp_year": 2030, "cvv": "" } } ``` Numa assinatura no cartão, depois que o upgrade é pago, o cartão novo passa a ser o cartão da assinatura. > **Os nomes dos campos são diferentes na troca de cartão** > > Aqui o objeto se chama `card` e a validade vai em `exp_month` e `exp_year`. Na [troca de cartão](/docs/guias/jornadas/trocar-cartao-da-assinatura), o objeto se chama `credit_card` e a validade vai em `expiration_month` e `expiration_year`. A resposta é `200`. O conteúdo depende do tipo da troca. **Downgrade:** ```json { "data": { "type": "DOWNGRADE" } } ``` **Upgrade pago na hora, no cartão:** ```json { "data": { "type": "UPGRADE", "upgrade": { "transaction_id": "", "charge_amount": 74.95, "status": "paid" } } } ``` **Upgrade pendente, em PIX:** ```json { "data": { "type": "UPGRADE", "upgrade": { "transaction_id": "", "charge_amount": 74.95, "status": "pending", "pix": { "qr_code": "" } } } } ``` Em boleto, no lugar de `pix` vem `boleto` com `barcode` (o código do boleto como o gateway devolveu) e `pdf_link` (link do PDF). | `upgrade.status` | O que significa | O que fazer | | ---------------- | ------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `paid` | O cartão foi cobrado na hora. A assinatura já está no novo plano. | Siga para o próximo passo. | | `pending` | A cobrança da diferença foi criada e espera a confirmação do pagamento. A assinatura continua no plano atual. | Em PIX, mostre `pix.qr_code`: ele vale por 1 hora. Em boleto, mostre `boleto.pdf_link`: o vencimento é em 5 dias. Em cartão, espere a confirmação e não cobre de novo. | Guarde `upgrade.transaction_id`. É o `id` da venda da diferença. > **Cartão aprovado pode responder pending** > > Às vezes o cartão é aprovado, mas a troca não pode ser concluída na hora, por exemplo quando o gateway falha ao atualizar o plano da assinatura. Nesse caso a resposta não é erro: vem `200` com `upgrade.status: "pending"`, e a venda da diferença continua em `PROCESSING`. > > A troca é concluída quando o gateway avisa o pagamento: a venda passa para `PAID`, o plano muda e o webhook `TRANSACTION_PAID` é enviado. Trate como qualquer upgrade `pending`. Não peça outro cartão nem repita a troca com outra `Idempotency-Key`. 4. **Confirme o resultado** **Upgrade com `status: "paid"`.** A troca já foi feita. Consulte [`GET /subscriptions/{id}`](/docs/referencia/assinaturas/get-subscription) e confira: * `offer.id` é o `id` da nova oferta; * `next_billing_amount` é o preço da nova oferta; * `next_billing_at` foi recalculado: o novo ciclo começa no dia da troca; * `status` é `ACTIVE`. **Upgrade com `status: "pending"`.** Espere o webhook [`TRANSACTION_PAID`](/docs/webhooks/eventos/transaction-paid) com `data.transaction.id` igual ao `upgrade.transaction_id`. Quando ele chegar, consulte a assinatura e confira os mesmos campos do caso `paid`. Sem webhook, consulte a venda em [`GET /sales/{identifier}`](/docs/referencia/vendas/get-sale) usando o `upgrade.transaction_id`. Quando `status` for `PAID`, consulte a assinatura. Se o cliente não pagar, a assinatura continua no plano atual. **Downgrade.** Nada muda agora: * `GET /subscriptions/{id}` continua mostrando a oferta e o valor atuais até a renovação. * A troca é aplicada quando a cobrança da próxima renovação é paga. A partir daí, `offer` passa a ser a nova oferta. * Na assinatura no cartão, a PagPolar já informa o novo valor ao gateway no momento do agendamento. > **A API não mostra o downgrade agendado** > > Nenhuma resposta da API traz a troca agendada. Guarde no seu sistema a nova oferta e a data de `effective_at` do passo 2 para mostrar ao cliente. Duas regras sobre trocas agendadas: * Um novo downgrade **substitui** o downgrade agendado antes. * Um upgrade confirmado **cancela** o downgrade agendado. ## Como o valor do upgrade é calculado O cliente recebe crédito pelos dias do ciclo que já pagou e não vai usar. O crédito é descontado do preço da nova oferta. | Etapa | Conta | Exemplo | | --------------------------------- | -------------------------------------------------------------------- | ----------------------- | | Dias do ciclo (`total_days`) | Dias entre o início do ciclo atual e a próxima cobrança. | 30 | | Dias restantes (`remaining_days`) | Dias entre hoje e a próxima cobrança. | 15 | | Crédito (`prorated_credit`) | dias restantes × preço atual ÷ dias do ciclo, arredondado em 2 casas | 15 × 49,90 ÷ 30 = 24,95 | | Diferença | preço novo − crédito, nunca abaixo de zero | 99,90 − 24,95 = 74,95 | Depois da diferença, duas regras podem mudar o valor final: * **Valor mínimo por meio de pagamento.** Se a diferença ficar abaixo do mínimo, a cobrança usa o mínimo. PIX: R$ 5,00. Boleto: R$ 10,00. Cartão: valor mínimo configurado pela PagPolar. * **Taxas do meio de pagamento.** O valor final é calculado com as regras de cobrança do meio de pagamento. Por isso, mostre sempre o `charge_amount` do preview. Não refaça a conta no seu sistema. ## Eventos de webhook deste fluxo | Situação | Evento | O que fazer | | --------------------------------------- | ------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------- | | Upgrade pendente e pago depois | [`TRANSACTION_PAID`](/docs/webhooks/eventos/transaction-paid) | Compare `data.transaction.id` com `upgrade.transaction_id`. Consulte a assinatura para ver o novo plano. | | Upgrade pago na hora no cartão | Nenhum | Use a resposta `200`. | | Downgrade agendado | Nenhum | Guarde a troca no seu sistema. | | Renovação em que o downgrade é aplicado | Os eventos de sempre da renovação | Veja [Ciclo de vida da assinatura](/docs/guias/conceitos/ciclo-de-vida-da-assinatura). | A criação da cobrança da diferença **não** envia [`TRANSACTION_CREATED`](/docs/webhooks/eventos/transaction-created). ## Quando algo dá errado Além dos [erros comuns a todas as rotas](/docs/guias/fundamentos/erros#erros-comuns), este fluxo pode responder: | Passo | Situação | Resposta | Como resolver | | ----- | ----------------------------------------------------------------------- | ------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Todos | A assinatura não existe na sua conta | `404 not_found` com `Assinatura não encontrada` | Confira o `id` da assinatura. | | 2 e 3 | `new_product_price_id` ausente ou fora do formato uuid | `400 invalid_request` | Envie o `id` de uma oferta de `options`. | | 2 e 3 | A oferta não existe ou é oculta | `404 not_found` com `Plano não encontrado.` | Escolha uma oferta de `options`. | | 2 | A oferta é de outro produto | `400 invalid_request` com `O plano selecionado não pertence a este produto.` | Escolha uma oferta de `options`. | | 3 | A oferta é de outro produto | `404 not_found` com `Plano não encontrado.` | Escolha uma oferta de `options`. | | 2 | A oferta está inativa | `400 invalid_request` com `O plano selecionado não está ativo.` | Ative a oferta ou escolha outra. | | 2 e 3 | A oferta não está marcada como selecionável | `403 forbidden` com `Este plano não está disponível para troca pelo cliente.` | Ligue **Plano selecionável pelo cliente?** na oferta, no painel. | | 2 e 3 | A nova oferta tem o mesmo preço da atual | `400 invalid_request` com `O novo plano possui o mesmo valor do plano atual.` | Escolha uma oferta com preço diferente. | | 2 e 3 | Upgrade num meio de pagamento que não pode ser cobrado nesta assinatura | `400 invalid_request` com `Método de pagamento não disponível para esta assinatura.` | Confira os meios de pagamento e as taxas da conta com o suporte. | | 3 | O produto não permite troca | `403 forbidden` com `Este produto não permite troca de plano pelo cliente.` | Ligue **Cliente pode trocar de plano?** no produto, no painel. | | 3 | `payment_choice: "new_card"` sem `card`, ou `card` incompleto | `400 invalid_request` | Envie todos os campos de `card`. | | 3 | Upgrade com `current` numa assinatura no cartão sem cartão salvo | `400 invalid_request` com `Cartão da assinatura não encontrado.` | Repita com `payment_choice: "new_card"` e uma nova `Idempotency-Key`. | | 3 | Cartão recusado no upgrade | `400 invalid_request` com a mensagem do gateway ou `Cartão recusado.` | Peça outro cartão ao cliente. Veja o aviso abaixo. | | 3 | O gateway não criou o PIX ou o boleto do upgrade | `400 invalid_request` com a mensagem do gateway ou `Erro ao criar pedido no gateway` | Espere alguns minutos e tente de novo. | | 3 | Erro inesperado | `500 internal_error` | Antes de repetir, consulte a assinatura e, se houver, a venda da diferença. Veja [O erro não garante que nada foi criado](/docs/guias/fundamentos/idempotencia#erro-nao-garante). | > **Upgrade recusado deixa uma venda FAILED** > > Quando a cobrança do upgrade falha com `400`, a venda da diferença pode ficar registrada com status `FAILED`. A assinatura continua no plano atual. A `Idempotency-Key` é liberada: repetir com a mesma chave tenta cobrar de novo. ## Próximos passos - [Trocar o cartão da assinatura](/docs/guias/jornadas/trocar-cartao-da-assinatura) — Troque o cartão sem mudar o plano. - [Ciclo de vida da assinatura](/docs/guias/conceitos/ciclo-de-vida-da-assinatura) — Entenda os status e as renovações. - [Idempotência](/docs/guias/fundamentos/idempotencia) — Repita a troca sem cobrar duas vezes. --- # Vender com afiliado URL: https://staging.pagpolar.com/docs/guias/jornadas/vender-com-afiliado > Credite a venda ao afiliado certo enviando affiliate_identifier na cobrança, e saiba quando o código é recusado ou ignorado. Um **afiliado** divulga o seu produto e recebe comissão pelas vendas que traz. No checkout da PagPolar, o link do afiliado cuida disso sozinho. Quando a venda é feita pela API, é você que informa o afiliado. Use este guia quando o seu sistema sabe qual afiliado trouxe o cliente. A venda em si segue o guia do meio de pagamento. Aqui você só acrescenta um campo: `affiliate_identifier`. ## Visão geral O diagrama mostra como a API decide se a venda fica com o afiliado. ```mermaid flowchart TD A[Cobrança com affiliate_identifier] --> B{PAO seguido de 10 dígitos?} B -->|Não| C[400: nada é criado] B -->|Sim| D{Código de um afiliado deste produto, com afiliação valendo?} D -->|Não| X[Venda criada sem afiliado] D -->|Sim| E{O afiliado pode receber a comissão?} E -->|Não| X E -->|Sim| F{A oferta está liberada para o afiliado?} F -->|Não| X F -->|Sim| G[Venda criada com a comissão do afiliado] ``` Os detalhes de cada pergunta estão em [Quando o código é ignorado](#codigo-ignorado). ## Antes de começar * Um produto com o **programa de afiliados** ligado no painel e pelo menos um afiliado aprovado. * Uma oferta desse produto. Veja [Criar produto e oferta](/docs/guias/jornadas/criar-produto-e-oferta). * Uma cobrança funcionando sem afiliado. Veja o [Início rápido](/docs/guias/inicio-rapido). * Um token de acesso. Veja [Autenticação](/docs/guias/fundamentos/autenticacao#obter-o-token); os exemplos em Node.js assumem a variável `accessToken`. ## Rotas que aceitam o código O campo `affiliate_identifier` é opcional e vale nestas quatro rotas: | Rota | O que faz | | ---------------------------------- | ----------------- | | `POST /payments/pix` | Venda por PIX. | | `POST /payments/boleto` | Venda por boleto. | | `POST /payments/credit-card` | Venda no cartão. | | `POST /plans/offer/{id}/subscribe` | Assinatura. | ## Passo a passo 1. **Consiga o código do afiliado** O código do afiliado tem o formato `PAO` seguido de 10 dígitos, como `PAO0123456789`. Cada afiliação tem um código próprio. Um afiliado de dois produtos tem dois códigos diferentes. O afiliado encontra o código no painel dele: 1. Ele abre **Afiliação → Minhas afiliações**. 2. Abre a afiliação do seu produto. 3. Na seção **Meus links de divulgação**, cada link termina com `?ref=` seguido do código. O código é o valor depois de `ref=`. > **A lista de afiliados não mostra o código** > > Na sua tela **Afiliação → Afiliados**, cada afiliado aparece com nome e e-mail, sem o código. Peça o código ao afiliado, ou leia do link de divulgação dele. Pela API não existe cookie nem regra de primeiro ou último clique. Vale o código que você enviar. Decidir qual afiliado creditar é tarefa do seu sistema. 2. **Envie ** `affiliate_identifier` na cobrança Acrescente o campo no corpo da cobrança. O exemplo usa PIX. Nas outras três rotas, o campo é o mesmo. #### cURL ```bash curl -X POST "https://api.pagpolar.com/v1/payments/pix" \ -H "Authorization: Bearer " \ -H "Idempotency-Key: 8d3f1a2b-5c6d-4e7f-9a0b-1c2d3e4f5a6b" \ -H "Content-Type: application/json" \ -d '{ "offer_identifier": "", "affiliate_identifier": "", "external_reference": "PEDIDO-2001", "customer": { "name": "Maria Silva", "email": "cliente@exemplo.com", "document": "", "phone": "" } }' ``` #### Node.js ```js import { randomUUID } from 'node:crypto'; const response = await fetch('https://api.pagpolar.com/v1/payments/pix', { method: 'POST', headers: { Authorization: `Bearer ${accessToken}`, 'Idempotency-Key': randomUUID(), 'Content-Type': 'application/json', }, body: JSON.stringify({ offer_identifier: '', affiliate_identifier: '', external_reference: 'PEDIDO-2001', customer: { name: 'Maria Silva', email: 'cliente@exemplo.com', document: '', phone: '', }, }), }); console.log(response.status, await response.json()); ``` | Campo | Obrigatório | O que é | | ---------------------- | ----------- | --------------------------------------------------------------------------------------------------------------------------- | | `affiliate_identifier` | Não | Código do afiliado: `PAO` em letras maiúsculas, seguido de exatamente 10 dígitos. Espaços no começo e no fim são removidos. | A resposta é igual à de uma venda sem afiliado, como em [Vender com PIX ou boleto](/docs/guias/jornadas/vender-com-pix-ou-boleto). Contrato completo: [`POST /payments/pix`](/docs/referencia/vendas/create-pix-payment), [`POST /payments/boleto`](/docs/referencia/vendas/create-boleto-payment), [`POST /payments/credit-card`](/docs/referencia/vendas/create-credit-card-payment) e [`POST /plans/offer/{id}/subscribe`](/docs/referencia/assinaturas/create-subscription). > **A API não avisa se o afiliado foi creditado** > > Se o código não se aplica, a venda é criada assim mesmo, sem afiliado e sem erro. A resposta da cobrança, `GET /sales/{identifier}` e os webhooks não trazem dados do afiliado. Para conferir, use o painel, como no próximo passo. 3. **Confira no painel** Abra **Afiliação → Afiliados** e a aba **Vendas**. Ela lista as vendas feitas por afiliados, com o afiliado e a comissão. Se a venda não aparece ali, o código foi ignorado. Veja os motivos na próxima seção. ## Quando o código é recusado A API confere o formato antes de tudo. Se o formato está errado, a resposta é `400` e **nada é criado**: nem venda, nem registro da `Idempotency-Key`. Corrija e envie de novo com a mesma chave. | Você envia | Resposta | | -------------------------------------------- | ----------------------------------------------------------------------------- | | `PAO0123456789` | Aceito. | | `" PAO0123456789 "` (com espaços nas pontas) | Aceito. Os espaços são removidos. | | `pao0123456789` (letras minúsculas) | `400 invalid_request` com `message` vazia | | `PAO123` (menos de 10 dígitos) | `400 invalid_request` com `message` vazia | | `null` | `400 invalid_request` com `message` vazia | | `""` (texto vazio) | `400 invalid_request` com `O campo affiliate_identifier não pode estar vazio` | Venda sem afiliado? Não envie o campo. Os outros erros da cobrança não mudam com o afiliado: veja os [erros comuns a todas as rotas](/docs/guias/fundamentos/erros#erros-comuns) e o guia do meio de pagamento. ## Quando o código é ignorado Com o formato certo, a API procura o afiliado. Em qualquer uma das situações abaixo, a venda é criada normalmente, **sem afiliado**, e a resposta continua `201`: | Situação | O que conferir | | ------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | | O código não existe. | Copie o código de novo do link de divulgação. | | O código é de uma afiliação de **outro produto**. | O código vale só para o produto da afiliação. Use o código do afiliado para o produto desta oferta. | | A afiliação não está ativa: pendente ou recusada. | Aprove o afiliado no painel. | | A afiliação foi encerrada e o prazo de carência já venceu. | Depois de banir ou remover um afiliado, o código ainda gera comissão durante a carência. Por padrão, a carência é de 3 dias. Depois dela, não gera mais. | | O programa de afiliados do produto está desligado. | Ligue o programa na aba **Afiliados** da configuração do produto. | | O código é de uma afiliação da **sua própria conta**. | Uma conta não recebe comissão das próprias vendas. | | O afiliado não tem conta de recebimento criada, ou a verificação de identidade dele foi recusada. | O afiliado precisa concluir o cadastro para receber. | | A comissão do afiliado está zerada. | Ajuste a comissão no painel. | | A oferta não está liberada para o afiliado. | Libere a oferta para o afiliado, ou libere todas as ofertas do produto. | | Falha interna ao consultar o afiliado. | A venda nunca falha por causa do afiliado. Confira no painel e fale com o suporte informando o `request_id`. | ## Oferta informada na hora e comissão Você pode cobrar sem criar a oferta antes, enviando `offer` no lugar de `offer_identifier`. Veja [Ofertas, planos e ofertas ocultas](/docs/guias/conceitos/ofertas-planos-e-ofertas-ocultas). Com afiliado, cuidado: * Se `offer` cria uma oferta **nova**, ela não está na lista de ofertas liberadas de ninguém. A comissão só vale se o afiliado puder divulgar **todas as ofertas** do produto. * Se `offer` reaproveita uma oferta que já existe e já está liberada para o afiliado, a comissão vale. > **Afiliado com lista de ofertas: crie a oferta antes** > > Crie a oferta antes com `POST /offers`, libere para o afiliado no painel e cobre com `offer_identifier`. ## Comissão na assinatura Em `POST /plans/offer/{id}/subscribe`, o afiliado recebe a comissão da primeira cobrança. Nas renovações, ele só continua recebendo se a afiliação estiver com **todas as recorrências** ligada. Sem essa opção, depois do primeiro ciclo a parte dele volta para você. ## Eventos de webhook deste fluxo Não existe evento próprio de afiliado. Os eventos da venda chegam como numa venda sem afiliado, como [`TRANSACTION_CREATED`](/docs/webhooks/eventos/transaction-created) e [`TRANSACTION_PAID`](/docs/webhooks/eventos/transaction-paid). Na venda criada pela API, `source.channel` é `API`, com ou sem afiliado. O valor `AWARD` é outra coisa: uma venda gerada como prêmio para o afiliado. Veja [Canal da venda](/docs/webhooks/formato-do-evento#source). ## Próximos passos - [Vender com PIX ou boleto](/docs/guias/jornadas/vender-com-pix-ou-boleto) — O fluxo completo da venda por PIX ou boleto. - [Vender com cartão de crédito](/docs/guias/jornadas/vender-com-cartao) — O fluxo completo da venda no cartão. - [Formato do evento](/docs/webhooks/formato-do-evento) — Leia o payload dos webhooks da venda. --- # Vender com cartão de crédito URL: https://staging.pagpolar.com/docs/guias/jornadas/vender-com-cartao > Cobre o cliente no cartão, à vista ou parcelado, e descubra se o pagamento foi aprovado ou recusado. Use este guia para cobrar **uma vez** no cartão de crédito, à vista ou parcelado. Você envia os dados do cartão, a PagPolar cobra no gateway e avisa o resultado pelo webhook. Para cobrar por PIX ou boleto, veja [Vender com PIX ou boleto](/docs/guias/jornadas/vender-com-pix-ou-boleto). Com a chave de Homologação, a cobrança roda no [ambiente de testes](/docs/guias/fundamentos/ambientes#ambiente-de-testes); com a chave de Produção, o cartão é cobrado de verdade. ## Visão geral O diagrama mostra o caminho de uma venda no cartão. Os números batem com os passos abaixo. ```mermaid sequenceDiagram autonumber participant S as Seu servidor participant A as API PagPolar participant G as Gateway participant W as Seu servidor de webhook S->>A: POST /payments/credit-card com Idempotency-Key alt parcelas acima do máximo da oferta A-->>S: 400 invalid_request else dados aceitos A->>G: cria a cobrança no cartão G-->>A: aceita para processar ou recusa A-->>S: 201 com transactions A-)W: TRANSACTION_CREATED com status PROCESSING ou FAILED opt gateway confirma o pagamento G-)A: pagamento confirmado A-)W: TRANSACTION_PAID end end S->>A: GET /sales/{id} A-->>S: 200 com status ``` ## Antes de começar * Um token de acesso. Veja [Autenticação](/docs/guias/fundamentos/autenticacao#obter-o-token); os exemplos em Node.js assumem a variável `accessToken`. * Uma oferta ativa com cartão ligado. Veja [Criar produto e oferta](/docs/guias/jornadas/criar-produto-e-oferta). * A URL do webhook cadastrada na credencial. Veja [Credenciais da API](/docs/guias/fundamentos/credenciais). * Um servidor seu para [chamar a API](/docs/guias/fundamentos/ambientes#servidor). Os dados do cartão vão no corpo da requisição: não grave o número do cartão nem o CVV nos seus logs. 1. **Confira as parcelas da oferta** Envie o **código da oferta** no campo `offer_identifier`. Na hora da cobrança, a API confere: | Regra | Se não for cumprida | | --------------------------------------------------------------------- | ----------------------------------------------------------------------------- | | A oferta existe na sua conta e não é oculta. | `404` com `Oferta não encontrada` | | A oferta está ativa e não passou da data de expiração. | `409` com `Oferta inativa` ou `Oferta expirada` | | O cartão está ligado na oferta (`is_enabled_credit_card`). | `409` com `Método de pagamento CREDIT_CARD não habilitado para esta oferta` | | `installments` não passa de `max_credit_card_installments` da oferta. | `400` com `Número de parcelas acima do permitido para esta oferta (máximo N)` | Também dá para informar a oferta na hora, com o campo `offer` no lugar de `offer_identifier`. Veja [Ofertas, planos e ofertas ocultas](/docs/guias/conceitos/ofertas-planos-e-ofertas-ocultas). 2. **Crie a cobrança** Envie uma `Idempotency-Key` nova para esta tentativa de cobrança e **grave no seu pedido antes de enviar**. Veja [Idempotência](/docs/guias/fundamentos/idempotencia). #### cURL ```bash curl -X POST "https://api.pagpolar.com/v1/payments/credit-card" \ -H "Authorization: Bearer " \ -H "Idempotency-Key: 9e1b3d5f-7a2c-4e6b-8d0f-1a3c5e7b9d2f" \ -H "Content-Type: application/json" \ -d '{ "offer_identifier": "", "external_reference": "PEDIDO-0004", "installments": 3, "customer": { "name": "Maria Silva", "email": "cliente@exemplo.com", "document": "", "phone": "" }, "credit_card": { "holder_name": "MARIA SILVA", "holder_document": "", "number": "", "expiration_month": 12, "expiration_year": 2030, "cvv": "" }, "buyer_ip": "" }' ``` #### Node.js ```js import { randomUUID } from 'node:crypto'; const idempotencyKey = randomUUID(); const response = await fetch('https://api.pagpolar.com/v1/payments/credit-card', { method: 'POST', headers: { Authorization: `Bearer ${accessToken}`, 'Idempotency-Key': idempotencyKey, 'Content-Type': 'application/json', }, body: JSON.stringify({ offer_identifier: '', external_reference: 'PEDIDO-0004', installments: 3, customer: { name: 'Maria Silva', email: 'cliente@exemplo.com', document: '', phone: '', }, credit_card: { holder_name: 'MARIA SILVA', holder_document: '', number: '', expiration_month: 12, expiration_year: 2030, cvv: '', }, buyer_ip: '', }), }); console.log(response.status, await response.json()); ``` Campos do corpo: | Campo | Obrigatório | O que é | | -------------------- | --------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `offer_identifier` | Sim, ou `offer` | Código da oferta. | | `installments` | Sim | Número de parcelas, de `1` a `12`. Não pode passar do máximo da oferta. | | `quantity` | Não | Quantidade de unidades. Padrão `1`. Mais de `1` só se a oferta permitir. | | `external_reference` | Não | Código do **seu** pedido, até 255 caracteres, como `PEDIDO-0004`. Não use o formato do código da venda. Veja [Como a venda é encontrada](/docs/guias/fundamentos/valores-datas-e-identificadores#como-a-venda-e-encontrada). | 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](/docs/guias/fundamentos/valores-datas-e-identificadores#documento-e-telefone). | | `customer.phone` | Sim | Telefone do cliente com DDD, com ou sem pontuação. Veja o formato em [Documento e telefone](/docs/guias/fundamentos/valores-datas-e-identificadores#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](/docs/guias/fundamentos/valores-datas-e-identificadores#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](/docs/guias/fundamentos/ambientes#dados-de-teste). 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](/docs/guias/jornadas/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): ```json { "data": { "offer_identifier": "PPP1234567890", "transactions": ["c3d4e5f6-a7b8-4c9d-0e1f-2a3b4c5d6e7f"] } } ``` **Guarde `transactions[0]` junto do seu pedido.** Ele é o `id` da venda. Contrato completo: [`POST /payments/credit-card`](/docs/referencia/vendas/create-credit-card-payment). > **O 201 não diz se o cartão foi aprovado** > > A resposta `201` quer dizer que a venda foi registrada. Ela não traz o status. Um cartão recusado também responde `201`. Descubra o resultado pelo webhook (passo 3) ou pela consulta (passo 4). > **Para tentar outro cartão, use outra Idempotency-Key** > > A resposta `201` do cartão recusado fica guardada com a chave. Repetir com a **mesma** chave devolve a mesma resposta e não cobra de novo. Para uma nova tentativa, com o mesmo cartão ou com outro, gere uma chave nova. 3. **Descubra o resultado pelo webhook** A PagPolar envia um `POST` para a URL do webhook da sua credencial. Autentique a requisição e descarte repetidos: veja [Autenticar requisições](/docs/webhooks/autenticar-requisicoes) e [Processar sem duplicar](/docs/webhooks/processar-sem-duplicar). | Evento | `data.transaction.status` | O que fazer | | ------------------------------------------------------------------- | ------------------------- | ---------------------------------------------------------------------------------------------------------- | | [`TRANSACTION_CREATED`](/docs/webhooks/eventos/transaction-created) | `PROCESSING` | O gateway aceitou o cartão para processar. Registre a venda e espere `TRANSACTION_PAID`. Não libere ainda. | | [`TRANSACTION_CREATED`](/docs/webhooks/eventos/transaction-created) | `FAILED` | O gateway recusou o cartão. Não libere. Peça outro cartão ao cliente. | | [`TRANSACTION_PAID`](/docs/webhooks/eventos/transaction-paid) | `PAID` | O pagamento foi confirmado. Libere o que foi vendido. | Decida sempre pelo `status` recebido, que é o [estado da venda no momento do envio](/docs/webhooks/formato-do-evento#estado-no-envio). Exemplo de `TRANSACTION_PAID` de uma venda parcelada (resumido): ```json { "id": "550e8400-e29b-41d4-a716-446655440000", "event": "TRANSACTION_PAID", "creation_date": "2026-09-15T14:35:10.000Z", "version": "1.0.0", "data": { "transaction": { "id": "c3d4e5f6-a7b8-4c9d-0e1f-2a3b4c5d6e7f", "identifier": "PPO9876543211", "status": "PAID", "payment_method": "CREDIT_CARD", "total_amount": "150.0000", "net_amount": 150, "installment_tax": "0.0000", "installments": 3, "paid_at": "2026-09-15T14:35:00.000Z" }, "payment_details": { "last_credit_card_digits": "4242" }, "source": { "channel": "API", "api_credential_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7" } } } ``` Use `data.transaction.id`, o mesmo valor de `transactions[0]`, para achar o seu pedido: o webhook **não** traz a `external_reference`. Veja todos os campos em [Formato do evento](/docs/webhooks/formato-do-evento). 4. **Consulte a venda** Use a consulta quando o webhook não chegou ou quando a venda ficou muito tempo em `PROCESSING`. Envie o `id` que veio em `transactions[0]`: #### cURL ```bash curl "https://api.pagpolar.com/v1/sales/c3d4e5f6-a7b8-4c9d-0e1f-2a3b4c5d6e7f" \ -H "Authorization: Bearer " ``` #### Node.js ```js const response = await fetch( 'https://api.pagpolar.com/v1/sales/c3d4e5f6-a7b8-4c9d-0e1f-2a3b4c5d6e7f', { headers: { Authorization: `Bearer ${accessToken}` } }, ); console.log(response.status, await response.json()); ``` Resposta `200` (resumida): ```json { "data": { "id": "c3d4e5f6-a7b8-4c9d-0e1f-2a3b4c5d6e7f", "identifier": "PPO9876543211", "external_reference": "PEDIDO-0004", "status": "PAID", "payment_method": "CREDIT_CARD", "installments": 3, "total_amount": 150, "paid_at": "2026-09-15T14:35:00.000Z", "payment_details": { "last_credit_card_digits": "4242" } } } ``` `total_amount` vem em reais: `150` na API e `"150.0000"`, como texto, no webhook. Veja [Valores nas respostas](/docs/guias/fundamentos/valores-datas-e-identificadores#valores-nas-respostas). Você também pode consultar pelo código da venda (`GET /sales/PPO9876543211`) ou pela sua referência (`GET /sales/PEDIDO-0004`). `GET /payments/{identifier}` faz a mesma consulta. Veja [Como a venda é encontrada](/docs/guias/fundamentos/valores-datas-e-identificadores#como-a-venda-e-encontrada). Contrato completo: [`GET /sales/{identifier}`](/docs/referencia/vendas/get-sale). ## Ciclo de vida O diagrama mostra os status de uma venda no cartão neste fluxo. ```mermaid stateDiagram-v2 [*] --> PROCESSING: gateway aceita o cartão para processar [*] --> FAILED: gateway recusa na criação PROCESSING --> PAID: gateway confirma o pagamento PROCESSING --> FAILED: gateway recusa depois ``` | Status | O que significa | O que você faz | | ------------ | ------------------------------------------------------ | ----------------------------------------------------------------------------------- | | `PROCESSING` | O gateway recebeu a cobrança e ainda não confirmou. | Espere `TRANSACTION_PAID`. Não libere. | | `PAID` | O pagamento foi confirmado. `paid_at` fica preenchido. | Libere o que foi vendido. | | `FAILED` | O gateway recusou a cobrança. | Não libere. Peça outro cartão e crie uma nova cobrança com outra `Idempotency-Key`. | Reembolso, cancelamento e chargeback estão em [Ciclo de vida da venda](/docs/guias/conceitos/ciclo-de-vida-da-venda). ## Cartão recusado Não existe evento próprio para recusa. Se o gateway recusa na hora da cobrança, chega `TRANSACTION_CREATED` com `status: FAILED` (passo 3). Se recusa depois da resposta, a venda passa de `PROCESSING` para `FAILED` e **nenhum** evento é enviado: se a venda continuar em `PROCESSING` sem `TRANSACTION_PAID`, consulte `GET /sales/{identifier}` de tempos em tempos (passo 4). A API não informa o motivo da recusa. Peça ao cliente para conferir os dados ou usar outro cartão. ## Quando algo dá errado Além dos [erros comuns a todas as rotas](/docs/guias/fundamentos/erros#erros-comuns), este fluxo pode responder: | Passo | Situação | Resposta | Como resolver | | ----- | ------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | 2 | `installments` ausente. | `400` com `O campo installments é obrigatório` | Envie o número de parcelas. | | 2 | `credit_card` ausente. | `400` com `O campo credit_card é obrigatório` | Envie os dados do cartão. | | 2 | `credit_card.holder_document` fora do formato. | `400` com `Documento do titular do cartão inválido: envie o CPF com 11 dígitos ou o CNPJ com 14 dígitos, com ou sem pontuação.` | Veja [Documento e telefone](/docs/guias/fundamentos/valores-datas-e-identificadores#documento-e-telefone). | | 2 | `installments` fora de `1` a `12`, número do cartão inválido ou validade fora do intervalo (mês de `1` a `12`, ano de `2000` a `2100`). | `400 invalid_request`, em geral com `message` vazia | Confira os campos na tabela do passo 2. | | 2 | `offer_identifier` e `offer` juntos, ou nenhum dos dois. | `400` com `Envie offer_identifier ou offer, nunca os dois` ou `Envie offer_identifier ou offer` | Envie só um. | | 2 | A oferta não passa numa das regras conferidas na cobrança: não encontrada, inativa, expirada, cartão desligado ou parcelas acima do máximo. | `404`, `409` ou `400`, conforme a tabela do passo 1 | Veja [Regras conferidas em toda cobrança](/docs/guias/conceitos/ofertas-planos-e-ofertas-ocultas#regras-da-cobranca). Com `price` abaixo do [valor mínimo](/docs/guias/jornadas/criar-produto-e-oferta#meios-de-pagamento), o cartão fica desligado mesmo com `true`. | | 2 | `quantity` acima do permitido pela oferta. | `400` com `Quantidade acima do limite permitido para esta oferta (máximo N)` ou `Esta oferta não permite compra de múltiplas unidades` | Reduza a quantidade. | | 2 | O gateway respondeu com erro ao criar a cobrança. | O status e a mensagem do gateway, ou `500 internal_error` | Procure a venda pela sua referência antes de repetir. Veja [Idempotência](/docs/guias/fundamentos/idempotencia#erro-nao-garante). | | 3 | O cartão foi recusado. | `201`, e depois `TRANSACTION_CREATED` com `FAILED` | Veja [Cartão recusado](#cartao-recusado). | | 3 | O webhook não chegou. | — | Veja [Entregas e retentativas](/docs/webhooks/entregas-e-retentativas). Enquanto isso, consulte a venda. | | 4 | Nenhuma venda com o valor enviado. | `404` com `Venda não encontrada` | Confira o `id`, o código ou a `external_reference`. | ## Confira no painel Ao abrir uma venda em **Vendas → Minhas vendas**, a tela **Detalhes da venda** mostra o status, o valor, o cliente e o cartão usado: Quando o cartão é recusado, a mesma tela mostra o status **Falha** e o bloco **Motivo do erro**: ## Próximos passos - [Vender com PIX ou boleto](/docs/guias/jornadas/vender-com-pix-ou-boleto) — Cobre por PIX ou boleto e confirme pelo webhook. - [Processar eventos sem duplicar](/docs/webhooks/processar-sem-duplicar) — Trate repetições e eventos fora de ordem. - [Ciclo de vida da venda](/docs/guias/conceitos/ciclo-de-vida-da-venda) — Todos os status da venda, inclusive reembolso e chargeback. --- # Vender com PIX ou boleto URL: https://staging.pagpolar.com/docs/guias/jornadas/vender-com-pix-ou-boleto > Cobre o cliente por PIX ou boleto, mostre o código de pagamento e confirme o pagamento pelo webhook. Use este guia para cobrar **uma vez** por PIX ou por boleto. O cliente recebe um código, paga no banco dele e a PagPolar avisa você quando o pagamento for confirmado. Para cobrar no cartão, veja [Vender com cartão de crédito](/docs/guias/jornadas/vender-com-cartao). Com a chave de Homologação, a cobrança roda no [ambiente de testes](/docs/guias/fundamentos/ambientes#ambiente-de-testes); com a chave de Produção, gera uma cobrança real. ## Visão geral O diagrama mostra o caminho completo de uma venda por PIX ou boleto. Os números batem com os passos abaixo. ```mermaid sequenceDiagram autonumber participant S as Seu servidor participant A as API PagPolar participant C as Cliente participant W as Seu servidor de webhook S->>A: POST /payments/pix ou /payments/boleto com Idempotency-Key A-->>S: 201 com transactions e o PIX ou o boleto A-)W: TRANSACTION_CREATED S->>C: mostra o código do PIX ou o link do boleto alt cliente paga C->>A: paga no banco e o gateway confirma A-)W: TRANSACTION_PAID else prazo vence sem pagamento A-)W: TRANSACTION_EXPIRED end S->>A: GET /sales/{id} A-->>S: 200 com status e paid_at ``` ## Antes de começar * Uma credencial com a chave de API e a URL do webhook cadastrada. Veja [Credenciais da API](/docs/guias/fundamentos/credenciais). * Um token de acesso. Veja [Autenticação](/docs/guias/fundamentos/autenticacao#obter-o-token); os exemplos em Node.js assumem a variável `accessToken`. * Uma oferta ativa com PIX ou boleto ligado. Veja [Criar produto e oferta](/docs/guias/jornadas/criar-produto-e-oferta). * Um servidor seu para [chamar a API](/docs/guias/fundamentos/ambientes#servidor). 1. **Separe o código da oferta** A cobrança precisa de uma oferta. Envie o **código da oferta** no campo `offer_identifier`. É o `identifier` que voltou quando você criou a oferta. Na hora da cobrança, a API confere a oferta: | Regra | Se não for cumprida | | -------------------------------------------- | --------------------------------------------------------------------------------- | | A oferta existe na sua conta e não é oculta. | `404` com `Oferta não encontrada` | | A oferta está ativa. | `409` com `Oferta inativa` | | A oferta não passou da data de expiração. | `409` com `Oferta expirada` | | O meio de pagamento está ligado na oferta. | `409` com `Método de pagamento PIX não habilitado para esta oferta` (ou `BOLETO`) | Também dá para informar a oferta na hora, com o campo `offer` no lugar de `offer_identifier`. Nunca envie os dois. Veja [Ofertas, planos e ofertas ocultas](/docs/guias/conceitos/ofertas-planos-e-ofertas-ocultas). 2. **Crie a cobrança** Envie uma `Idempotency-Key` única para esta cobrança e **grave no seu pedido antes de enviar**. Veja [Idempotência](/docs/guias/fundamentos/idempotencia). O exemplo cria um PIX: #### cURL ```bash curl -X POST "https://api.pagpolar.com/v1/payments/pix" \ -H "Authorization: Bearer " \ -H "Idempotency-Key: 3c8e1f7a-2b4d-4e6f-9a1c-5d7e9f0a1b2c" \ -H "Content-Type: application/json" \ -d '{ "offer_identifier": "", "external_reference": "PEDIDO-0002", "customer": { "name": "Maria Silva", "email": "cliente@exemplo.com", "document": "", "phone": "" } }' ``` #### Node.js ```js import { randomUUID } from 'node:crypto'; const idempotencyKey = randomUUID(); const response = await fetch('https://api.pagpolar.com/v1/payments/pix', { method: 'POST', headers: { Authorization: `Bearer ${accessToken}`, 'Idempotency-Key': idempotencyKey, 'Content-Type': 'application/json', }, body: JSON.stringify({ offer_identifier: '', external_reference: 'PEDIDO-0002', customer: { name: 'Maria Silva', email: 'cliente@exemplo.com', document: '', phone: '', }, }), }); console.log(response.status, await response.json()); ``` Para o **boleto**, o corpo é o mesmo. Só a rota muda: `POST /payments/boleto`. Campos do corpo: | Campo | Obrigatório | O que é | | -------------------- | --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `offer_identifier` | Sim, ou `offer` | Código da oferta. | | `quantity` | Não | Quantidade de unidades. Padrão `1`. Mais de `1` só se a oferta permitir. | | `external_reference` | Não | Código do **seu** pedido, até 255 caracteres, como `PEDIDO-0002`. Serve para achar a venda depois. Não use o formato do código da venda. Veja [Como a venda é encontrada](/docs/guias/fundamentos/valores-datas-e-identificadores#como-a-venda-e-encontrada). | 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](/docs/guias/fundamentos/valores-datas-e-identificadores#documento-e-telefone). | | `customer.phone` | Sim | Telefone do cliente com DDD, com ou sem pontuação. Veja o formato em [Documento e telefone](/docs/guias/fundamentos/valores-datas-e-identificadores#documento-e-telefone). | 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](/docs/guias/jornadas/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. | Não envie `installments`. No PIX e no boleto, o único valor aceito é `1`. Resposta `201` do PIX: ```json { "data": { "offer_identifier": "PPP1234567890", "transactions": ["a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d"], "pix": { "qr_code": "00020126580014br.gov.bcb.pix0136a1b2c3d4-e5f6-4a7b-8c9d" } } } ``` Resposta `201` do boleto: ```json { "data": { "offer_identifier": "PPP1234567890", "transactions": ["b2c3d4e5-f6a7-4b8c-9d0e-1f2a3b4c5d6e"], "boleto": { "barcode": "34191.79001 01043.510047 91020.150008 1 96610000015000", "pdf_link": "https://boletos.pagpolar.com/a1b2c3d4.pdf" } } } ``` As duas respostas estão resumidas. Contrato completo: [`POST /payments/pix`](/docs/referencia/vendas/create-pix-payment) e [`POST /payments/boleto`](/docs/referencia/vendas/create-boleto-payment). **Guarde `transactions[0]` junto do seu pedido.** Ele é o `id` da venda. Você vai usar esse valor para ligar o webhook ao pedido e para consultar a venda. 3. **Mostre o PIX ou o boleto ao cliente** **PIX.** Mostre `pix.qr_code` como código "copia e cola". Se quiser mostrar a imagem do QR Code, gere a imagem a partir desse texto. * O PIX é criado com validade de **5 horas**. * A data e a hora exatas do vencimento aparecem em `payment_details.qr_code_expires_at` quando você consulta a venda (passo 5). **Boleto.** Mostre o link `boleto.pdf_link` para o cliente abrir e pagar. * O boleto é criado com vencimento em **5 dias**. * `boleto.barcode` traz o código do boleto como o gateway devolveu. > **O código do boleto na consulta pode ser outro campo** > > Na consulta da venda, `payment_details.billet_barcode` vem de outro campo do gateway. O formato pode ser diferente do `boleto.barcode` da resposta de criação. Guarde os dois se for mostrar o código ao cliente mais tarde. 4. **Espere o webhook** A PagPolar envia um `POST` para a URL do webhook da sua credencial a cada mudança importante. Autentique a requisição e descarte repetidos: veja [Autenticar requisições](/docs/webhooks/autenticar-requisicoes) e [Processar sem duplicar](/docs/webhooks/processar-sem-duplicar). | Evento | Quando chega | O que fazer | | ------------------------------------------------------------------- | --------------------------------------- | ----------------------------------------------------------------------------------------------- | | [`TRANSACTION_CREATED`](/docs/webhooks/eventos/transaction-created) | Logo depois da resposta `201`. | Registre a venda. **Não** libere o produto. | | [`TRANSACTION_PAID`](/docs/webhooks/eventos/transaction-paid) | O gateway confirmou o pagamento. | Libere o que foi vendido. | | [`TRANSACTION_EXPIRED`](/docs/webhooks/eventos/transaction-expired) | O PIX ou o boleto venceu sem pagamento. | Não libere. Se o cliente ainda quiser comprar, crie outra cobrança com outra `Idempotency-Key`. | Exemplo de `TRANSACTION_PAID` de um PIX (resumido): ```json { "id": "550e8400-e29b-41d4-a716-446655440000", "event": "TRANSACTION_PAID", "creation_date": "2026-09-15T14:35:10.000Z", "version": "1.0.0", "data": { "transaction": { "id": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d", "identifier": "PPO9876543210", "status": "PAID", "payment_method": "PIX", "total_amount": "10.0000", "net_amount": 10, "paid_at": "2026-09-15T14:35:00.000Z" }, "source": { "channel": "API", "api_credential_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7" } } } ``` Use `data.transaction.id`, o mesmo valor de `transactions[0]`, para achar o seu pedido: o webhook **não** traz a `external_reference`. Decida pelo `data.transaction.status`, que é o [estado da venda no momento do envio](/docs/webhooks/formato-do-evento#estado-no-envio). Veja todos os campos em [Formato do evento](/docs/webhooks/formato-do-evento). 5. **Consulte a venda** Use a consulta quando precisar do status atual: o webhook atrasou, o seu servidor ficou fora do ar ou você quer conferir antes de liberar. Envie o `id` que veio em `transactions[0]`: #### cURL ```bash curl "https://api.pagpolar.com/v1/sales/a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d" \ -H "Authorization: Bearer " ``` #### Node.js ```js const response = await fetch( 'https://api.pagpolar.com/v1/sales/a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d', { headers: { Authorization: `Bearer ${accessToken}` } }, ); console.log(response.status, await response.json()); ``` Resposta `200` (resumida): ```json { "data": { "id": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d", "identifier": "PPO9876543210", "external_reference": "PEDIDO-0002", "status": "PAID", "payment_method": "PIX", "total_amount": 10, "paid_at": "2026-09-15T14:35:00.000Z", "payment_details": { "qr_code": "00020126580014br.gov.bcb.pix0136a1b2c3d4-e5f6-4a7b-8c9d", "qr_code_expires_at": "2026-09-15T19:30:00.000Z", "billet_barcode": null, "billet_link": null, "last_credit_card_digits": null } } } ``` `total_amount` vem em reais: `10` na API e `"10.0000"`, como texto, no webhook. Veja [Valores nas respostas](/docs/guias/fundamentos/valores-datas-e-identificadores#valores-nas-respostas). Você também pode consultar pelo código da venda (`GET /sales/PPO9876543210`) ou pela sua referência (`GET /sales/PEDIDO-0002`). `GET /payments/{identifier}` faz a mesma consulta. Veja [Como a venda é encontrada](/docs/guias/fundamentos/valores-datas-e-identificadores#como-a-venda-e-encontrada). Contrato completo: [`GET /sales/{identifier}`](/docs/referencia/vendas/get-sale). ## Ciclo de vida O diagrama mostra os status que uma venda por PIX ou boleto percorre neste fluxo. ```mermaid stateDiagram-v2 [*] --> PROCESSING: POST cria a cobrança PROCESSING --> PAID: gateway confirma o pagamento PROCESSING --> EXPIRED: PIX ou boleto venceu EXPIRED --> PAID: pagamento confirmado depois do vencimento ``` | Status | O que significa | O que você faz | | ------------ | --------------------------------------------------------------------------------------------------- | --------------------------------------------------- | | `PROCESSING` | O PIX ou o boleto foi gerado e espera o pagamento. É o status que você vê depois da resposta `201`. | Mostre o código ao cliente e espere. | | `PAID` | O pagamento foi confirmado. `paid_at` fica preenchido. | Libere o que foi vendido. | | `EXPIRED` | O prazo passou sem pagamento. | Não libere. Crie outra cobrança se o cliente pedir. | **O vencimento não é avisado na hora.** A PagPolar confere os vencimentos a cada 3 horas. O PIX vence quando passa do `qr_code_expires_at`. O boleto vence 5 dias depois de criado. Por isso `EXPIRED` e o evento `TRANSACTION_EXPIRED` podem chegar algumas horas depois do prazo. **`EXPIRED` não é definitivo.** Se o gateway confirmar um pagamento depois do vencimento, a venda passa para `PAID`. Se chegar `TRANSACTION_PAID` depois de `TRANSACTION_EXPIRED`, libere o produto. Reembolso, cancelamento e chargeback estão em [Ciclo de vida da venda](/docs/guias/conceitos/ciclo-de-vida-da-venda). ## Quando algo dá errado Além dos [erros comuns a todas as rotas](/docs/guias/fundamentos/erros#erros-comuns), este fluxo pode responder: | Passo | Situação | Resposta | Como resolver | | ----- | ------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | 2 | `offer_identifier` e `offer` juntos, ou nenhum dos dois. | `400` com `Envie offer_identifier ou offer, nunca os dois` ou `Envie offer_identifier ou offer` | Envie só um. | | 2 | Campo obrigatório do cliente faltando. | `400` com `O campo customer.email é obrigatório` (muda conforme o campo) | Complete o `customer`. A mensagem mostra um campo por vez. | | 2 | A oferta não passa numa das regras conferidas na cobrança: não encontrada, inativa, expirada, ou PIX ou boleto desligado. | `404` ou `409`, conforme a tabela do passo 1 | Veja [Regras conferidas em toda cobrança](/docs/guias/conceitos/ofertas-planos-e-ofertas-ocultas#regras-da-cobranca). Abaixo do [valor mínimo](/docs/guias/jornadas/criar-produto-e-oferta#meios-de-pagamento), o meio fica desligado mesmo com `true`. | | 2 | `quantity` maior que o limite da oferta. | `400` com `Quantidade acima do limite permitido para esta oferta (máximo N)` | Reduza a quantidade. | | 2 | `quantity` maior que `1` numa oferta que não aceita. | `400` com `Esta oferta não permite compra de múltiplas unidades` | Envie `quantity: 1` ou não envie o campo. | | 2 | `installments` diferente de `1`. | `400` com `Para o campo installments os valores permitidos são [1]` | Não envie o campo. | | 2 | O gateway não conseguiu gerar o PIX ou o boleto. | `400` com a mensagem do gateway, ou `Erro ao criar pedido no gateway` | Veja o aviso abaixo antes de repetir. | | 4 | O webhook não chegou. | — | Confira a URL e os eventos da credencial. Veja [Entregas e retentativas](/docs/webhooks/entregas-e-retentativas). Enquanto isso, consulte a venda. | | 5 | Nenhuma venda com o valor enviado. | `404` com `Venda não encontrada` | Confira o `id`, o código ou a `external_reference`. | | 5 | A `external_reference` tem o formato do código da venda e bate com o código de outra venda. | `200` com a venda errada | Consulte pelo `id`, ou por `GET /sales?external_reference=`, que só procura pela referência. Nas próximas cobranças, não use o formato do código na referência. Veja [Como a venda é encontrada](/docs/guias/fundamentos/valores-datas-e-identificadores#como-a-venda-e-encontrada). | > **Erro na geração não garante que nada foi criado** > > Quando o gateway não gera o PIX ou o boleto, a resposta é um erro, mas a venda pode ficar registrada sem código de pagamento. Nesse caso, `TRANSACTION_CREATED` não é enviado. Mais tarde a venda vence e chega `TRANSACTION_EXPIRED`. > > Antes de repetir, procure pela sua referência: `GET /sales/PEDIDO-0002`. Se a venda existir sem código de pagamento, crie uma nova cobrança com outra `external_reference` e outra `Idempotency-Key`. Veja [Idempotência](/docs/guias/fundamentos/idempotencia). ## Confira no painel A venda aparece em **Vendas → Minhas vendas**, com código, cliente, produto, valor recebido e status: Ao abrir a venda, a tela **Detalhes da venda** mostra o status, os valores, o cliente e a forma de pagamento: ## Próximos passos - [Vender com cartão de crédito](/docs/guias/jornadas/vender-com-cartao) — Cobre no cartão, à vista ou parcelado. - [Processar eventos sem duplicar](/docs/webhooks/processar-sem-duplicar) — Trate repetições e eventos fora de ordem. - [Ciclo de vida da venda](/docs/guias/conceitos/ciclo-de-vida-da-venda) — Todos os status da venda, inclusive reembolso e chargeback. --- # Vender um produto físico URL: https://staging.pagpolar.com/docs/guias/jornadas/vender-um-produto-fisico > Consulte o frete pelo CEP do cliente, cobre com o endereço de entrega e a opção de frete escolhida e acompanhe a venda até a separação. Use este guia quando a oferta é de um **produto físico**. A cobrança é feita pelas mesmas rotas de sempre, com dois campos a mais: `address`, com o endereço de entrega, e `shipping_option_id`, com a opção de frete escolhida. Antes da cobrança entra um passo novo: você consulta o frete para o CEP do cliente e escolhe uma das opções devolvidas. Os detalhes de cada meio de pagamento ficam em [Vender com PIX ou boleto](/docs/guias/jornadas/vender-com-pix-ou-boleto) e [Vender com cartão de crédito](/docs/guias/jornadas/vender-com-cartao). Aqui está só o que muda por causa da entrega. Com a chave de Homologação, a cobrança roda no [ambiente de testes](/docs/guias/fundamentos/ambientes#ambiente-de-testes); com a chave de Produção, gera uma cobrança real. ## Visão geral O diagrama mostra o caminho completo de uma venda com entrega. Os números batem com os passos abaixo. ```mermaid sequenceDiagram autonumber participant S as Seu servidor participant A as API PagPolar participant W as Seu servidor de webhook participant V as Vendedor no painel S->>A: GET /offers/{identifier} A-->>S: 200 com requires_shipping S->>A: GET /offers/{identifier}/shipping com postal_code A-->>S: 200 com as opções de frete S->>A: POST /payments/pix com address e shipping_option_id A-->>S: 201 com transactions A-)W: TRANSACTION_PAID S->>A: GET /sales/{identifier} A-->>S: 200 com o bloco shipping A-)V: pedido de separação no painel V->>V: registra o envio e o rastreio no painel ``` ## Antes de começar * Uma credencial com a chave de API e a URL do webhook cadastrada. Veja [Credenciais da API](/docs/guias/fundamentos/credenciais). * Um token de acesso. Veja [Autenticação](/docs/guias/fundamentos/autenticacao#obter-o-token); os exemplos em Node.js assumem a variável `accessToken`. * Uma oferta ativa, com o meio de pagamento que você vai cobrar ligado. Veja [Criar produto e oferta](/docs/guias/jornadas/criar-produto-e-oferta). * Um servidor seu para [chamar a API](/docs/guias/fundamentos/ambientes#servidor). Três coisas são feitas pelo **vendedor no painel da PagPolar**, uma vez por produto. Não há rota de API para nenhuma delas: | No painel | O que cadastrar | | ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Configurações → Integrações** | A integração de logística. Hoje, Correios: **Usuário (CNPJ)**, **Contrato**, **Chave de API** e **CEP de Origem**. Dentro da integração ficam os serviços de logística. | | No produto, do tipo **Físico** | **Peso** em gramas e **Altura**, **Largura** e **Comprimento** em centímetros. | | No produto, aba **Frete** | Pelo menos uma configuração ativa: **Frete Grátis**, **Frete Fixo** ou **Frete Dinâmico**. O frete dinâmico só fica disponível quando o peso e as dimensões estão preenchidos, e depende da integração. | > **Sem configuração de frete, a consulta falha** > > Se o produto físico não tem nenhuma configuração de frete, a consulta do passo 2 responde `400` com `Nenhuma configuração de frete encontrada para este produto.`. Não há como criar a configuração pela API: peça ao vendedor para cadastrar na aba **Frete** do produto. 1. **Confirme que a oferta exige frete** Consulte a oferta e leia `requires_shipping`: #### cURL ```bash curl "https://api.pagpolar.com/v1/offers/PPP1234567890" \ -H "Authorization: Bearer " ``` #### Node.js ```js const response = await fetch('https://api.pagpolar.com/v1/offers/PPP1234567890', { headers: { Authorization: `Bearer ${accessToken}` }, }); console.log(response.status, await response.json()); ``` Resposta `200` (resumida): ```json { "data": { "id": "d5e6f7a8-b9c0-4d1e-8f3a-4b5c6d7e8f9a", "identifier": "PPP1234567890", "title": "Caneca personalizada", "price": 97, "is_active": true, "requires_shipping": true, "payment_methods": { "pix": true, "credit_card": true, "billet": true } } } ``` | `requires_shipping` | O que fazer | | ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `true` | O produto é físico. Siga para o passo 2: a cobrança vai exigir `address` e `shipping_option_id`. | | `false` | Siga direto para [Vender com PIX ou boleto](/docs/guias/jornadas/vender-com-pix-ou-boleto) ou [Vender com cartão de crédito](/docs/guias/jornadas/vender-com-cartao). Enviar `shipping_option_id` nessa oferta responde `400`. | Esta rota não devolve oferta oculta: o retorno é `404` com `Oferta não encontrada`. Veja [Ofertas, planos e ofertas ocultas](/docs/guias/conceitos/ofertas-planos-e-ofertas-ocultas). Contrato completo: [`GET /offers/{identifier}`](/docs/referencia/ofertas/get-offer). 2. **Consulte o frete pelo CEP** Envie o CEP de destino. A API devolve as opções de entrega daquele produto para aquele CEP. #### cURL ```bash curl "https://api.pagpolar.com/v1/offers/PPP1234567890/shipping?postal_code=01311000&quantity=1" \ -H "Authorization: Bearer " ``` #### Node.js ```js const response = await fetch( 'https://api.pagpolar.com/v1/offers/PPP1234567890/shipping?postal_code=01311000&quantity=1', { headers: { Authorization: `Bearer ${accessToken}` } }, ); console.log(response.status, await response.json()); ``` | Parâmetro | Onde vai | Obrigatório | O que é | | ------------- | ---------- | ----------- | --------------------------------------------------------------------------------------------- | | `identifier` | No caminho | Sim | Código da oferta (`PPP` e 10 dígitos, como `PPP1234567890`) ou o `id` da oferta (uuid). | | `postal_code` | Na query | Sim | CEP de destino, com ou sem pontuação. Precisa ter 8 dígitos. | | `quantity` | Na query | Não | Quantidade de unidades, número inteiro a partir de `1`. Padrão `1`. Entra no cálculo do peso. | Resposta `200` (resumida): ```json { "data": [ { "id": "pss_a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d", "name": "Retirada na loja", "type": "FREE", "cost": 0, "min_days": 1, "max_days": 2, "days_type": "BUSINESS_DAYS" }, { "id": "wil_b2c3d4e5-f6a7-4b8c-9d0e-1f2a3b4c5d6e", "name": "SEDEX", "type": "DYNAMIC", "cost": 32.9, "min_days": 5, "max_days": 5, "days_type": "BUSINESS_DAYS" } ] } ``` | Campo | O que é | | ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `id` | Identificador da opção. É o valor que você envia em `shipping_option_id` no passo 3. O formato é `pss_` ou `wil_` seguido de um uuid; você não precisa saber a diferença, só repassar o valor como veio. | | `name` | Nome da opção, como o comprador vê. | | `type` | `FREE` frete grátis, `PAID` valor fixo definido pelo vendedor, `DYNAMIC` calculado na hora pela transportadora. | | `cost` | Valor do frete **em reais**: `32.9` é R$ 32,90. | | `min_days` e `max_days` | Prazo de entrega, já com o prazo de preparo do vendedor somado. Nas opções `DYNAMIC` os dois vêm iguais: a transportadora devolve um prazo único. | | `days_type` | `BUSINESS_DAYS` para dias úteis, `CALENDAR_DAYS` para dias corridos. | A lista vem ordenada pelo `cost`, do menor para o maior. > **Consulte com a mesma quantidade que você vai cobrar** > > O valor do frete é recalculado na cobrança, com o `quantity` enviado lá e o CEP de `address.postal_code`. Se você consultar com `quantity=1` e cobrar com `quantity: 3`, o frete cobrado é o da quantidade 3, não o que você mostrou ao cliente. Use o mesmo valor nas duas chamadas. Contrato completo: [`GET /offers/{identifier}/shipping`](/docs/referencia/ofertas/list-offer-shipping). 3. **Cobre com o endereço e a opção de frete** O cliente escolheu uma opção. Envie o `id` dela em `shipping_option_id` e o endereço de entrega em `address`. Envie uma `Idempotency-Key` única para esta cobrança e **grave no seu pedido antes de enviar**. Veja [Idempotência](/docs/guias/fundamentos/idempotencia). O exemplo cobra por PIX: #### cURL ```bash curl -X POST "https://api.pagpolar.com/v1/payments/pix" \ -H "Authorization: Bearer " \ -H "Idempotency-Key: 3c8e1f7a-2b4d-4e6f-9a1c-5d7e9f0a1b2c" \ -H "Content-Type: application/json" \ -d '{ "offer_identifier": "PPP1234567890", "quantity": 1, "external_reference": "PEDIDO-0003", "shipping_option_id": "wil_b2c3d4e5-f6a7-4b8c-9d0e-1f2a3b4c5d6e", "customer": { "name": "Maria Silva", "email": "cliente@exemplo.com", "document": "", "phone": "" }, "address": { "street": "Rua das Flores", "number": "123", "neighborhood": "Centro", "city": "São Paulo", "state": "SP", "postal_code": "01311000" } }' ``` #### Node.js ```js import { randomUUID } from 'node:crypto'; const idempotencyKey = randomUUID(); const response = await fetch('https://api.pagpolar.com/v1/payments/pix', { method: 'POST', headers: { Authorization: `Bearer ${accessToken}`, 'Idempotency-Key': idempotencyKey, 'Content-Type': 'application/json', }, body: JSON.stringify({ offer_identifier: 'PPP1234567890', quantity: 1, external_reference: 'PEDIDO-0003', shipping_option_id: 'wil_b2c3d4e5-f6a7-4b8c-9d0e-1f2a3b4c5d6e', customer: { name: 'Maria Silva', email: 'cliente@exemplo.com', document: '', phone: '', }, address: { street: 'Rua das Flores', number: '123', neighborhood: 'Centro', city: 'São Paulo', state: 'SP', postal_code: '01311000', }, }), }); console.log(response.status, await response.json()); ``` Para **boleto**, troque a rota por `POST /payments/boleto`. Para **cartão de crédito**, use `POST /payments/credit-card` e acrescente `installments` e `credit_card`, como em [Vender com cartão de crédito](/docs/guias/jornadas/vender-com-cartao). Os dois campos de entrega são os mesmos nas três rotas. Os campos de entrega: | Campo | Obrigatório | O que é | | -------------------- | ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | | `shipping_option_id` | Sim, quando `requires_shipping` é `true` | O `id` da opção escolhida no passo 2, repassado como veio. Até 64 caracteres. Numa oferta que não exige frete, enviar este campo responde `400`. | | `address` | Sim, quando `requires_shipping` é `true` | Endereço de entrega. Os campos obrigatórios estão na tabela abaixo. | Dentro de `address`: | Campo | Obrigatório | O que é | | -------------- | ----------- | ------------------------------------------------------------------------------------------------------------ | | `street` | Sim | Rua, avenida ou logradouro. | | `number` | Sim | Número, como texto. | | `complement` | Não | Complemento, como `Apto 4B`. Aceita texto vazio ou `null`. | | `neighborhood` | Sim | Bairro. | | `city` | Sim | Cidade. | | `state` | Sim | Estado, como `SP`. | | `postal_code` | Sim | CEP de entrega. Use o mesmo CEP que você consultou no passo 2: é com ele que o valor do frete é recalculado. | 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](/docs/guias/fundamentos/valores-datas-e-identificadores#documento-e-telefone). | | `customer.phone` | Sim | Telefone do cliente com DDD, com ou sem pontuação. Veja o formato em [Documento e telefone](/docs/guias/fundamentos/valores-datas-e-identificadores#documento-e-telefone). | Os demais campos da cobrança — `offer`, `quantity`, `external_reference`, `affiliate_identifier`, `buyer_ip` e `buyer_user_agent` — funcionam igual ao produto digital. Veja [Vender com PIX ou boleto](/docs/guias/jornadas/vender-com-pix-ou-boleto). Resposta `201` do PIX (resumida): ```json { "data": { "offer_identifier": "PPP1234567890", "transactions": ["a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d"], "pix": { "qr_code": "00020126580014br.gov.bcb.pix0136a1b2c3d4-e5f6-4a7b-8c9d" } } } ``` **Guarde `transactions[0]` junto do seu pedido.** Ele é o `id` da venda, e é por ele que você liga o webhook ao pedido. A resposta da criação não mostra o valor do frete. Para conferir quanto foi cobrado, consulte a venda no passo 5. Contrato completo: [`POST /payments/pix`](/docs/referencia/vendas/create-pix-payment), [`POST /payments/boleto`](/docs/referencia/vendas/create-boleto-payment) e [`POST /payments/credit-card`](/docs/referencia/vendas/create-credit-card-payment). 4. **Espere o ** `TRANSACTION_PAID` A PagPolar envia um `POST` para a URL do webhook da sua credencial a cada mudança importante. Autentique a requisição e descarte repetidos: veja [Autenticar requisições](/docs/webhooks/autenticar-requisicoes) e [Processar sem duplicar](/docs/webhooks/processar-sem-duplicar). O evento que confirma o pagamento é o mesmo do produto digital: | Evento | Quando chega | O que fazer | | ------------------------------------------------------------- | -------------------------------- | --------------------------------------------------------------------------------------------------------- | | [`TRANSACTION_PAID`](/docs/webhooks/eventos/transaction-paid) | O gateway confirmou o pagamento. | Dê a venda por fechada no seu sistema. A partir daqui a separação e o envio correm no painel do vendedor. | Use `data.transaction.id`, o mesmo valor de `transactions[0]`, para achar o seu pedido: o webhook **não** traz a `external_reference`. Veja todos os campos em [Formato do evento](/docs/webhooks/formato-do-evento). Os outros eventos do fluxo mudam conforme o meio de pagamento. Para PIX e boleto, veja [Espere o webhook](/docs/guias/jornadas/vender-com-pix-ou-boleto); para cartão, [Vender com cartão de crédito](/docs/guias/jornadas/vender-com-cartao). Os status da venda estão em [Ciclo de vida da venda](/docs/guias/conceitos/ciclo-de-vida-da-venda). 5. **Confira o frete na venda** A consulta da venda traz o bloco `shipping`, com o frete cobrado e o endereço gravado. #### cURL ```bash curl "https://api.pagpolar.com/v1/sales/a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d" \ -H "Authorization: Bearer " ``` #### Node.js ```js const response = await fetch( 'https://api.pagpolar.com/v1/sales/a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d', { headers: { Authorization: `Bearer ${accessToken}` } }, ); console.log(response.status, await response.json()); ``` Resposta `200` (resumida): ```json { "data": { "id": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d", "identifier": "PPO9876543210", "external_reference": "PEDIDO-0003", "status": "PAID", "payment_method": "PIX", "total_amount": 129.9, "paid_at": "2026-09-15T14:35:00.000Z", "shipping": { "amount": 32.9, "option_name": "SEDEX", "address": { "street": "Rua das Flores", "number": "123", "complement": null, "neighborhood": "Centro", "city": "São Paulo", "state": "SP", "postal_code": "01311000" } } } } ``` | Campo | O que é | | ---------------------- | ----------------------------------------------------------------------------------------------------------------------------- | | `shipping.amount` | Valor do frete **em reais**, já somado ao `total_amount`. No exemplo, R$ 97,00 do produto e R$ 32,90 de frete fecham `129.9`. | | `shipping.option_name` | Nome da opção escolhida na cobrança. Pode vir `null`. | | `shipping.address` | O endereço de entrega gravado na venda. | | `shipping` | Vem `null` quando a venda não tem entrega. | Os valores vêm em reais, como número. Veja [Valores nas respostas](/docs/guias/fundamentos/valores-datas-e-identificadores#valores-nas-respostas). Contrato completo: [`GET /sales/{identifier}`](/docs/referencia/vendas/get-sale). ## Quando o frete não vem A consulta do passo 2 pode responder `200` com menos opções do que você espera, ou com a lista vazia. Ela não falha por causa da transportadora. | Situação | O que você recebe | O que fazer | | ------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- | | A transportadora não responde ou recusa o cálculo. | `200`. As opções calculadas pela transportadora ficam de fora. As opções grátis e de valor fixo que não dependem da integração continuam na lista. | Mostre ao cliente só o que veio. Se a lista ficou vazia, tente de novo em alguns instantes. | | A oferta não é de produto físico. | `200` com `"data": []`. | Não há frete a cobrar. Cobre sem `address` nem `shipping_option_id`. | | O produto físico não tem nenhuma configuração de frete. | `400` com `Nenhuma configuração de frete encontrada para este produto.` | Peça ao vendedor para criar uma configuração ativa na aba **Frete** do produto. | | O produto não tem o peso e as dimensões preenchidos. | A configuração de **Frete Dinâmico** não fica disponível no painel, então a consulta devolve só as opções que não dependem da integração. | Peça ao vendedor para preencher **Peso**, **Altura**, **Largura** e **Comprimento** no produto. | Nunca mande o cliente para o pagamento sem uma opção escolhida: a cobrança de produto físico sem `shipping_option_id` responde `400`. ## Erros desta jornada Além dos [erros comuns a todas as rotas](/docs/guias/fundamentos/erros#erros-comuns), que incluem `401`, `403` e `429`, este fluxo pode responder: | Resposta | Quando acontece | O que fazer | | ----------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `400` com `postal_code deve ter 8 dígitos` | O `postal_code` da consulta de frete não tem 8 dígitos. | Envie o CEP completo. Pontuação é aceita: `01311-000` e `01311000` valem. | | `400` com `Nenhuma configuração de frete encontrada para este produto.` | A oferta é de produto físico, mas o produto não tem configuração de frete. | Veja [Quando o frete não vem](#quando-o-frete-nao-vem). | | `404` com `Oferta não encontrada` | Na consulta de frete: o código ou o `id` não existe na sua conta. Na consulta da oferta, também quando a oferta é oculta ou foi removida. | Confira o identificador. A chave precisa ser da mesma conta da oferta. | | `400` com `Endereço de entrega é obrigatório para este produto` | A cobrança de produto físico foi enviada sem `address`, ou sem `address.postal_code`. | Envie `address` com todos os campos obrigatórios. | | `400` com `Informe shipping_option_id: consulte as opções em GET /offers/{identifier}/shipping` | A cobrança de produto físico foi enviada sem `shipping_option_id`. | Faça o passo 2 e envie o `id` da opção escolhida. | | `400` com `shipping_option_id inválido` | O valor enviado não está no formato devolvido pela consulta. | Repasse o `id` exatamente como veio, com o prefixo `pss_` ou `wil_`. Não recorte nem monte o valor. | | `400` com `Opção de frete não encontrada` | O formato está certo, mas a opção não aparece entre as opções daquele produto para o CEP e a quantidade da cobrança. | Consulte o frete de novo com o CEP e o `quantity` da cobrança, e escolha uma opção da lista nova. A configuração de frete do produto pode ter mudado. | | `400` com `Esta oferta não exige frete, remova shipping_option_id` | `shipping_option_id` foi enviado numa oferta com `requires_shipping: false`. | Remova o campo. | | `400`, `404` ou `409` nas regras da oferta | As mesmas conferências de toda cobrança: oferta inexistente, inativa, expirada, meio de pagamento desligado, quantidade acima do limite, `Idempotency-Key` ausente. | Veja [Regras conferidas em toda cobrança](/docs/guias/conceitos/ofertas-planos-e-ofertas-ocultas#regras-da-cobranca) e [Vender com PIX ou boleto](/docs/guias/jornadas/vender-com-pix-ou-boleto). | ## Depois da venda Com o pagamento confirmado, a PagPolar gera o pedido de separação e o **vendedor registra o envio e o código de rastreio no painel**. Essa parte **não está na API pública**: * não há rota para registrar o envio; * não há rota para informar ou consultar o código de rastreio; * o bloco `shipping` de `GET /sales/{identifier}` traz o valor do frete, o nome da opção e o endereço de entrega — e nada sobre o envio. Se o seu sistema precisa do rastreio, combine com o vendedor como esse dado chega até você fora da API. ## Próximos passos - [Vender com PIX ou boleto](/docs/guias/jornadas/vender-com-pix-ou-boleto) — Os eventos e os prazos de cada meio de pagamento. - [Vender com cartão de crédito](/docs/guias/jornadas/vender-com-cartao) — Cobre no cartão, à vista ou parcelado. - [Conciliar vendas](/docs/guias/jornadas/conciliar-vendas) — Liste e confira as vendas do período. - [Ciclo de vida da venda](/docs/guias/conceitos/ciclo-de-vida-da-venda) — Todos os status da venda, inclusive reembolso e chargeback. --- # Assinaturas URL: https://staging.pagpolar.com/docs/referencia/assinaturas > Assine, consulte, cancele, troque o plano e troque o cartão. Objeto principal: **[Assinatura](/docs/referencia/entidades/assinatura)**. | Campo | Tipo | Obrigatório | Nulo | Descrição | | --- | --- | --- | --- | --- | | `id` | texto (uuid) | não | não | Exemplo: `f7a8b9c0-d1e2-4f3a-4b5c-6d7e8f9a0b1c`. | | `status` | enum | não | não | Valores: `DRAFT`, `PENDING_PAYMENT`, `ACTIVE`, `PENDING_RENEWAL`, `PROCESSING`, `CANCELING`, `CANCELED`, `ASK_REFUND`, `REFUNDED`, `ABANDONED`, `EXPIRED`, `FAILED`. Exemplo: `ACTIVE`. | | `payment_method` | enum | não | sim | Valores: `CREDIT_CARD`, `PIX`, `BOLETO`, `APPLE_PAY`, `GOOGLE_PAY`. Exemplo: `CREDIT_CARD`. | | `start_at` | texto (date-time) | não | sim | Exemplo: `2026-01-01T00:00:00.000Z`. | | `end_at` | texto (date-time) | não | sim | Data de término, quando a assinatura tem prazo definido. Exemplo: `null`. | | `next_billing_at` | texto (date-time) | não | sim | Exemplo: `2026-09-01T00:00:00.000Z`. | | `next_billing_amount` | número | não | sim | Exemplo: `97`. | | `total_amount` | número | não | sim | Exemplo: `1164`. | | `canceled_at` | texto (date-time) | não | sim | null enquanto a assinatura não é cancelada. Exemplo: `null`. | | `cycle_limit` | inteiro | não | sim | Quantidade máxima de ciclos cobrados. `null` quando não há limite. Exemplo: `null`. | | `paid_at` | texto (date-time) | não | sim | Exemplo: `2026-01-01T00:05:00.000Z`. | | `created_at` | texto (date-time) | não | não | Exemplo: `2026-01-01T00:00:00.000Z`. | | `customer` | [Cliente](/docs/referencia/entidades/cliente) | não | não | — | | `offer` | [Oferta](/docs/referencia/entidades/oferta) | não | não | — | | `product` | [Produto](/docs/referencia/entidades/produto) | não | não | — | --- # Autenticação URL: https://staging.pagpolar.com/docs/referencia/autenticacao > Obtenha o token de acesso e confira a credencial usada na chamada. ## Token de acesso `POST /auth/token` devolve o objeto **[Token de acesso](/docs/referencia/entidades/token-de-acesso)**. | Campo | Tipo | Obrigatório | Nulo | Descrição | | --- | --- | --- | --- | --- | | `access_token` | texto | sim | não | Token de acesso (JWT). Envie em `Authorization: Bearer `. Exemplo: `eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJlNmY3YThiOS1jMGQxLTRlMmYtM2E0Yi01YzZkN2U4ZjlhMGIifQ.assinatura-do-token`. | | `token_type` | enum | sim | não | Sempre `Bearer`. Valores: `Bearer`. Exemplo: `Bearer`. | | `expires_in` | inteiro | sim | não | Segundos até o token expirar, contados a partir da emissão. `86400` são 24 horas. Exemplo: `86400`. | ## Credencial `GET /me` devolve o objeto **[Credencial](/docs/referencia/entidades/credencial)**. | Campo | Tipo | Obrigatório | Nulo | Descrição | | --- | --- | --- | --- | --- | | `credential_id` | texto (uuid) | sim | não | Identificador da credencial dona do token. Exemplo: `e6f7a8b9-c0d1-4e2f-3a4b-5c6d7e8f9a0b`. | | `environment` | enum | sim | não | Ambiente da credencial, definido na criação e imutável. As duas cobram de verdade. Valores: `PRODUCTION`, `STAGING`. Exemplo: `PRODUCTION`. | | `rate_limit_per_minute` | inteiro | sim | não | Limite de requisições por minuto desta credencial. Exemplo: `120`. | --- # Clientes URL: https://staging.pagpolar.com/docs/referencia/clientes > Liste e consulte clientes. Objeto principal: **[Cliente](/docs/referencia/entidades/cliente)**. | Campo | Tipo | Obrigatório | Nulo | Descrição | | --- | --- | --- | --- | --- | | `id` | texto (uuid) | não | não | Exemplo: `e6f7a8b9-c0d1-4e2f-3a4b-5c6d7e8f9a0b`. | | `name` | texto | não | não | Exemplo: `Maria Souza`. | | `email` | texto (email) | não | não | Exemplo: `maria@exemplo.com`. | | `document` | texto | não | sim | Mascarado: CPF vira ***.XXX.***-**, CNPJ vira **.XXX.***/****-**. null se o cliente não tem documento cadastrado. Exemplo: `***.456.***-**`. | | `phone` | texto | não | sim | Mascarado: mantém só os últimos 4 dígitos (****XXXX). null se o cliente não tem telefone cadastrado. Exemplo: `****4321`. | | `is_active` | booleano | não | não | Exemplo: `true`. | | `created_at` | texto (date-time) | não | não | Exemplo: `2026-01-05T08:00:00.000Z`. | --- # Assinatura URL: https://staging.pagpolar.com/docs/referencia/entidades/assinatura > Contrato de cobrança recorrente de um cliente num plano. Cada ciclo cobrado vira uma [Venda](/docs/referencia/entidades/venda). Contrato de cobrança recorrente de um cliente num plano. Cada ciclo cobrado vira uma [Venda](/docs/referencia/entidades/venda). ## Onde aparece * [`POST /plans/offer/{id}/subscribe`](/docs/referencia/assinaturas/create-subscription) * [`GET /subscriptions`](/docs/referencia/assinaturas/list-subscriptions) * [`GET /subscriptions/{id}`](/docs/referencia/assinaturas/get-subscription) ## Campos Campos anuláveis chegam com `null`. Campos com opções fixas listam todos os valores. | Campo | Tipo | Obrigatório | Nulo | Descrição | | --- | --- | --- | --- | --- | | `id` | texto (uuid) | não | não | Exemplo: `f7a8b9c0-d1e2-4f3a-4b5c-6d7e8f9a0b1c`. | | `status` | enum | não | não | Valores: `DRAFT`, `PENDING_PAYMENT`, `ACTIVE`, `PENDING_RENEWAL`, `PROCESSING`, `CANCELING`, `CANCELED`, `ASK_REFUND`, `REFUNDED`, `ABANDONED`, `EXPIRED`, `FAILED`. Exemplo: `ACTIVE`. | | `payment_method` | enum | não | sim | Valores: `CREDIT_CARD`, `PIX`, `BOLETO`, `APPLE_PAY`, `GOOGLE_PAY`. Exemplo: `CREDIT_CARD`. | | `start_at` | texto (date-time) | não | sim | Exemplo: `2026-01-01T00:00:00.000Z`. | | `end_at` | texto (date-time) | não | sim | Data de término, quando a assinatura tem prazo definido. Exemplo: `null`. | | `next_billing_at` | texto (date-time) | não | sim | Exemplo: `2026-09-01T00:00:00.000Z`. | | `next_billing_amount` | número | não | sim | Exemplo: `97`. | | `total_amount` | número | não | sim | Exemplo: `1164`. | | `canceled_at` | texto (date-time) | não | sim | null enquanto a assinatura não é cancelada. Exemplo: `null`. | | `cycle_limit` | inteiro | não | sim | Quantidade máxima de ciclos cobrados. `null` quando não há limite. Exemplo: `null`. | | `paid_at` | texto (date-time) | não | sim | Exemplo: `2026-01-01T00:05:00.000Z`. | | `created_at` | texto (date-time) | não | não | Exemplo: `2026-01-01T00:00:00.000Z`. | | `customer` | [Cliente](/docs/referencia/entidades/cliente) | não | não | — | | `offer` | [Oferta](/docs/referencia/entidades/oferta) | não | não | — | | `product` | [Produto](/docs/referencia/entidades/produto) | não | não | — | --- # Cliente URL: https://staging.pagpolar.com/docs/referencia/entidades/cliente > Quem comprou. Documento e telefone chegam mascarados. Quem comprou. Documento e telefone chegam mascarados. ## Onde aparece * [`GET /customers`](/docs/referencia/clientes/list-customers) * [`GET /customers/{id}`](/docs/referencia/clientes/get-customer) Também aparece dentro de [Venda](/docs/referencia/entidades/venda), [Assinatura](/docs/referencia/entidades/assinatura) e [Reembolso](/docs/referencia/entidades/reembolso), no campo `customer`. ## Campos Campos anuláveis chegam com `null`. Campos com opções fixas listam todos os valores. | Campo | Tipo | Obrigatório | Nulo | Descrição | | --- | --- | --- | --- | --- | | `id` | texto (uuid) | não | não | Exemplo: `e6f7a8b9-c0d1-4e2f-3a4b-5c6d7e8f9a0b`. | | `name` | texto | não | não | Exemplo: `Maria Souza`. | | `email` | texto (email) | não | não | Exemplo: `maria@exemplo.com`. | | `document` | texto | não | sim | Mascarado: CPF vira ***.XXX.***-**, CNPJ vira **.XXX.***/****-**. null se o cliente não tem documento cadastrado. Exemplo: `***.456.***-**`. | | `phone` | texto | não | sim | Mascarado: mantém só os últimos 4 dígitos (****XXXX). null se o cliente não tem telefone cadastrado. Exemplo: `****4321`. | | `is_active` | booleano | não | não | Exemplo: `true`. | | `created_at` | texto (date-time) | não | não | Exemplo: `2026-01-05T08:00:00.000Z`. | --- # Cobrança por boleto URL: https://staging.pagpolar.com/docs/referencia/entidades/cobranca-boleto > Corpo para criar uma venda por boleto. Corpo para criar uma venda por boleto. ## Onde é enviado * [`POST /payments/boleto`](/docs/referencia/vendas/create-boleto-payment) ## Campos Campos anuláveis chegam com `null`. Campos com opções fixas listam todos os valores. | Campo | Tipo | Obrigatório | Nulo | Descrição | | --- | --- | --- | --- | --- | | `offer_identifier` | texto | não | não | Código de uma oferta existente. Envie este campo **ou** `offer` — nunca os dois. Exemplo: `PPP1234567890`. | | `offer` | objeto | não | não | Oferta informada na hora. Envie este objeto **ou** `offer_identifier` — nunca os dois. Reaproveita a oferta com mesmo produto, nome, valor e visibilidade; senão cria. | | `offer.product_id` | texto (uuid) | sim | não | Produto da sua conta. | | `offer.name` | texto | sim | não | Nome da oferta. Exemplo: `Consultoria avulsa`. | | `offer.value` | inteiro | sim | não | Valor em centavos. Mínimo 500 (R$ 5,00). Exemplo: `4990`. | | `offer.createOffer` | booleano | sim | não | `true`: oferta visível. `false`: oferta oculta, fora das listagens e usável só por esta API. Exemplo: `false`. | | `quantity` | inteiro | não | não | — | | `affiliate_identifier` | texto | não | não | Código do afiliado que trouxe a venda, no formato `PAO` seguido de 10 dígitos. Formato inválido retorna `400`. Código de afiliado inexistente, de outro produto, inativo ou sem a oferta liberada é **ignorado**: a venda é processada normalmente, sem afiliado. Não há cookie nem regra de primeiro ou último clique — vale o código enviado. Exemplo: `PAO1234567890`. | | `external_reference` | texto | não | não | Referência do pedido no seu sistema (por exemplo, o número do pedido). Opcional. Depois você consulta a venda por ela em `GET /sales/{identifier}` e filtra em `GET /sales?external_reference=`. Pode se repetir entre pedidos: a consulta devolve a venda mais recente com essa referência. Exemplo: `PED-2026-0001`. | | `customer` | objeto | sim | não | — | | `customer.name` | texto | sim | não | Exemplo: `Fulano de Tal`. | | `customer.email` | texto (email) | sim | não | Exemplo: `fulano@exemplo.com`. | | `customer.document` | texto | sim | não | CPF (11 dígitos) ou CNPJ (14 dígitos), com ou sem pontuação: `12345678909` ou `123.456.789-09`. A API grava só os dígitos. Fora disso, 400. Exemplo: `12345678909`. | | `customer.phone` | texto | sim | não | DDD e número, com 10 ou 11 dígitos (12 ou 13 com o código do país 55). Pontuação, espaços, parênteses e `+` são aceitos: `11999998888`, `(11) 99999-8888` ou `+55 11 99999-8888`. A API grava só os dígitos. Fora disso, 400. Exemplo: `11999999999`. | | `address` | objeto | não | não | Obrigatório quando a oferta é de produto físico (`requires_shipping: true`); nos demais casos é opcional e não é usado no processamento da venda. | | `address.street` | texto | sim | não | Exemplo: `Rua das Flores`. | | `address.number` | texto | sim | não | Exemplo: `123`. | | `address.complement` | texto | não | sim | Exemplo: `Apto 4B`. | | `address.neighborhood` | texto | sim | não | Exemplo: `Centro`. | | `address.city` | texto | sim | não | Exemplo: `São Paulo`. | | `address.state` | texto | sim | não | Exemplo: `SP`. | | `address.postal_code` | texto | sim | não | Exemplo: `01000-000`. | | `shipping_option_id` | texto | não | não | Opção de frete escolhida, obrigatória quando a oferta é de produto físico (`requires_shipping: true`). Use o `id` devolvido por `GET /offers/{identifier}/shipping` para o mesmo CEP enviado em `address.postal_code`. Enviar este campo numa oferta que não exige frete retorna `400`. Exemplo: `wil_a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d`. | | `buyer_ip` | texto | não | não | IP do comprador final. Melhora a análise antifraude. Exemplo: `203.0.113.10`. | | `buyer_user_agent` | texto | não | não | User agent do comprador final. | --- # Cobrança no cartão URL: https://staging.pagpolar.com/docs/referencia/entidades/cobranca-cartao > Corpo para criar uma venda no cartão de crédito. Corpo para criar uma venda no cartão de crédito. ## Onde é enviado * [`POST /payments/credit-card`](/docs/referencia/vendas/create-credit-card-payment) ## Campos Campos anuláveis chegam com `null`. Campos com opções fixas listam todos os valores. | Campo | Tipo | Obrigatório | Nulo | Descrição | | --- | --- | --- | --- | --- | | `offer_identifier` | texto | não | não | Código de uma oferta existente. Envie este campo **ou** `offer` — nunca os dois. Exemplo: `PPP1234567890`. | | `offer` | objeto | não | não | Oferta informada na hora. Envie este objeto **ou** `offer_identifier` — nunca os dois. Reaproveita a oferta com mesmo produto, nome, valor e visibilidade; senão cria. | | `offer.product_id` | texto (uuid) | sim | não | Produto da sua conta. | | `offer.name` | texto | sim | não | Nome da oferta. Exemplo: `Consultoria avulsa`. | | `offer.value` | inteiro | sim | não | Valor em centavos. Mínimo 500 (R$ 5,00). Exemplo: `4990`. | | `offer.createOffer` | booleano | sim | não | `true`: oferta visível. `false`: oferta oculta, fora das listagens e usável só por esta API. Exemplo: `false`. | | `quantity` | inteiro | não | não | — | | `affiliate_identifier` | texto | não | não | Código do afiliado que trouxe a venda, no formato `PAO` seguido de 10 dígitos. Formato inválido retorna `400`. Código de afiliado inexistente, de outro produto, inativo ou sem a oferta liberada é **ignorado**: a venda é processada normalmente, sem afiliado. Não há cookie nem regra de primeiro ou último clique — vale o código enviado. Exemplo: `PAO1234567890`. | | `external_reference` | texto | não | não | Referência do pedido no seu sistema (por exemplo, o número do pedido). Opcional. Depois você consulta a venda por ela em `GET /sales/{identifier}` e filtra em `GET /sales?external_reference=`. Pode se repetir entre pedidos: a consulta devolve a venda mais recente com essa referência. Exemplo: `PED-2026-0001`. | | `customer` | objeto | sim | não | — | | `customer.name` | texto | sim | não | Exemplo: `Fulano de Tal`. | | `customer.email` | texto (email) | sim | não | Exemplo: `fulano@exemplo.com`. | | `customer.document` | texto | sim | não | CPF (11 dígitos) ou CNPJ (14 dígitos), com ou sem pontuação: `12345678909` ou `123.456.789-09`. A API grava só os dígitos. Fora disso, 400. Exemplo: `12345678909`. | | `customer.phone` | texto | sim | não | DDD e número, com 10 ou 11 dígitos (12 ou 13 com o código do país 55). Pontuação, espaços, parênteses e `+` são aceitos: `11999998888`, `(11) 99999-8888` ou `+55 11 99999-8888`. A API grava só os dígitos. Fora disso, 400. Exemplo: `11999999999`. | | `address` | objeto | não | não | Obrigatório quando a oferta é de produto físico (`requires_shipping: true`); nos demais casos é opcional e não é usado no processamento da venda. | | `address.street` | texto | sim | não | Exemplo: `Rua das Flores`. | | `address.number` | texto | sim | não | Exemplo: `123`. | | `address.complement` | texto | não | sim | Exemplo: `Apto 4B`. | | `address.neighborhood` | texto | sim | não | Exemplo: `Centro`. | | `address.city` | texto | sim | não | Exemplo: `São Paulo`. | | `address.state` | texto | sim | não | Exemplo: `SP`. | | `address.postal_code` | texto | sim | não | Exemplo: `01000-000`. | | `shipping_option_id` | texto | não | não | Opção de frete escolhida, obrigatória quando a oferta é de produto físico (`requires_shipping: true`). Use o `id` devolvido por `GET /offers/{identifier}/shipping` para o mesmo CEP enviado em `address.postal_code`. Enviar este campo numa oferta que não exige frete retorna `400`. Exemplo: `wil_a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d`. | | `buyer_ip` | texto | não | não | IP do comprador final. Melhora a análise antifraude. Exemplo: `203.0.113.10`. | | `buyer_user_agent` | texto | não | não | User agent do comprador final. | | `installments` | inteiro | sim | não | O máximo real pode ser menor que 12: é limitado pela configuração da oferta (consulte max_credit_card_installments em GET /offers/{identifier}). Acima do limite da oferta, retorna 400. Exemplo: `3`. | | `credit_card` | objeto | sim | não | Dados do cartão de crédito usado na cobrança. | | `credit_card.holder_name` | texto | sim | não | Exemplo: `FULANO DE TAL`. | | `credit_card.holder_document` | texto | sim | não | CPF (11 dígitos) ou CNPJ (14 dígitos) do titular do cartão, com ou sem pontuação. A API grava só os dígitos. Fora disso, 400. Exemplo: `12345678909`. | | `credit_card.number` | texto | sim | não | Exemplo: `4111111111111111`. | | `credit_card.expiration_month` | inteiro | sim | não | Exemplo: `12`. | | `credit_card.expiration_year` | inteiro | sim | não | Exemplo: `2030`. | | `credit_card.cvv` | texto | sim | não | Exemplo: `123`. | --- # Cobrança PIX URL: https://staging.pagpolar.com/docs/referencia/entidades/cobranca-pix > Corpo para criar uma venda PIX. Corpo para criar uma venda PIX. ## Onde é enviado * [`POST /payments/pix`](/docs/referencia/vendas/create-pix-payment) ## Campos Campos anuláveis chegam com `null`. Campos com opções fixas listam todos os valores. | Campo | Tipo | Obrigatório | Nulo | Descrição | | --- | --- | --- | --- | --- | | `offer_identifier` | texto | não | não | Código de uma oferta existente. Envie este campo **ou** `offer` — nunca os dois. Exemplo: `PPP1234567890`. | | `offer` | objeto | não | não | Oferta informada na hora. Envie este objeto **ou** `offer_identifier` — nunca os dois. Reaproveita a oferta com mesmo produto, nome, valor e visibilidade; senão cria. | | `offer.product_id` | texto (uuid) | sim | não | Produto da sua conta. | | `offer.name` | texto | sim | não | Nome da oferta. Exemplo: `Consultoria avulsa`. | | `offer.value` | inteiro | sim | não | Valor em centavos. Mínimo 500 (R$ 5,00). Exemplo: `4990`. | | `offer.createOffer` | booleano | sim | não | `true`: oferta visível. `false`: oferta oculta, fora das listagens e usável só por esta API. Exemplo: `false`. | | `quantity` | inteiro | não | não | — | | `affiliate_identifier` | texto | não | não | Código do afiliado que trouxe a venda, no formato `PAO` seguido de 10 dígitos. Formato inválido retorna `400`. Código de afiliado inexistente, de outro produto, inativo ou sem a oferta liberada é **ignorado**: a venda é processada normalmente, sem afiliado. Não há cookie nem regra de primeiro ou último clique — vale o código enviado. Exemplo: `PAO1234567890`. | | `external_reference` | texto | não | não | Referência do pedido no seu sistema (por exemplo, o número do pedido). Opcional. Depois você consulta a venda por ela em `GET /sales/{identifier}` e filtra em `GET /sales?external_reference=`. Pode se repetir entre pedidos: a consulta devolve a venda mais recente com essa referência. Exemplo: `PED-2026-0001`. | | `customer` | objeto | sim | não | — | | `customer.name` | texto | sim | não | Exemplo: `Fulano de Tal`. | | `customer.email` | texto (email) | sim | não | Exemplo: `fulano@exemplo.com`. | | `customer.document` | texto | sim | não | CPF (11 dígitos) ou CNPJ (14 dígitos), com ou sem pontuação: `12345678909` ou `123.456.789-09`. A API grava só os dígitos. Fora disso, 400. Exemplo: `12345678909`. | | `customer.phone` | texto | sim | não | DDD e número, com 10 ou 11 dígitos (12 ou 13 com o código do país 55). Pontuação, espaços, parênteses e `+` são aceitos: `11999998888`, `(11) 99999-8888` ou `+55 11 99999-8888`. A API grava só os dígitos. Fora disso, 400. Exemplo: `11999999999`. | | `address` | objeto | não | não | Obrigatório quando a oferta é de produto físico (`requires_shipping: true`); nos demais casos é opcional e não é usado no processamento da venda. | | `address.street` | texto | sim | não | Exemplo: `Rua das Flores`. | | `address.number` | texto | sim | não | Exemplo: `123`. | | `address.complement` | texto | não | sim | Exemplo: `Apto 4B`. | | `address.neighborhood` | texto | sim | não | Exemplo: `Centro`. | | `address.city` | texto | sim | não | Exemplo: `São Paulo`. | | `address.state` | texto | sim | não | Exemplo: `SP`. | | `address.postal_code` | texto | sim | não | Exemplo: `01000-000`. | | `shipping_option_id` | texto | não | não | Opção de frete escolhida, obrigatória quando a oferta é de produto físico (`requires_shipping: true`). Use o `id` devolvido por `GET /offers/{identifier}/shipping` para o mesmo CEP enviado em `address.postal_code`. Enviar este campo numa oferta que não exige frete retorna `400`. Exemplo: `wil_a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d`. | | `buyer_ip` | texto | não | não | IP do comprador final. Melhora a análise antifraude. Exemplo: `203.0.113.10`. | | `buyer_user_agent` | texto | não | não | User agent do comprador final. | --- # Credencial URL: https://staging.pagpolar.com/docs/referencia/entidades/credencial > Dados da credencial dona do token de acesso usado na chamada. Dados da credencial dona do token de acesso usado na chamada. ## Onde aparece * [`GET /me`](/docs/referencia/autenticacao/get-current-credential) ## Campos Campos anuláveis chegam com `null`. Campos com opções fixas listam todos os valores. | Campo | Tipo | Obrigatório | Nulo | Descrição | | --- | --- | --- | --- | --- | | `credential_id` | texto (uuid) | sim | não | Identificador da credencial dona do token. Exemplo: `e6f7a8b9-c0d1-4e2f-3a4b-5c6d7e8f9a0b`. | | `environment` | enum | sim | não | Ambiente da credencial, definido na criação e imutável. As duas cobram de verdade. Valores: `PRODUCTION`, `STAGING`. Exemplo: `PRODUCTION`. | | `rate_limit_per_minute` | inteiro | sim | não | Limite de requisições por minuto desta credencial. Exemplo: `120`. | --- # Oferta URL: https://staging.pagpolar.com/docs/referencia/entidades/oferta > Preço de venda de um produto, com meios de pagamento, parcelas e, na oferta de plano, a periodicidade da cobrança. Preço de venda de um produto, com meios de pagamento, parcelas e, na oferta de plano, a periodicidade da cobrança. ## Onde aparece * [`POST /offers`](/docs/referencia/ofertas/create-offer) * [`GET /offers/{identifier}`](/docs/referencia/ofertas/get-offer) * [`GET /offers/by-product/{id}`](/docs/referencia/ofertas/list-product-offers) * [`PATCH /offers/{id}`](/docs/referencia/ofertas/update-offer) * [`GET /plans/{id}/offers`](/docs/referencia/planos/list-plan-offers) * [`POST /plans/{id}/offers`](/docs/referencia/planos/create-plan-offer) * [`PATCH /plan-offers/{id}`](/docs/referencia/planos/update-plan-offer) O `price` da resposta vem em **reais**. No envio, `price` é em centavos — veja [Valores, datas e identificadores](/docs/guias/fundamentos/valores-datas-e-identificadores). Também aparece dentro de [Assinatura](/docs/referencia/entidades/assinatura), no campo `offer`. ## Campos Campos anuláveis chegam com `null`. Campos com opções fixas listam todos os valores. | Campo | Tipo | Obrigatório | Nulo | Descrição | | --- | --- | --- | --- | --- | | `id` | texto (uuid) | não | não | Exemplo: `d5e6f7a8-b9c0-4d1e-2f3a-4b5c6d7e8f9a`. | | `identifier` | texto | não | não | Código da oferta: prefixo `PPP` seguido de 10 dígitos. Exemplo: `PPP1234567890`. | | `title` | texto | não | sim | Exemplo: `Plano Mensal`. | | `price` | número | não | não | Exemplo: `197.9`. | | `is_active` | booleano | não | não | Exemplo: `true`. | | `requires_shipping` | booleano | não | não | true quando o produto é físico: a cobrança exige `address` e `shipping_option_id`. Consulte as opções em `GET /offers/{identifier}/shipping`. Exemplo: `false`. | | `is_default` | booleano | não | não | Exemplo: `false`. | | `product_id` | texto (uuid) | não | não | Exemplo: `c4d5e6f7-a8b9-4c0d-1e2f-3a4b5c6d7e8f`. | | `payment_methods` | objeto | não | não | Meios de pagamento ligados. Cada meio só fica ligado se o preço atinge o mínimo dele: PIX R$ 5,00, cartão R$ 5,00 e boleto R$ 10,00. | | `payment_methods.pix` | booleano | não | não | Exemplo: `true`. | | `payment_methods.credit_card` | booleano | não | não | Exemplo: `true`. | | `payment_methods.billet` | booleano | não | não | Exemplo: `false`. | | `max_credit_card_installments` | inteiro | não | não | Exemplo: `12`. | | `cycle` | enum | não | sim | null para oferta avulsa (não recorrente). Preenchido só quando a oferta é de assinatura. Valores: `DAILY`, `WEEKLY`, `MONTHLY`, `YEARLY`. Exemplo: `MONTHLY`. | | `cycle_interval` | inteiro | não | sim | Exemplo: `1`. | | `cycle_interval_limit` | inteiro | não | sim | null quando a assinatura não tem limite de ciclos. Exemplo: `12`. | | `allow_purchase_quantity` | booleano | não | não | Exemplo: `false`. | | `purchase_quantity_limit` | inteiro | não | sim | Exemplo: `10`. | | `purchase_quantity_min` | inteiro | não | não | Exemplo: `1`. | | `expires_at` | texto (date-time) | não | sim | Exemplo: `null`. | | `created_at` | texto (date-time) | não | não | Exemplo: `2026-01-10T10:00:00.000Z`. | --- # Opção de frete URL: https://staging.pagpolar.com/docs/referencia/entidades/opcao-de-frete > Uma forma de entrega disponível para um CEP, com valor e prazo. Uma forma de entrega disponível para um CEP, com valor e prazo. O `id` é o que você envia em `shipping_option_id` na criação do pagamento. ## Onde aparece * [`GET /offers/{identifier}/shipping`](/docs/referencia/ofertas/list-offer-shipping) ## Campos Campos anuláveis chegam com `null`. Campos com opções fixas listam todos os valores. | Campo | Tipo | Obrigatório | Nulo | Descrição | | --- | --- | --- | --- | --- | | `id` | texto | não | sim | Identificador da opção de frete. Envie este valor em `shipping_option_id` na criação do pagamento. Vale enquanto a configuração de frete do produto não mudar. Exemplo: `wil_a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d`. | | `name` | texto | não | não | Nome da opção, como o comprador vê. Exemplo: `SEDEX`. | | `type` | enum | não | não | `FREE` frete grátis, `PAID` valor fixo definido pelo vendedor, `DYNAMIC` calculado na hora pela transportadora. Valores: `FREE`, `PAID`, `DYNAMIC`. Exemplo: `DYNAMIC`. | | `cost` | número | não | sim | Valor do frete em reais. Exemplo: `32.9`. | | `min_days` | inteiro | não | não | Prazo mínimo de entrega, já somado o preparo do vendedor. No frete calculado (`DYNAMIC`) vem igual ao máximo. Exemplo: `5`. | | `max_days` | inteiro | não | não | Prazo máximo de entrega, já somado o preparo do vendedor. Exemplo: `5`. | | `days_type` | enum | não | não | Se o prazo é contado em dias úteis ou em dias corridos. Valores: `BUSINESS_DAYS`, `CALENDAR_DAYS`. Exemplo: `BUSINESS_DAYS`. | --- # Opções de troca de plano URL: https://staging.pagpolar.com/docs/referencia/entidades/opcoes-de-troca-de-plano > Planos para os quais a assinatura pode ser trocada. Planos para os quais a assinatura pode ser trocada. ## Onde aparece * [`GET /subscriptions/{id}/plan-options`](/docs/referencia/assinaturas/list-subscription-plan-options) ## Campos Campos anuláveis chegam com `null`. Campos com opções fixas listam todos os valores. | Campo | Tipo | Obrigatório | Nulo | Descrição | | --- | --- | --- | --- | --- | | `allow_client_plan_change` | booleano | não | não | Exemplo: `true`. | | `current_product_price_id` | texto (uuid) | não | não | Exemplo: `d5e6f7a8-b9c0-4d1e-2f3a-4b5c6d7e8f9a`. | | `options` | [lista de Oferta](/docs/referencia/entidades/oferta) | não | não | Ofertas do mesmo produto disponíveis para troca. Inclui a oferta atual da assinatura mesmo se ela não estiver mais selecionável para novas trocas. | --- # Pedido de reembolso URL: https://staging.pagpolar.com/docs/referencia/entidades/pedido-de-reembolso > Corpo para pedir o reembolso de uma venda. Corpo para pedir o reembolso de uma venda. ## Onde é enviado * [`POST /refunds`](/docs/referencia/reembolsos/create-refund) ## Campos Campos anuláveis chegam com `null`. Campos com opções fixas listam todos os valores. | Campo | Tipo | Obrigatório | Nulo | Descrição | | --- | --- | --- | --- | --- | | `sale_identifier` | texto | sim | não | Código público da venda a reembolsar — o `identifier` que vem em `GET /sales`, na resposta da cobrança e no payload dos webhooks. São só dígitos; o prefixo e a URL do checkout que contenha o código também são aceitos. **Não** é o `id` (uuid) da venda: com o uuid a resposta é `404`. A venda precisa ser da própria conta da credencial. Exemplo: `PPO9876543210`. | | `requested_by` | enum | não | não | Quem pediu o reembolso, para o registro ficar fiel: `SELLER` quando a decisão foi sua e `CLIENT` quando o comprador pediu por fora (e-mail, telefone, atendimento). O campo só muda o registro, que volta em `requested_by` na consulta — o estorno é imediato nos dois casos, porque quem chama a rota é o vendedor. Valores: `SELLER`, `CLIENT`. Exemplo: `SELLER`. | | `reason` | texto | não | não | Motivo do reembolso, em texto livre. Fica gravado no pedido e aparece em `GET /refunds`. Exemplo: `Cliente desistiu da compra`. | | `customer_observation` | texto | não | não | Observação do comprador, quando houver. Exemplo: `Pediu o cancelamento por e-mail`. | --- # Prévia da troca de plano URL: https://staging.pagpolar.com/docs/referencia/entidades/previa-da-troca-de-plano > Cálculo da troca antes de executar: direção, crédito proporcional e valor a cobrar. Cálculo da troca antes de executar: direção, crédito proporcional e valor a cobrar. ## Onde aparece * [`POST /subscriptions/{id}/plan-change/preview`](/docs/referencia/assinaturas/preview-subscription-plan-change) ## Campos Campos anuláveis chegam com `null`. Campos com opções fixas listam todos os valores. | Campo | Tipo | Obrigatório | Nulo | Descrição | | --- | --- | --- | --- | --- | | `type` | enum | não | não | Valores: `UPGRADE`, `DOWNGRADE`. | | `current_product_price_id` | texto (uuid) | não | não | — | | `current_price` | número | não | não | — | | `new_product_price_id` | texto (uuid) | não | não | — | | `new_price` | número | não | não | — | | `total_days` | inteiro | não | não | — | | `remaining_days` | inteiro | não | não | — | | `prorated_credit` | número | não | não | — | | `charge_amount` | número | não | não | — | | `effective_at` | texto (date-time) | não | não | — | | `current_payment_method` | texto | não | não | — | --- # Produto URL: https://staging.pagpolar.com/docs/referencia/entidades/produto > Produto à venda: dados de exibição, tipo, garantia e categoria. Um plano de assinatura também é um produto, com `type` `SUBSCRIPTION`. Produto à venda: dados de exibição, tipo, garantia e categoria. Um plano de assinatura também é um produto, com `type` `SUBSCRIPTION`. ## Onde aparece * [`GET /products`](/docs/referencia/produtos/list-products) * [`POST /products`](/docs/referencia/produtos/create-product) * [`GET /products/{id}`](/docs/referencia/produtos/get-product) * [`PATCH /products/{id}`](/docs/referencia/produtos/update-product) * [`GET /plans`](/docs/referencia/planos/list-plans) * [`POST /plans`](/docs/referencia/planos/create-plan) * [`GET /plans/{id}`](/docs/referencia/planos/get-plan) * [`PATCH /plans/{id}`](/docs/referencia/planos/update-plan) Também aparece dentro de [Assinatura](/docs/referencia/entidades/assinatura), no campo `product`. ## Campos Campos anuláveis chegam com `null`. Campos com opções fixas listam todos os valores. | Campo | Tipo | Obrigatório | Nulo | Descrição | | --- | --- | --- | --- | --- | | `id` | texto (uuid) | não | não | Exemplo: `c4d5e6f7-a8b9-4c0d-1e2f-3a4b5c6d7e8f`. | | `name` | texto | não | não | Exemplo: `Curso de Marketing Digital`. | | `description` | texto | não | sim | Exemplo: `Aprenda a vender online do zero`. | | `author` | texto | não | sim | Exemplo: `João Silva`. | | `promotional_text` | texto | não | sim | Exemplo: `Oferta por tempo limitado`. | | `is_active` | booleano | não | não | Exemplo: `true`. | | `image` | texto | não | sim | null quando o produto não tem imagem cadastrada. Exemplo: `https://api.pagpolar.com/files/abc123.png`. | | `type` | enum | não | não | Tipo do produto. `PHYSICAL` pode aparecer em produtos criados no painel, mas produtos físicos ainda não são processados pela API: não há envio, frete nem rastreio. Valores: `PHYSICAL`, `DIGITAL`, `SUBSCRIPTION`, `PACKAGE`. Exemplo: `DIGITAL`. | | `content_type` | enum | não | não | Valores: `DEFAULT`, `EVENT_ONLINE`, `EVENT_IN_PERSON`, `EBOOK`, `COURSE`. Exemplo: `COURSE`. | | `warranty_time` | inteiro | não | não | Prazo de garantia em dias. Exemplo: `7`. | | `category` | objeto | não | sim | null quando o produto não tem categoria. | | `category.id` | texto (uuid) | não | não | — | | `category.name` | texto | não | não | Exemplo: `Cursos`. | | `created_at` | texto (date-time) | não | não | Exemplo: `2026-01-15T12:00:00.000Z`. | | `updated_at` | texto (date-time) | não | não | Exemplo: `2026-02-01T09:30:00.000Z`. | --- # Reembolso URL: https://staging.pagpolar.com/docs/referencia/entidades/reembolso > Pedido de reembolso de uma venda, total ou parcial, com o andamento e a venda de origem. Pedido de reembolso de uma venda, total ou parcial, com o andamento e a venda de origem. ## Onde aparece * [`GET /refunds`](/docs/referencia/reembolsos/list-refunds) * [`POST /refunds`](/docs/referencia/reembolsos/create-refund) ## Campos Campos anuláveis chegam com `null`. Campos com opções fixas listam todos os valores. | Campo | Tipo | Obrigatório | Nulo | Descrição | | --- | --- | --- | --- | --- | | `id` | texto (uuid) | não | não | — | | `status` | enum | não | não | PENDING aguarda o vendedor · ACCEPTED/ACCEPTED_BY_ADMIN aceito pelo vendedor, pelo admin ou automaticamente · REFUSED/REFUSED_BY_ADMIN recusado · WAITING_SEND, WAITING_TRACK_CODE e SENT são etapas da devolução do produto físico · REFUNDING estorno pedido ao gateway · REFUNDED estorno concluído · CANCELED pedido desfeito · FAILED o gateway recusou o estorno. Valores: `PENDING`, `ACCEPTED`, `ACCEPTED_BY_ADMIN`, `REFUSED`, `REFUSED_BY_ADMIN`, `WAITING_SEND`, `WAITING_TRACK_CODE`, `SENT`, `REFUNDING`, `REFUNDED`, `CANCELED`, `FAILED`. Exemplo: `REFUNDED`. | | `requested_by` | enum | não | não | Quem pediu o reembolso. `CLIENT` é o pedido do comprador — aberto por ele no painel ou informado por você em `POST /refunds`. `SELLER` é a decisão do vendedor, no painel ou pela API. Valores: `CLIENT`, `SELLER`. Exemplo: `CLIENT`. | | `is_partial` | booleano | não | não | `true` quando o reembolso é só de alguns itens. Exemplo: `false`. | | `refund_amount` | número | não | sim | Valor do reembolso parcial, em reais. `null` no reembolso total — vale o total da venda. Exemplo: `null`. | | `reason` | texto | não | sim | Motivo informado no pedido. Exemplo: `Não atendeu às expectativas`. | | `customer_observation` | texto | não | sim | Exemplo: `null`. | | `refused_reason` | texto | não | sim | Motivo da recusa, quando recusado. Exemplo: `null`. | | `canceled_reason` | texto | não | sim | Motivo do cancelamento, quando cancelado. Exemplo: `null`. | | `return_tracking` | objeto | não | sim | Rastreio da devolução do produto físico. `null` quando não há devolução. | | `return_tracking.code` | texto | não | não | Exemplo: `BR123456789BR`. | | `return_tracking.provider` | texto | não | sim | — | | `return_tracking.sent_at` | texto (date-time) | não | sim | — | | `return_tracking.received_at` | texto (date-time) | não | sim | — | | `sale` | objeto | não | não | — | | `sale.identifier` | texto | não | não | Exemplo: `PPO9876543210`. | | `sale.status` | texto | não | não | Exemplo: `REFUNDED`. | | `sale.payment_method` | texto | não | não | Exemplo: `PIX`. | | `sale.total_amount` | número | não | não | Exemplo: `197`. | | `customer` | [Cliente](/docs/referencia/entidades/cliente) | não | não | — | | `created_at` | texto (date-time) | não | não | Exemplo: `2026-01-25T09:00:00.000Z`. | | `updated_at` | texto (date-time) | não | não | Exemplo: `2026-01-27T16:20:00.000Z`. | --- # Resultado da cobrança URL: https://staging.pagpolar.com/docs/referencia/entidades/resultado-da-cobranca > Resposta da criação de uma cobrança, com os ids das vendas e os dados para o cliente pagar. Resposta da criação de uma cobrança, com os ids das vendas e os dados para o cliente pagar. ## Onde aparece * [`POST /payments/pix`](/docs/referencia/vendas/create-pix-payment) * [`POST /payments/boleto`](/docs/referencia/vendas/create-boleto-payment) * [`POST /payments/credit-card`](/docs/referencia/vendas/create-credit-card-payment) Para acompanhar a venda depois, use o primeiro id de `transactions` e a entidade [Venda](/docs/referencia/entidades/venda). ## Campos Campos anuláveis chegam com `null`. Campos com opções fixas listam todos os valores. | Campo | Tipo | Obrigatório | Nulo | Descrição | | --- | --- | --- | --- | --- | | `offer_identifier` | texto | sim | não | Código da oferta usada na venda — a informada, a reaproveitada ou a criada a partir de `offer`. Exemplo: `PPP1234567890`. | | `transactions` | lista de texto (uuid) | sim | não | Ids das vendas criadas. O primeiro é a venda principal; use em `GET /sales/{identifier}`. Exemplo: `["a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d"]`. | | `subscriptions` | lista de texto (uuid) | sim | não | Ids das assinaturas criadas. Vazio em venda avulsa. Exemplo: `[]`. | | `pix` | objeto | não | não | Só vem na cobrança PIX. | | `pix.qr_code` | texto | não | não | Código "copia e cola" para o cliente pagar. | | `boleto` | objeto | não | não | Só vem na cobrança por boleto. | | `boleto.barcode` | texto | não | não | Linha digitável. | | `boleto.pdf_link` | texto | não | não | Link do PDF do boleto. | --- # Resultado da operação URL: https://staging.pagpolar.com/docs/referencia/entidades/resultado-da-operacao > Confirmação de uma operação que não devolve outro dado. Confirmação de uma operação que não devolve outro dado. ## Onde aparece * [`DELETE /subscriptions/{id}`](/docs/referencia/assinaturas/cancel-subscription) * [`PATCH /subscriptions/{id}/card`](/docs/referencia/assinaturas/update-subscription-card) ## Campos Campos anuláveis chegam com `null`. Campos com opções fixas listam todos os valores. | Campo | Tipo | Obrigatório | Nulo | Descrição | | --- | --- | --- | --- | --- | | `success` | booleano | sim | não | Sempre `true` quando a operação foi aceita. Exemplo: `true`. | --- # Token de acesso URL: https://staging.pagpolar.com/docs/referencia/entidades/token-de-acesso > Token que autentica as chamadas da API. Vale 24 horas e vai no header Authorization. Token que autentica as chamadas da API. Vale 24 horas e vai no header Authorization. ## Onde aparece * [`POST /auth/token`](/docs/referencia/autenticacao/create-access-token) Veja como usar e renovar em [Autenticação](/docs/guias/fundamentos/autenticacao). ## Campos Campos anuláveis chegam com `null`. Campos com opções fixas listam todos os valores. | Campo | Tipo | Obrigatório | Nulo | Descrição | | --- | --- | --- | --- | --- | | `access_token` | texto | sim | não | Token de acesso (JWT). Envie em `Authorization: Bearer `. Exemplo: `eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJlNmY3YThiOS1jMGQxLTRlMmYtM2E0Yi01YzZkN2U4ZjlhMGIifQ.assinatura-do-token`. | | `token_type` | enum | sim | não | Sempre `Bearer`. Valores: `Bearer`. Exemplo: `Bearer`. | | `expires_in` | inteiro | sim | não | Segundos até o token expirar, contados a partir da emissão. `86400` são 24 horas. Exemplo: `86400`. | --- # Troca de plano URL: https://staging.pagpolar.com/docs/referencia/entidades/troca-de-plano > Resultado da troca executada: upgrade com cobrança da diferença ou downgrade agendado. Resultado da troca executada: upgrade com cobrança da diferença ou downgrade agendado. ## Onde aparece * [`POST /subscriptions/{id}/plan-change`](/docs/referencia/assinaturas/change-subscription-plan) ## Campos Campos anuláveis chegam com `null`. Campos com opções fixas listam todos os valores. | Campo | Tipo | Obrigatório | Nulo | Descrição | | --- | --- | --- | --- | --- | | `type` | enum | não | não | Valores: `UPGRADE`, `DOWNGRADE`. | | `upgrade` | objeto | não | sim | Presente apenas quando type=UPGRADE | | `upgrade.transaction_id` | texto (uuid) | não | não | — | | `upgrade.charge_amount` | número | não | não | — | | `upgrade.status` | enum | não | não | Valores: `paid`, `pending`, `failed`. | | `upgrade.pix` | objeto | não | sim | — | | `upgrade.pix.qr_code` | texto | não | não | — | | `upgrade.boleto` | objeto | não | sim | — | | `upgrade.boleto.barcode` | texto | não | não | — | | `upgrade.boleto.pdf_link` | texto | não | não | — | --- # Venda URL: https://staging.pagpolar.com/docs/referencia/entidades/venda > Uma cobrança: avulsa ou um ciclo de assinatura. Traz status, valores, itens e os dados para o cliente pagar. Uma cobrança: avulsa ou um ciclo de assinatura. Traz status, valores, itens e os dados para o cliente pagar. ## Onde aparece * [`GET /sales`](/docs/referencia/vendas/list-sales) * [`GET /sales/{identifier}`](/docs/referencia/vendas/get-sale) * [`GET /payments/{identifier}`](/docs/referencia/vendas/get-payment) Os valores em dinheiro vêm em **reais**, como número. ## Campos Campos anuláveis chegam com `null`. Campos com opções fixas listam todos os valores. | Campo | Tipo | Obrigatório | Nulo | Descrição | | --- | --- | --- | --- | --- | | `id` | texto (uuid) | não | não | Id da venda. É o mesmo valor que volta em `transactions` na criação do pagamento e pode ser usado em `GET /sales/{identifier}`. Exemplo: `a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d`. | | `identifier` | texto | não | não | Código da venda: prefixo `PPO` seguido de 10 dígitos. É o mesmo código do painel e do webhook. Exemplo: `PPO9876543210`. | | `external_reference` | texto | não | sim | Referência do pedido enviada por você na criação do pagamento ou da assinatura. `null` quando não foi enviada. Exemplo: `PED-2026-0001`. | | `status` | enum | não | não | Valores: `DRAFT`, `OPEN`, `PROCESSING`, `PAID`, `CANCELED`, `ASK_REFUND`, `REFUNDED`, `REFUNDING`, `ABANDONED`, `EXPIRED`, `FAILED`, `CHARGEBACK_REQUESTED`, `CHARGEBACK_APPROVED`. Exemplo: `PAID`. | | `type` | enum | não | não | Valores: `BILLING`, `TRANSFER`, `FEE`. Exemplo: `BILLING`. | | `payment_method` | enum | não | não | Valores: `CREDIT_CARD`, `PIX`, `BOLETO`, `APPLE_PAY`, `GOOGLE_PAY`. Exemplo: `PIX`. | | `installments` | inteiro | não | não | Exemplo: `1`. | | `total_amount` | número | não | não | Exemplo: `197`. | | `cycle` | inteiro | não | não | Posição do ciclo de cobrança para vendas recorrentes (não é a periodicidade — para isso veja Offer.cycle). 1 para venda avulsa. Exemplo: `1`. | | `paid_at` | texto (date-time) | não | sim | null enquanto a venda não é paga. Exemplo: `2026-01-20T14:32:10.000Z`. | | `refund_at` | texto (date-time) | não | sim | Exemplo: `null`. | | `created_at` | texto (date-time) | não | não | Exemplo: `2026-01-20T14:30:00.000Z`. | | `customer` | [Cliente](/docs/referencia/entidades/cliente) | não | não | — | | `items` | lista de objetos | não | não | — | | `items[].quantity` | inteiro | não | não | Exemplo: `1`. | | `items[].amount` | número | não | não | Exemplo: `197`. | | `items[].original_amount` | número | não | não | Exemplo: `197`. | | `items[].discount_value` | número | não | não | Exemplo: `0`. | | `items[].offer` | objeto | não | sim | — | | `items[].offer.id` | texto (uuid) | não | não | — | | `items[].offer.identifier` | texto | não | não | Código da oferta: prefixo `PPP` seguido de 10 dígitos. Exemplo: `PPP1234567890`. | | `items[].offer.title` | texto | não | não | Exemplo: `Plano Mensal`. | | `items[].product` | objeto | não | sim | — | | `items[].product.id` | texto (uuid) | não | não | — | | `items[].product.name` | texto | não | não | Exemplo: `Curso de Marketing Digital`. | | `items[].product.type` | texto | não | não | Exemplo: `DIGITAL`. | | `shipping` | objeto | não | sim | Frete cobrado e endereço de entrega. `null` quando a venda não tem entrega. | | `shipping.amount` | número | não | não | Valor do frete em reais, já somado ao total da venda. Exemplo: `32.9`. | | `shipping.option_name` | texto | não | sim | Nome da opção de frete escolhida na cobrança. Exemplo: `SEDEX`. | | `shipping.address` | objeto | não | sim | — | | `shipping.address.street` | texto | não | não | Exemplo: `Rua das Flores`. | | `shipping.address.number` | texto | não | não | Exemplo: `123`. | | `shipping.address.complement` | texto | não | sim | Exemplo: `Apto 4B`. | | `shipping.address.neighborhood` | texto | não | não | Exemplo: `Centro`. | | `shipping.address.city` | texto | não | não | Exemplo: `São Paulo`. | | `shipping.address.state` | texto | não | não | Exemplo: `SP`. | | `shipping.address.postal_code` | texto | não | não | Exemplo: `01311000`. | | `payment_details` | objeto | não | sim | null antes da venda ser processada (ex.: aguardando confirmação do gateway). | | `payment_details.qr_code` | texto | não | sim | Exemplo: `00020126...`. | | `payment_details.qr_code_expires_at` | texto (date-time) | não | sim | — | | `payment_details.billet_barcode` | texto | não | sim | Exemplo: `34191.79001 01043.510047 91020.150008 1 96610000015000`. | | `payment_details.billet_link` | texto | não | sim | Exemplo: `https://boletos.pagpolar.com/a1b2c3d4.pdf`. | | `payment_details.last_credit_card_digits` | texto | não | sim | Exemplo: `4242`. | --- # Ofertas URL: https://staging.pagpolar.com/docs/referencia/ofertas > Crie, consulte e altere as ofertas de um produto. Objeto principal: **[Oferta](/docs/referencia/entidades/oferta)**. | Campo | Tipo | Obrigatório | Nulo | Descrição | | --- | --- | --- | --- | --- | | `id` | texto (uuid) | não | não | Exemplo: `d5e6f7a8-b9c0-4d1e-2f3a-4b5c6d7e8f9a`. | | `identifier` | texto | não | não | Código da oferta: prefixo `PPP` seguido de 10 dígitos. Exemplo: `PPP1234567890`. | | `title` | texto | não | sim | Exemplo: `Plano Mensal`. | | `price` | número | não | não | Exemplo: `197.9`. | | `is_active` | booleano | não | não | Exemplo: `true`. | | `requires_shipping` | booleano | não | não | true quando o produto é físico: a cobrança exige `address` e `shipping_option_id`. Consulte as opções em `GET /offers/{identifier}/shipping`. Exemplo: `false`. | | `is_default` | booleano | não | não | Exemplo: `false`. | | `product_id` | texto (uuid) | não | não | Exemplo: `c4d5e6f7-a8b9-4c0d-1e2f-3a4b5c6d7e8f`. | | `payment_methods` | objeto | não | não | Meios de pagamento ligados. Cada meio só fica ligado se o preço atinge o mínimo dele: PIX R$ 5,00, cartão R$ 5,00 e boleto R$ 10,00. | | `payment_methods.pix` | booleano | não | não | Exemplo: `true`. | | `payment_methods.credit_card` | booleano | não | não | Exemplo: `true`. | | `payment_methods.billet` | booleano | não | não | Exemplo: `false`. | | `max_credit_card_installments` | inteiro | não | não | Exemplo: `12`. | | `cycle` | enum | não | sim | null para oferta avulsa (não recorrente). Preenchido só quando a oferta é de assinatura. Valores: `DAILY`, `WEEKLY`, `MONTHLY`, `YEARLY`. Exemplo: `MONTHLY`. | | `cycle_interval` | inteiro | não | sim | Exemplo: `1`. | | `cycle_interval_limit` | inteiro | não | sim | null quando a assinatura não tem limite de ciclos. Exemplo: `12`. | | `allow_purchase_quantity` | booleano | não | não | Exemplo: `false`. | | `purchase_quantity_limit` | inteiro | não | sim | Exemplo: `10`. | | `purchase_quantity_min` | inteiro | não | não | Exemplo: `1`. | | `expires_at` | texto (date-time) | não | sim | Exemplo: `null`. | | `created_at` | texto (date-time) | não | não | Exemplo: `2026-01-10T10:00:00.000Z`. | --- # Planos URL: https://staging.pagpolar.com/docs/referencia/planos > Crie planos de assinatura e as ofertas de cada plano. ## Produto As operações de plano (`/plans` e `/plans/{id}`) devolvem o plano como **[Produto](/docs/referencia/entidades/produto)**. | Campo | Tipo | Obrigatório | Nulo | Descrição | | --- | --- | --- | --- | --- | | `id` | texto (uuid) | não | não | Exemplo: `c4d5e6f7-a8b9-4c0d-1e2f-3a4b5c6d7e8f`. | | `name` | texto | não | não | Exemplo: `Curso de Marketing Digital`. | | `description` | texto | não | sim | Exemplo: `Aprenda a vender online do zero`. | | `author` | texto | não | sim | Exemplo: `João Silva`. | | `promotional_text` | texto | não | sim | Exemplo: `Oferta por tempo limitado`. | | `is_active` | booleano | não | não | Exemplo: `true`. | | `image` | texto | não | sim | null quando o produto não tem imagem cadastrada. Exemplo: `https://api.pagpolar.com/files/abc123.png`. | | `type` | enum | não | não | Tipo do produto. `PHYSICAL` pode aparecer em produtos criados no painel, mas produtos físicos ainda não são processados pela API: não há envio, frete nem rastreio. Valores: `PHYSICAL`, `DIGITAL`, `SUBSCRIPTION`, `PACKAGE`. Exemplo: `DIGITAL`. | | `content_type` | enum | não | não | Valores: `DEFAULT`, `EVENT_ONLINE`, `EVENT_IN_PERSON`, `EBOOK`, `COURSE`. Exemplo: `COURSE`. | | `warranty_time` | inteiro | não | não | Prazo de garantia em dias. Exemplo: `7`. | | `category` | objeto | não | sim | null quando o produto não tem categoria. | | `category.id` | texto (uuid) | não | não | — | | `category.name` | texto | não | não | Exemplo: `Cursos`. | | `created_at` | texto (date-time) | não | não | Exemplo: `2026-01-15T12:00:00.000Z`. | | `updated_at` | texto (date-time) | não | não | Exemplo: `2026-02-01T09:30:00.000Z`. | ## Oferta As operações de oferta de plano (`/plans/{id}/offers` e `/plan-offers/{id}`) devolvem **[Oferta](/docs/referencia/entidades/oferta)**. | Campo | Tipo | Obrigatório | Nulo | Descrição | | --- | --- | --- | --- | --- | | `id` | texto (uuid) | não | não | Exemplo: `d5e6f7a8-b9c0-4d1e-2f3a-4b5c6d7e8f9a`. | | `identifier` | texto | não | não | Código da oferta: prefixo `PPP` seguido de 10 dígitos. Exemplo: `PPP1234567890`. | | `title` | texto | não | sim | Exemplo: `Plano Mensal`. | | `price` | número | não | não | Exemplo: `197.9`. | | `is_active` | booleano | não | não | Exemplo: `true`. | | `requires_shipping` | booleano | não | não | true quando o produto é físico: a cobrança exige `address` e `shipping_option_id`. Consulte as opções em `GET /offers/{identifier}/shipping`. Exemplo: `false`. | | `is_default` | booleano | não | não | Exemplo: `false`. | | `product_id` | texto (uuid) | não | não | Exemplo: `c4d5e6f7-a8b9-4c0d-1e2f-3a4b5c6d7e8f`. | | `payment_methods` | objeto | não | não | Meios de pagamento ligados. Cada meio só fica ligado se o preço atinge o mínimo dele: PIX R$ 5,00, cartão R$ 5,00 e boleto R$ 10,00. | | `payment_methods.pix` | booleano | não | não | Exemplo: `true`. | | `payment_methods.credit_card` | booleano | não | não | Exemplo: `true`. | | `payment_methods.billet` | booleano | não | não | Exemplo: `false`. | | `max_credit_card_installments` | inteiro | não | não | Exemplo: `12`. | | `cycle` | enum | não | sim | null para oferta avulsa (não recorrente). Preenchido só quando a oferta é de assinatura. Valores: `DAILY`, `WEEKLY`, `MONTHLY`, `YEARLY`. Exemplo: `MONTHLY`. | | `cycle_interval` | inteiro | não | sim | Exemplo: `1`. | | `cycle_interval_limit` | inteiro | não | sim | null quando a assinatura não tem limite de ciclos. Exemplo: `12`. | | `allow_purchase_quantity` | booleano | não | não | Exemplo: `false`. | | `purchase_quantity_limit` | inteiro | não | sim | Exemplo: `10`. | | `purchase_quantity_min` | inteiro | não | não | Exemplo: `1`. | | `expires_at` | texto (date-time) | não | sim | Exemplo: `null`. | | `created_at` | texto (date-time) | não | não | Exemplo: `2026-01-10T10:00:00.000Z`. | --- # Produtos URL: https://staging.pagpolar.com/docs/referencia/produtos > Crie, liste e altere produtos. Objeto principal: **[Produto](/docs/referencia/entidades/produto)**. | Campo | Tipo | Obrigatório | Nulo | Descrição | | --- | --- | --- | --- | --- | | `id` | texto (uuid) | não | não | Exemplo: `c4d5e6f7-a8b9-4c0d-1e2f-3a4b5c6d7e8f`. | | `name` | texto | não | não | Exemplo: `Curso de Marketing Digital`. | | `description` | texto | não | sim | Exemplo: `Aprenda a vender online do zero`. | | `author` | texto | não | sim | Exemplo: `João Silva`. | | `promotional_text` | texto | não | sim | Exemplo: `Oferta por tempo limitado`. | | `is_active` | booleano | não | não | Exemplo: `true`. | | `image` | texto | não | sim | null quando o produto não tem imagem cadastrada. Exemplo: `https://api.pagpolar.com/files/abc123.png`. | | `type` | enum | não | não | Tipo do produto. `PHYSICAL` pode aparecer em produtos criados no painel, mas produtos físicos ainda não são processados pela API: não há envio, frete nem rastreio. Valores: `PHYSICAL`, `DIGITAL`, `SUBSCRIPTION`, `PACKAGE`. Exemplo: `DIGITAL`. | | `content_type` | enum | não | não | Valores: `DEFAULT`, `EVENT_ONLINE`, `EVENT_IN_PERSON`, `EBOOK`, `COURSE`. Exemplo: `COURSE`. | | `warranty_time` | inteiro | não | não | Prazo de garantia em dias. Exemplo: `7`. | | `category` | objeto | não | sim | null quando o produto não tem categoria. | | `category.id` | texto (uuid) | não | não | — | | `category.name` | texto | não | não | Exemplo: `Cursos`. | | `created_at` | texto (date-time) | não | não | Exemplo: `2026-01-15T12:00:00.000Z`. | | `updated_at` | texto (date-time) | não | não | Exemplo: `2026-02-01T09:30:00.000Z`. | --- # Reembolsos URL: https://staging.pagpolar.com/docs/referencia/reembolsos > Liste os pedidos de reembolso e peça o reembolso de uma venda. Objeto principal: **[Reembolso](/docs/referencia/entidades/reembolso)**. | Campo | Tipo | Obrigatório | Nulo | Descrição | | --- | --- | --- | --- | --- | | `id` | texto (uuid) | não | não | — | | `status` | enum | não | não | PENDING aguarda o vendedor · ACCEPTED/ACCEPTED_BY_ADMIN aceito pelo vendedor, pelo admin ou automaticamente · REFUSED/REFUSED_BY_ADMIN recusado · WAITING_SEND, WAITING_TRACK_CODE e SENT são etapas da devolução do produto físico · REFUNDING estorno pedido ao gateway · REFUNDED estorno concluído · CANCELED pedido desfeito · FAILED o gateway recusou o estorno. Valores: `PENDING`, `ACCEPTED`, `ACCEPTED_BY_ADMIN`, `REFUSED`, `REFUSED_BY_ADMIN`, `WAITING_SEND`, `WAITING_TRACK_CODE`, `SENT`, `REFUNDING`, `REFUNDED`, `CANCELED`, `FAILED`. Exemplo: `REFUNDED`. | | `requested_by` | enum | não | não | Quem pediu o reembolso. `CLIENT` é o pedido do comprador — aberto por ele no painel ou informado por você em `POST /refunds`. `SELLER` é a decisão do vendedor, no painel ou pela API. Valores: `CLIENT`, `SELLER`. Exemplo: `CLIENT`. | | `is_partial` | booleano | não | não | `true` quando o reembolso é só de alguns itens. Exemplo: `false`. | | `refund_amount` | número | não | sim | Valor do reembolso parcial, em reais. `null` no reembolso total — vale o total da venda. Exemplo: `null`. | | `reason` | texto | não | sim | Motivo informado no pedido. Exemplo: `Não atendeu às expectativas`. | | `customer_observation` | texto | não | sim | Exemplo: `null`. | | `refused_reason` | texto | não | sim | Motivo da recusa, quando recusado. Exemplo: `null`. | | `canceled_reason` | texto | não | sim | Motivo do cancelamento, quando cancelado. Exemplo: `null`. | | `return_tracking` | objeto | não | sim | Rastreio da devolução do produto físico. `null` quando não há devolução. | | `return_tracking.code` | texto | não | não | Exemplo: `BR123456789BR`. | | `return_tracking.provider` | texto | não | sim | — | | `return_tracking.sent_at` | texto (date-time) | não | sim | — | | `return_tracking.received_at` | texto (date-time) | não | sim | — | | `sale` | objeto | não | não | — | | `sale.identifier` | texto | não | não | Exemplo: `PPO9876543210`. | | `sale.status` | texto | não | não | Exemplo: `REFUNDED`. | | `sale.payment_method` | texto | não | não | Exemplo: `PIX`. | | `sale.total_amount` | número | não | não | Exemplo: `197`. | | `customer` | [Cliente](/docs/referencia/entidades/cliente) | não | não | — | | `created_at` | texto (date-time) | não | não | Exemplo: `2026-01-25T09:00:00.000Z`. | | `updated_at` | texto (date-time) | não | não | Exemplo: `2026-01-27T16:20:00.000Z`. | --- # Vendas URL: https://staging.pagpolar.com/docs/referencia/vendas > Cobre por PIX, boleto ou cartão e consulte as vendas. Objeto principal: **[Venda](/docs/referencia/entidades/venda)**. | Campo | Tipo | Obrigatório | Nulo | Descrição | | --- | --- | --- | --- | --- | | `id` | texto (uuid) | não | não | Id da venda. É o mesmo valor que volta em `transactions` na criação do pagamento e pode ser usado em `GET /sales/{identifier}`. Exemplo: `a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d`. | | `identifier` | texto | não | não | Código da venda: prefixo `PPO` seguido de 10 dígitos. É o mesmo código do painel e do webhook. Exemplo: `PPO9876543210`. | | `external_reference` | texto | não | sim | Referência do pedido enviada por você na criação do pagamento ou da assinatura. `null` quando não foi enviada. Exemplo: `PED-2026-0001`. | | `status` | enum | não | não | Valores: `DRAFT`, `OPEN`, `PROCESSING`, `PAID`, `CANCELED`, `ASK_REFUND`, `REFUNDED`, `REFUNDING`, `ABANDONED`, `EXPIRED`, `FAILED`, `CHARGEBACK_REQUESTED`, `CHARGEBACK_APPROVED`. Exemplo: `PAID`. | | `type` | enum | não | não | Valores: `BILLING`, `TRANSFER`, `FEE`. Exemplo: `BILLING`. | | `payment_method` | enum | não | não | Valores: `CREDIT_CARD`, `PIX`, `BOLETO`, `APPLE_PAY`, `GOOGLE_PAY`. Exemplo: `PIX`. | | `installments` | inteiro | não | não | Exemplo: `1`. | | `total_amount` | número | não | não | Exemplo: `197`. | | `cycle` | inteiro | não | não | Posição do ciclo de cobrança para vendas recorrentes (não é a periodicidade — para isso veja Offer.cycle). 1 para venda avulsa. Exemplo: `1`. | | `paid_at` | texto (date-time) | não | sim | null enquanto a venda não é paga. Exemplo: `2026-01-20T14:32:10.000Z`. | | `refund_at` | texto (date-time) | não | sim | Exemplo: `null`. | | `created_at` | texto (date-time) | não | não | Exemplo: `2026-01-20T14:30:00.000Z`. | | `customer` | [Cliente](/docs/referencia/entidades/cliente) | não | não | — | | `items` | lista de objetos | não | não | — | | `items[].quantity` | inteiro | não | não | Exemplo: `1`. | | `items[].amount` | número | não | não | Exemplo: `197`. | | `items[].original_amount` | número | não | não | Exemplo: `197`. | | `items[].discount_value` | número | não | não | Exemplo: `0`. | | `items[].offer` | objeto | não | sim | — | | `items[].offer.id` | texto (uuid) | não | não | — | | `items[].offer.identifier` | texto | não | não | Código da oferta: prefixo `PPP` seguido de 10 dígitos. Exemplo: `PPP1234567890`. | | `items[].offer.title` | texto | não | não | Exemplo: `Plano Mensal`. | | `items[].product` | objeto | não | sim | — | | `items[].product.id` | texto (uuid) | não | não | — | | `items[].product.name` | texto | não | não | Exemplo: `Curso de Marketing Digital`. | | `items[].product.type` | texto | não | não | Exemplo: `DIGITAL`. | | `shipping` | objeto | não | sim | Frete cobrado e endereço de entrega. `null` quando a venda não tem entrega. | | `shipping.amount` | número | não | não | Valor do frete em reais, já somado ao total da venda. Exemplo: `32.9`. | | `shipping.option_name` | texto | não | sim | Nome da opção de frete escolhida na cobrança. Exemplo: `SEDEX`. | | `shipping.address` | objeto | não | sim | — | | `shipping.address.street` | texto | não | não | Exemplo: `Rua das Flores`. | | `shipping.address.number` | texto | não | não | Exemplo: `123`. | | `shipping.address.complement` | texto | não | sim | Exemplo: `Apto 4B`. | | `shipping.address.neighborhood` | texto | não | não | Exemplo: `Centro`. | | `shipping.address.city` | texto | não | não | Exemplo: `São Paulo`. | | `shipping.address.state` | texto | não | não | Exemplo: `SP`. | | `shipping.address.postal_code` | texto | não | não | Exemplo: `01311000`. | | `payment_details` | objeto | não | sim | null antes da venda ser processada (ex.: aguardando confirmação do gateway). | | `payment_details.qr_code` | texto | não | sim | Exemplo: `00020126...`. | | `payment_details.qr_code_expires_at` | texto (date-time) | não | sim | — | | `payment_details.billet_barcode` | texto | não | sim | Exemplo: `34191.79001 01043.510047 91020.150008 1 96610000015000`. | | `payment_details.billet_link` | texto | não | sim | Exemplo: `https://boletos.pagpolar.com/a1b2c3d4.pdf`. | | `payment_details.last_credit_card_digits` | texto | não | sim | Exemplo: `4242`. | --- # Catálogo de eventos URL: https://staging.pagpolar.com/docs/webhooks/eventos > Encontre o evento certo para cada situação de venda ou assinatura e veja o que fazer ao receber cada um. A PagPolar envia 15 eventos. Cada página de evento mostra quando ele é enviado, o que fazer, os cuidados e um exemplo completo do payload. Antes de tratar os eventos, leia [Formato do evento](/docs/webhooks/formato-do-evento) e [Processar eventos sem duplicar](/docs/webhooks/processar-sem-duplicar). ## Eventos de venda | Evento | Quando é enviado | O que fazer | | ------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------- | ------------------------------------- | | [`TRANSACTION_CREATED`](/docs/webhooks/eventos/transaction-created) | Uma venda foi registrada, no checkout ou pela API. | Registre a venda. Não libere nada. | | [`TRANSACTION_PENDING`](/docs/webhooks/eventos/transaction-pending) | A cobrança de renovação de uma assinatura em PIX ou boleto foi gerada. | Mostre a nova cobrança ao cliente. | | [`TRANSACTION_PAID`](/docs/webhooks/eventos/transaction-paid) | O pagamento foi confirmado. | Libere o que foi vendido. | | [`TRANSACTION_EXPIRED`](/docs/webhooks/eventos/transaction-expired) | Um PIX ou boleto venceu sem pagamento. | Marque o pedido como não pago. | | [`TRANSACTION_CANCELED`](/docs/webhooks/eventos/transaction-canceled) | A venda foi cancelada sem ter sido paga. | Cancele o pedido. | | [`TRANSACTION_ASK_REFUNDING`](/docs/webhooks/eventos/transaction-ask-refunding) | O cliente pediu reembolso. O dinheiro ainda não voltou. | Registre o pedido e espere o estorno. | | [`TRANSACTION_REFUNDED`](/docs/webhooks/eventos/transaction-refunded) | O estorno foi concluído. | Revogue o acesso. | | [`TRANSACTION_CHARGEBACK_APPROVED`](/docs/webhooks/eventos/transaction-chargeback-approved) | O banco do cliente aprovou uma contestação. | Revogue o acesso. | ## Eventos de assinatura | Evento | Quando é enviado | O que fazer | | ------------------------------------------------------------------------- | -------------------------------------------------------------------- | ------------------------------------------------------------------------------------- | | [`SUBSCRIPTION_CREATED`](/docs/webhooks/eventos/subscription-created) | Uma assinatura foi registrada. | Registre a assinatura. Não libere o acesso. | | [`SUBSCRIPTION_CONFIRMED`](/docs/webhooks/eventos/subscription-confirmed) | O gateway aceitou a assinatura no cartão. O status continua `DRAFT`. | Guarde o `external_id`. Espere o pagamento. | | [`SUBSCRIPTION_FAILED`](/docs/webhooks/eventos/subscription-failed) | O gateway recusou a assinatura no cartão. | Peça outro cartão. | | [`SUBSCRIPTION_RENEWED`](/docs/webhooks/eventos/subscription-renewed) | Um ciclo do cartão, a partir do segundo, foi pago. | Mantenha o acesso. | | [`SUBSCRIPTION_DELAYED`](/docs/webhooks/eventos/subscription-delayed) | A renovação em PIX ou boleto está atrasada, dentro da carência. | Lembre o cliente de pagar. | | [`SUBSCRIPTION_EXPIRED`](/docs/webhooks/eventos/subscription-expired) | A assinatura em PIX ou boleto passou da carência sem pagar. | Revogue o acesso. | | [`SUBSCRIPTION_CANCELED`](/docs/webhooks/eventos/subscription-canceled) | O cancelamento foi efetivado. | Revogue o acesso. Se precisar da data, confira `end_at` em `GET /subscriptions/{id}`. | ## Eventos só de assinaturas em PIX ou boleto Pela API, a assinatura é sempre no cartão. Os eventos de PIX ou boleto das tabelas acima (`TRANSACTION_PENDING`, `SUBSCRIPTION_DELAYED` e `SUBSCRIPTION_EXPIRED`) só chegam para as assinaturas vendidas no checkout da PagPolar. ## Situações que não geram evento | Situação | O que chega | Como acompanhar | | ---------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- | | Cartão recusado | Só `TRANSACTION_CREATED`. Ele chega com `status: FAILED` se a recusa veio antes do envio. Se a recusa vier depois, a mudança para `FAILED` não gera evento, nem `TRANSACTION_CANCELED` | Leia o `status` do `TRANSACTION_CREATED`. Se a venda continuar `PROCESSING`, consulte `GET /sales/{identifier}`. | | Pedido de cancelamento de assinatura no cartão (`CANCELING`) | Nada, até o gateway confirmar | Espere `SUBSCRIPTION_CANCELED`. | | Troca do cartão de uma assinatura | Nada | Resposta de `PATCH /subscriptions/{id}/card`. | | Reembolso de parte dos itens, quando ainda restam itens na venda | Nada. A venda volta para `PAID`. | Consulte `GET /refunds`. | | Pedido de reembolso aberto pelo vendedor ou pelo suporte | Nada de `TRANSACTION_ASK_REFUNDING` | Espere `TRANSACTION_REFUNDED`. | | Renovação paga de assinatura em PIX ou boleto | `TRANSACTION_PAID`, sem `SUBSCRIPTION_RENEWED` | Use o `TRANSACTION_PAID` com `subscription` preenchida. | | Vendas de uma assinatura recusada que ficam `FAILED` | `TRANSACTION_CREATED` na criação e depois `SUBSCRIPTION_FAILED`; a mudança da venda para `FAILED` não gera evento | Trate a assinatura inteira como recusada ao receber `SUBSCRIPTION_FAILED`. | --- # Obter o token de acesso URL: https://staging.pagpolar.com/docs/referencia/autenticacao/create-access-token > Troca a chave da credencial por um **token de acesso**. É a primeira chamada de qualquer integração: todas as outras rotas exigem esse token. Envie a chave no header `X-API-Key`. A resposta traz o token, que vale **24 horas**. Nas demais rotas, envie `Authorization: Bearer `. Não existe rota de renovação: quando o token expirar, chame esta rota de novo com a mesma chave. Guarde o token em memória e reaproveite enquanto valer — não peça um token por requisição. A chave continua sendo o segredo da integração: ela só trafega nesta rota. `POST /auth/token` ## Autenticação - Chave de API no header `X-API-Key` (esquema `ApiKeyAuth`). Chave da credencial. Usada apenas em POST /auth/token, para obter o token de acesso. ## Respostas ### 200 — Token de acesso emitido | Campo | Tipo | Obrigatório | Descrição | | --- | --- | --- | --- | | `data` | [Token de acesso](/docs/referencia/entidades/token-de-acesso) | não | — | #### Exemplo ```json { "data": { "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJlNmY3YThiOS1jMGQxLTRlMmYtM2E0Yi01YzZkN2U4ZjlhMGIifQ.assinatura-do-token", "token_type": "Bearer", "expires_in": 86400 } } ``` ## Erros | Status | Descrição | | --- | --- | | `401` | Header X-API-Key ausente, chave inválida, revogada ou expirada | | `403` | IP não autorizado para esta credencial | Lista completa de códigos e como tratá-los: [Erros](/docs/guias/fundamentos/erros). --- # Dados da credencial autenticada URL: https://staging.pagpolar.com/docs/referencia/autenticacao/get-current-credential > Retorna o ambiente da credencial (PRODUCTION ou STAGING) e o limite de requisições por minuto. Útil para validar a integração. `GET /me` ## Autenticação - Token Bearer no header `Authorization` (esquema `BearerAuth`). Token de acesso devolvido por POST /auth/token. Envie como "Authorization: Bearer ". Vale 24 horas. ## Respostas ### 200 — Dados da credencial | Campo | Tipo | Obrigatório | Descrição | | --- | --- | --- | --- | | `data` | [Credencial](/docs/referencia/entidades/credencial) | não | — | #### Exemplo ```json { "data": { "credential_id": "e6f7a8b9-c0d1-4e2f-3a4b-5c6d7e8f9a0b", "environment": "PRODUCTION", "rate_limit_per_minute": 120 } } ``` ## Erros | Status | Descrição | | --- | --- | | `401` | Token ausente, inválido ou expirado, ou credencial revogada ou expirada. Chame POST /auth/token para obter um token novo. | | `403` | IP não autorizado | | `429` | Limite de requisições excedido | Lista completa de códigos e como tratá-los: [Erros](/docs/guias/fundamentos/erros). --- # Listar produtos URL: https://staging.pagpolar.com/docs/referencia/produtos/list-products `GET /products` ## Autenticação - Token Bearer no header `Authorization` (esquema `BearerAuth`). Token de acesso devolvido por POST /auth/token. Envie como "Authorization: Bearer ". Vale 24 horas. ## Parâmetros de consulta | Parâmetro | Tipo | Obrigatório | Descrição | | --- | --- | --- | --- | | `page` | inteiro | não | Número da página, começando em 1. | | `per_page` | inteiro | não | Itens por página, de 1 a 100. | | `is_active` | booleano | não | `true` lista só os ativos; `false`, só os inativos. Sem o filtro, lista os dois. | | `name` | texto | não | Nome do produto. Busca por parte do texto, sem diferenciar maiúsculas de minúsculas. | | `type` | enum | não | Tipo do produto. Planos (`SUBSCRIPTION`) não aparecem aqui: use `GET /plans`. Valores: `DIGITAL`, `PHYSICAL`, `PACKAGE`. | ## Respostas ### 200 — Lista de produtos | Campo | Tipo | Obrigatório | Descrição | | --- | --- | --- | --- | | `data` | [lista de Produto](/docs/referencia/entidades/produto) | não | — | | `meta` | objeto | não | — | | `meta.page` | inteiro | não | Exemplo: `1`. | | `meta.per_page` | inteiro | não | Exemplo: `25`. | | `meta.total` | inteiro | não | Exemplo: `143`. | | `meta.total_pages` | inteiro | não | Exemplo: `6`. | ## Erros | Status | Descrição | | --- | --- | | `401` | Token ausente, inválido ou expirado, ou credencial revogada ou expirada. Chame POST /auth/token para obter um token novo. | | `403` | IP não autorizado | | `429` | Limite de requisições excedido | Lista completa de códigos e como tratá-los: [Erros](/docs/guias/fundamentos/erros). --- # Criar produto URL: https://staging.pagpolar.com/docs/referencia/produtos/create-product > Cria um produto **digital** (`type` sempre `DIGITAL` — outros tipos não são criáveis pela API pública nesta versão). **Produtos físicos ainda não são processados pela API.** Não há envio, frete nem código de rastreio por aqui: use a API só para produtos digitais e assinaturas. O produto é criado sem imagem e sem categoria — ambos podem ser preenchidos depois pelo painel. `warranty_time` (garantia, em dias) também não é aceito no corpo: é resolvido automaticamente a partir da configuração mínima de garantia da sua conta. Este endpoint só cria o produto. Para vender, crie também uma oferta (preço) para ele — consulte a documentação da rota de ofertas. `POST /products` ## Autenticação - Token Bearer no header `Authorization` (esquema `BearerAuth`). Token de acesso devolvido por POST /auth/token. Envie como "Authorization: Bearer ". Vale 24 horas. ## Corpo da requisição Content-type: `application/json`. | Campo | Tipo | Obrigatório | Descrição | | --- | --- | --- | --- | | `name` | texto | sim | Exemplo: `Curso de Marketing Digital`. | | `description` | texto | não | Exemplo: `Aprenda a vender online do zero`. Pode ser nulo. | ### Exemplo do corpo Produto digital: ```json { "name": "Curso de Marketing Digital", "description": "Aprenda a vender online do zero" } ``` ## Respostas ### 201 — Produto criado | Campo | Tipo | Obrigatório | Descrição | | --- | --- | --- | --- | | `data` | [Produto](/docs/referencia/entidades/produto) | não | — | #### Exemplo default: ```json { "data": { "id": "c4d5e6f7-a8b9-4c0d-1e2f-3a4b5c6d7e8f", "name": "Curso de Marketing Digital", "description": "Aprenda a vender online do zero", "author": null, "promotional_text": null, "is_active": true, "image": null, "type": "DIGITAL", "content_type": "DEFAULT", "warranty_time": 7, "category": null, "created_at": "2026-01-15T12:00:00.000Z", "updated_at": "2026-01-15T12:00:00.000Z" } } ``` ## Erros | Status | Descrição | | --- | --- | | `400` | Dados inválidos | | `401` | Token ausente, inválido ou expirado, ou credencial revogada ou expirada. Chame POST /auth/token para obter um token novo. | | `403` | IP não autorizado | | `429` | Limite de requisições excedido | Lista completa de códigos e como tratá-los: [Erros](/docs/guias/fundamentos/erros). --- # Consultar produto URL: https://staging.pagpolar.com/docs/referencia/produtos/get-product `GET /products/{id}` ## Autenticação - Token Bearer no header `Authorization` (esquema `BearerAuth`). Token de acesso devolvido por POST /auth/token. Envie como "Authorization: Bearer ". Vale 24 horas. ## Parâmetros de caminho | Parâmetro | Tipo | Obrigatório | Descrição | | --- | --- | --- | --- | | `id` | texto (uuid) | sim | — | ## Respostas ### 200 — Produto | Campo | Tipo | Obrigatório | Descrição | | --- | --- | --- | --- | | `data` | [Produto](/docs/referencia/entidades/produto) | não | — | ## Erros | Status | Descrição | | --- | --- | | `401` | Token ausente, inválido ou expirado, ou credencial revogada ou expirada. Chame POST /auth/token para obter um token novo. | | `403` | IP não autorizado | | `404` | Produto não encontrado | | `429` | Limite de requisições excedido | Lista completa de códigos e como tratá-los: [Erros](/docs/guias/fundamentos/erros). --- # Editar produto URL: https://staging.pagpolar.com/docs/referencia/produtos/update-product > Edita campos básicos de um produto já criado. Só `name`, `description` e `is_active` podem ser alterados por aqui — envie apenas os campos que deseja atualizar (edição parcial). Não é possível trocar `image`, `category`, `warranty_time` ou `type` por esta rota; esses campos continuam editáveis apenas pelo painel. O corpo não pode vir vazio: pelo menos um dos três campos precisa ser enviado. `PATCH /products/{id}` ## Autenticação - Token Bearer no header `Authorization` (esquema `BearerAuth`). Token de acesso devolvido por POST /auth/token. Envie como "Authorization: Bearer ". Vale 24 horas. ## Parâmetros de caminho | Parâmetro | Tipo | Obrigatório | Descrição | | --- | --- | --- | --- | | `id` | texto (uuid) | sim | — | ## Corpo da requisição Content-type: `application/json`. | Campo | Tipo | Obrigatório | Descrição | | --- | --- | --- | --- | | `name` | texto | não | Exemplo: `Curso de Marketing Digital`. | | `description` | texto | não | Exemplo: `Aprenda a vender online do zero`. Pode ser nulo. | | `is_active` | booleano | não | Exemplo: `true`. | ### Exemplo do corpo Editar nome e descrição: ```json { "name": "Curso de Marketing Digital", "description": "Aprenda a vender online do zero" } ``` Desativar produto: ```json { "is_active": false } ``` ## Respostas ### 200 — Produto atualizado | Campo | Tipo | Obrigatório | Descrição | | --- | --- | --- | --- | | `data` | [Produto](/docs/referencia/entidades/produto) | não | — | #### Exemplo default: ```json { "data": { "id": "c4d5e6f7-a8b9-4c0d-1e2f-3a4b5c6d7e8f", "name": "Curso de Marketing Digital", "description": "Aprenda a vender online do zero", "author": null, "promotional_text": null, "is_active": true, "image": null, "type": "DIGITAL", "content_type": "DEFAULT", "warranty_time": 7, "category": null, "created_at": "2026-01-15T12:00:00.000Z", "updated_at": "2026-01-15T12:00:00.000Z" } } ``` ## Erros | Status | Descrição | | --- | --- | | `400` | Dados inválidos ou corpo vazio | | `401` | Token ausente, inválido ou expirado, ou credencial revogada ou expirada. Chame POST /auth/token para obter um token novo. | | `403` | IP não autorizado | | `404` | Produto não encontrado | | `429` | Limite de requisições excedido | Lista completa de códigos e como tratá-los: [Erros](/docs/guias/fundamentos/erros). --- # Criar oferta URL: https://staging.pagpolar.com/docs/referencia/ofertas/create-offer > Cria uma oferta (preço) avulsa para um produto já existente do seu catálogo. `product_id` precisa pertencer à sua conta — produtos de outro whitelabel retornam 404. Esta rota **não cria assinatura**: `cycle`/`cycle_interval` não são aceitos aqui. Toda oferta criada pela API pública é de cobrança avulsa. Limites de quantidade de compra, texto promocional, cobrança de taxas do comprador e ajuste de taxa de parcelamento também não são aceitos nesta versão — configure-os pelo painel, se necessário. `is_default` não pode ser definido pelo corpo: a primeira oferta criada para um produto é marcada automaticamente como padrão pela plataforma. `POST /offers` ## Autenticação - Token Bearer no header `Authorization` (esquema `BearerAuth`). Token de acesso devolvido por POST /auth/token. Envie como "Authorization: Bearer ". Vale 24 horas. ## Corpo da requisição Content-type: `application/json`. | Campo | Tipo | Obrigatório | Descrição | | --- | --- | --- | --- | | `product_id` | texto (uuid) | sim | Exemplo: `c4d5e6f7-a8b9-4c0d-1e2f-3a4b5c6d7e8f`. | | `price` | inteiro | sim | Preço da oferta em centavos, número inteiro — a mesma unidade de offer.value. 4990 = R$ 49,90. Valor com casas decimais retorna 400. Exemplo: `19790`. | | `title` | texto | não | Exemplo: `Plano Mensal`. Pode ser nulo. | | `is_active` | booleano | não | Exemplo: `true`. | | `is_enabled_pix` | booleano | não | PIX. Mínimo de R$ 5,00. Os três meios começam ligados, mas cada um só fica ligado se o preço atinge o mínimo dele: PIX R$ 5,00, cartão R$ 5,00 e boleto R$ 10,00. Abaixo do mínimo, o meio é desligado ao salvar, mesmo enviando `true`. Envie `false` para desligar um meio acima do mínimo. Na edição sem `price`, só os meios enviados são recalculados; com `price`, os três são recalculados. Exemplo: `true`. | | `is_enabled_credit_card` | booleano | não | Cartão de crédito. Mínimo de R$ 5,00. Os três meios começam ligados, mas cada um só fica ligado se o preço atinge o mínimo dele: PIX R$ 5,00, cartão R$ 5,00 e boleto R$ 10,00. Abaixo do mínimo, o meio é desligado ao salvar, mesmo enviando `true`. Envie `false` para desligar um meio acima do mínimo. Na edição sem `price`, só os meios enviados são recalculados; com `price`, os três são recalculados. Exemplo: `true`. | | `is_enabled_billet` | booleano | não | Boleto. Mínimo de R$ 10,00. Os três meios começam ligados, mas cada um só fica ligado se o preço atinge o mínimo dele: PIX R$ 5,00, cartão R$ 5,00 e boleto R$ 10,00. Abaixo do mínimo, o meio é desligado ao salvar, mesmo enviando `true`. Envie `false` para desligar um meio acima do mínimo. Na edição sem `price`, só os meios enviados são recalculados; com `price`, os três são recalculados. Exemplo: `true`. | | `max_credit_card_installments` | inteiro | não | Máximo de parcelas no cartão de crédito. O valor de cada parcela (price / max_credit_card_installments) não pode ficar abaixo de R$ 5,00 — abaixo disso, a criação/edição retorna 400. Exemplo: `12`. | ### Exemplo do corpo Oferta avulsa: ```json { "product_id": "c4d5e6f7-a8b9-4c0d-1e2f-3a4b5c6d7e8f", "price": 19790, "title": "Plano Mensal", "is_enabled_pix": true, "is_enabled_credit_card": true, "is_enabled_billet": false, "max_credit_card_installments": 12 } ``` ## Respostas ### 201 — Oferta criada | Campo | Tipo | Obrigatório | Descrição | | --- | --- | --- | --- | | `data` | [Oferta](/docs/referencia/entidades/oferta) | não | — | #### Exemplo default: ```json { "data": { "id": "d5e6f7a8-b9c0-4d1e-2f3a-4b5c6d7e8f9a", "identifier": "PPP1234567890", "title": "Plano Mensal", "price": 197.9, "is_active": true, "is_default": true, "product_id": "c4d5e6f7-a8b9-4c0d-1e2f-3a4b5c6d7e8f", "payment_methods": { "pix": true, "credit_card": true, "billet": false }, "max_credit_card_installments": 12, "cycle": null, "cycle_interval": null, "cycle_interval_limit": null, "allow_purchase_quantity": false, "purchase_quantity_limit": null, "purchase_quantity_min": 1, "expires_at": null, "created_at": "2026-01-15T12:00:00.000Z" } } ``` ## Erros | Status | Descrição | | --- | --- | | `400` | Dados inválidos | | `401` | Token ausente, inválido ou expirado, ou credencial revogada ou expirada. Chame POST /auth/token para obter um token novo. | | `403` | IP não autorizado | | `404` | Produto não encontrado | | `429` | Limite de requisições excedido | Lista completa de códigos e como tratá-los: [Erros](/docs/guias/fundamentos/erros). --- # Consultar o frete de uma oferta URL: https://staging.pagpolar.com/docs/referencia/ofertas/list-offer-shipping > Devolve as opções de entrega de uma oferta de produto físico para um CEP. Use o `id` da opção escolhida em `shipping_option_id` na criação do pagamento. Pré-requisito, feito pelo vendedor no painel: a integração de logística cadastrada, as dimensões e o peso do produto preenchidos e pelo menos uma configuração de frete ativa. Sem configuração de frete, a consulta responde `400`. Oferta que não é de produto físico responde `200` com a lista vazia. Se a transportadora não responder, as opções de frete calculado ficam de fora e as opções grátis e de valor fixo continuam na lista. `GET /offers/{identifier}/shipping` ## Autenticação - Token Bearer no header `Authorization` (esquema `BearerAuth`). Token de acesso devolvido por POST /auth/token. Envie como "Authorization: Bearer ". Vale 24 horas. ## Parâmetros de caminho | Parâmetro | Tipo | Obrigatório | Descrição | | --- | --- | --- | --- | | `identifier` | texto | sim | Exemplo: `PPP1234567890`. | ## Parâmetros de consulta | Parâmetro | Tipo | Obrigatório | Descrição | | --- | --- | --- | --- | | `postal_code` | texto | sim | CEP de destino, com ou sem pontuação. Exemplo: `01311000`. | | `quantity` | inteiro | não | Quantidade de unidades, para o cálculo do peso. O padrão é 1. Exemplo: `1`. | ## Respostas ### 200 — Opções de frete | Campo | Tipo | Obrigatório | Descrição | | --- | --- | --- | --- | | `data` | [lista de Opção de frete](/docs/referencia/entidades/opcao-de-frete) | não | — | ## Erros | Status | Descrição | | --- | --- | | `400` | CEP inválido, ou produto físico sem configuração de frete ativa | | `401` | Token ausente, inválido ou expirado, ou credencial revogada ou expirada. Chame POST /auth/token para obter um token novo. | | `403` | IP não autorizado | | `404` | Oferta não encontrada | | `429` | Limite de requisições excedido | Lista completa de códigos e como tratá-los: [Erros](/docs/guias/fundamentos/erros). --- # Consultar oferta pelo código URL: https://staging.pagpolar.com/docs/referencia/ofertas/get-offer `GET /offers/{identifier}` ## Autenticação - Token Bearer no header `Authorization` (esquema `BearerAuth`). Token de acesso devolvido por POST /auth/token. Envie como "Authorization: Bearer ". Vale 24 horas. ## Parâmetros de caminho | Parâmetro | Tipo | Obrigatório | Descrição | | --- | --- | --- | --- | | `identifier` | texto | sim | Exemplo: `PPP1234567890`. | ## Respostas ### 200 — Oferta | Campo | Tipo | Obrigatório | Descrição | | --- | --- | --- | --- | | `data` | [Oferta](/docs/referencia/entidades/oferta) | não | — | ## Erros | Status | Descrição | | --- | --- | | `401` | Token ausente, inválido ou expirado, ou credencial revogada ou expirada. Chame POST /auth/token para obter um token novo. | | `403` | IP não autorizado | | `404` | Oferta não encontrada | | `429` | Limite de requisições excedido | Lista completa de códigos e como tratá-los: [Erros](/docs/guias/fundamentos/erros). --- # Listar ofertas de um produto URL: https://staging.pagpolar.com/docs/referencia/ofertas/list-product-offers > `id` precisa ser o id (uuid) de um produto da sua conta — produtos de outro whitelabel retornam 404. `GET /offers/by-product/{id}` ## Autenticação - Token Bearer no header `Authorization` (esquema `BearerAuth`). Token de acesso devolvido por POST /auth/token. Envie como "Authorization: Bearer ". Vale 24 horas. ## Parâmetros de caminho | Parâmetro | Tipo | Obrigatório | Descrição | | --- | --- | --- | --- | | `id` | texto (uuid) | sim | Exemplo: `c4d5e6f7-a8b9-4c0d-1e2f-3a4b5c6d7e8f`. | ## Parâmetros de consulta | Parâmetro | Tipo | Obrigatório | Descrição | | --- | --- | --- | --- | | `page` | inteiro | não | Número da página, começando em 1. | | `per_page` | inteiro | não | Itens por página, de 1 a 100. | | `is_active` | booleano | não | `true` lista só os ativos; `false`, só os inativos. Sem o filtro, lista os dois. | | `title` | texto | não | Título da oferta. Busca por parte do texto, sem diferenciar maiúsculas de minúsculas. | ## Respostas ### 200 — Lista de ofertas do produto | Campo | Tipo | Obrigatório | Descrição | | --- | --- | --- | --- | | `data` | [lista de Oferta](/docs/referencia/entidades/oferta) | não | — | | `meta` | objeto | não | — | | `meta.page` | inteiro | não | Exemplo: `1`. | | `meta.per_page` | inteiro | não | Exemplo: `25`. | | `meta.total` | inteiro | não | Exemplo: `143`. | | `meta.total_pages` | inteiro | não | Exemplo: `6`. | ## Erros | Status | Descrição | | --- | --- | | `401` | Token ausente, inválido ou expirado, ou credencial revogada ou expirada. Chame POST /auth/token para obter um token novo. | | `403` | IP não autorizado | | `404` | Produto não encontrado | | `429` | Limite de requisições excedido | Lista completa de códigos e como tratá-los: [Erros](/docs/guias/fundamentos/erros). --- # Editar oferta URL: https://staging.pagpolar.com/docs/referencia/ofertas/update-offer > Edita campos comerciais de uma oferta já criada. Envie apenas os campos que deseja atualizar (edição parcial) — o corpo não pode vir vazio. Diferente da consulta (`GET /offers/{identifier}`, que usa o código da oferta, `PPP` seguido de 10 dígitos), esta rota identifica a oferta pelo `id` (uuid). Não é possível alterar `product_id`, `cycle`/`cycle_interval` (assinatura) ou `is_default` por aqui — a troca de oferta padrão continua sendo feita apenas pelo painel. `PATCH /offers/{id}` ## Autenticação - Token Bearer no header `Authorization` (esquema `BearerAuth`). Token de acesso devolvido por POST /auth/token. Envie como "Authorization: Bearer ". Vale 24 horas. ## Parâmetros de caminho | Parâmetro | Tipo | Obrigatório | Descrição | | --- | --- | --- | --- | | `id` | texto (uuid) | sim | Exemplo: `d5e6f7a8-b9c0-4d1e-2f3a-4b5c6d7e8f9a`. | ## Corpo da requisição Content-type: `application/json`. | Campo | Tipo | Obrigatório | Descrição | | --- | --- | --- | --- | | `price` | inteiro | não | Preço da oferta em centavos, número inteiro — a mesma unidade de offer.value. 4990 = R$ 49,90. Valor com casas decimais retorna 400. Exemplo: `24790`. | | `title` | texto | não | Exemplo: `Plano Mensal Promocional`. Pode ser nulo. | | `is_active` | booleano | não | Exemplo: `true`. | | `is_enabled_pix` | booleano | não | PIX. Mínimo de R$ 5,00. Os três meios começam ligados, mas cada um só fica ligado se o preço atinge o mínimo dele: PIX R$ 5,00, cartão R$ 5,00 e boleto R$ 10,00. Abaixo do mínimo, o meio é desligado ao salvar, mesmo enviando `true`. Envie `false` para desligar um meio acima do mínimo. Na edição sem `price`, só os meios enviados são recalculados; com `price`, os três são recalculados. Exemplo: `true`. | | `is_enabled_credit_card` | booleano | não | Cartão de crédito. Mínimo de R$ 5,00. Os três meios começam ligados, mas cada um só fica ligado se o preço atinge o mínimo dele: PIX R$ 5,00, cartão R$ 5,00 e boleto R$ 10,00. Abaixo do mínimo, o meio é desligado ao salvar, mesmo enviando `true`. Envie `false` para desligar um meio acima do mínimo. Na edição sem `price`, só os meios enviados são recalculados; com `price`, os três são recalculados. Exemplo: `true`. | | `is_enabled_billet` | booleano | não | Boleto. Mínimo de R$ 10,00. Os três meios começam ligados, mas cada um só fica ligado se o preço atinge o mínimo dele: PIX R$ 5,00, cartão R$ 5,00 e boleto R$ 10,00. Abaixo do mínimo, o meio é desligado ao salvar, mesmo enviando `true`. Envie `false` para desligar um meio acima do mínimo. Na edição sem `price`, só os meios enviados são recalculados; com `price`, os três são recalculados. Exemplo: `true`. | | `max_credit_card_installments` | inteiro | não | Máximo de parcelas no cartão de crédito. O valor de cada parcela (price / max_credit_card_installments) não pode ficar abaixo de R$ 5,00 — abaixo disso, a criação/edição retorna 400. Exemplo: `6`. | ### Exemplo do corpo Editar preço e título: ```json { "price": 24790, "title": "Plano Mensal Promocional" } ``` Desativar oferta: ```json { "is_active": false } ``` ## Respostas ### 200 — Oferta atualizada | Campo | Tipo | Obrigatório | Descrição | | --- | --- | --- | --- | | `data` | [Oferta](/docs/referencia/entidades/oferta) | não | — | #### Exemplo default: ```json { "data": { "id": "d5e6f7a8-b9c0-4d1e-2f3a-4b5c6d7e8f9a", "identifier": "PPP1234567890", "title": "Plano Mensal Promocional", "price": 247.9, "is_active": true, "is_default": false, "product_id": "c4d5e6f7-a8b9-4c0d-1e2f-3a4b5c6d7e8f", "payment_methods": { "pix": true, "credit_card": true, "billet": false }, "max_credit_card_installments": 6, "cycle": null, "cycle_interval": null, "cycle_interval_limit": null, "allow_purchase_quantity": false, "purchase_quantity_limit": null, "purchase_quantity_min": 1, "expires_at": null, "created_at": "2026-01-15T12:00:00.000Z" } } ``` ## Erros | Status | Descrição | | --- | --- | | `400` | Dados inválidos ou corpo vazio | | `401` | Token ausente, inválido ou expirado, ou credencial revogada ou expirada. Chame POST /auth/token para obter um token novo. | | `403` | IP não autorizado | | `404` | Oferta de produto não encontrada | | `429` | Limite de requisições excedido | Lista completa de códigos e como tratá-los: [Erros](/docs/guias/fundamentos/erros). --- # Listar planos URL: https://staging.pagpolar.com/docs/referencia/planos/list-plans > Um plano é um produto do tipo `SUBSCRIPTION` — aparece aqui, não em `GET /products`. `GET /plans` ## Autenticação - Token Bearer no header `Authorization` (esquema `BearerAuth`). Token de acesso devolvido por POST /auth/token. Envie como "Authorization: Bearer ". Vale 24 horas. ## Parâmetros de consulta | Parâmetro | Tipo | Obrigatório | Descrição | | --- | --- | --- | --- | | `page` | inteiro | não | Número da página, começando em 1. | | `per_page` | inteiro | não | Itens por página, de 1 a 100. | | `is_active` | booleano | não | `true` lista só os ativos; `false`, só os inativos. Sem o filtro, lista os dois. | | `name` | texto | não | Nome do plano. Busca por parte do texto, sem diferenciar maiúsculas de minúsculas. | ## Respostas ### 200 — Lista de planos | Campo | Tipo | Obrigatório | Descrição | | --- | --- | --- | --- | | `data` | [lista de Produto](/docs/referencia/entidades/produto) | não | — | | `meta` | objeto | não | — | | `meta.page` | inteiro | não | Exemplo: `1`. | | `meta.per_page` | inteiro | não | Exemplo: `25`. | | `meta.total` | inteiro | não | Exemplo: `143`. | | `meta.total_pages` | inteiro | não | Exemplo: `6`. | ## Erros | Status | Descrição | | --- | --- | | `401` | Token ausente, inválido ou expirado, ou credencial revogada ou expirada. Chame POST /auth/token para obter um token novo. | | `403` | IP não autorizado | | `429` | Limite de requisições excedido | Lista completa de códigos e como tratá-los: [Erros](/docs/guias/fundamentos/erros). --- # Criar plano URL: https://staging.pagpolar.com/docs/referencia/planos/create-plan > Cria um plano de assinatura — internamente é um produto com `type=SUBSCRIPTION`, por isso a resposta usa o mesmo formato de `Product`. O plano é criado sem imagem e sem categoria — ambos podem ser preenchidos depois pelo painel. `warranty_time` também não é aceito no corpo: é resolvido automaticamente a partir da configuração mínima de garantia da sua conta. Este endpoint só cria o plano. Para vender, crie também uma oferta recorrente para ele — consulte `POST /plans/{id}/offers`. `POST /plans` ## Autenticação - Token Bearer no header `Authorization` (esquema `BearerAuth`). Token de acesso devolvido por POST /auth/token. Envie como "Authorization: Bearer ". Vale 24 horas. ## Corpo da requisição Content-type: `application/json`. | Campo | Tipo | Obrigatório | Descrição | | --- | --- | --- | --- | | `name` | texto | sim | Exemplo: `Assinatura Premium`. | | `description` | texto | não | Exemplo: `Acesso completo à plataforma, renovação mensal`. Pode ser nulo. | ### Exemplo do corpo Plano de assinatura: ```json { "name": "Assinatura Premium", "description": "Acesso completo à plataforma, renovação mensal" } ``` ## Respostas ### 201 — Plano criado | Campo | Tipo | Obrigatório | Descrição | | --- | --- | --- | --- | | `data` | [Produto](/docs/referencia/entidades/produto) | não | — | #### Exemplo default: ```json { "data": { "id": "a8b9c0d1-e2f3-4a4b-5c6d-7e8f9a0b1c2d", "name": "Assinatura Premium", "description": "Acesso completo à plataforma, renovação mensal", "author": null, "promotional_text": null, "is_active": true, "image": null, "type": "SUBSCRIPTION", "content_type": "DEFAULT", "warranty_time": 7, "category": null, "created_at": "2026-01-15T12:00:00.000Z", "updated_at": "2026-01-15T12:00:00.000Z" } } ``` ## Erros | Status | Descrição | | --- | --- | | `400` | Dados inválidos | | `401` | Token ausente, inválido ou expirado, ou credencial revogada ou expirada. Chame POST /auth/token para obter um token novo. | | `403` | IP não autorizado | | `429` | Limite de requisições excedido | Lista completa de códigos e como tratá-los: [Erros](/docs/guias/fundamentos/erros). --- # Consultar plano URL: https://staging.pagpolar.com/docs/referencia/planos/get-plan `GET /plans/{id}` ## Autenticação - Token Bearer no header `Authorization` (esquema `BearerAuth`). Token de acesso devolvido por POST /auth/token. Envie como "Authorization: Bearer ". Vale 24 horas. ## Parâmetros de caminho | Parâmetro | Tipo | Obrigatório | Descrição | | --- | --- | --- | --- | | `id` | texto (uuid) | sim | Exemplo: `a8b9c0d1-e2f3-4a4b-5c6d-7e8f9a0b1c2d`. | ## Respostas ### 200 — Plano | Campo | Tipo | Obrigatório | Descrição | | --- | --- | --- | --- | | `data` | [Produto](/docs/referencia/entidades/produto) | não | — | ## Erros | Status | Descrição | | --- | --- | | `401` | Token ausente, inválido ou expirado, ou credencial revogada ou expirada. Chame POST /auth/token para obter um token novo. | | `403` | IP não autorizado | | `404` | Plano não encontrado | | `429` | Limite de requisições excedido | Lista completa de códigos e como tratá-los: [Erros](/docs/guias/fundamentos/erros). --- # Editar plano URL: https://staging.pagpolar.com/docs/referencia/planos/update-plan > Edita campos básicos de um plano já criado. Só `name`, `description` e `is_active` podem ser alterados por aqui — envie apenas os campos que deseja atualizar (edição parcial). O corpo não pode vir vazio: pelo menos um dos três campos precisa ser enviado. `PATCH /plans/{id}` ## Autenticação - Token Bearer no header `Authorization` (esquema `BearerAuth`). Token de acesso devolvido por POST /auth/token. Envie como "Authorization: Bearer ". Vale 24 horas. ## Parâmetros de caminho | Parâmetro | Tipo | Obrigatório | Descrição | | --- | --- | --- | --- | | `id` | texto (uuid) | sim | Exemplo: `a8b9c0d1-e2f3-4a4b-5c6d-7e8f9a0b1c2d`. | ## Corpo da requisição Content-type: `application/json`. | Campo | Tipo | Obrigatório | Descrição | | --- | --- | --- | --- | | `name` | texto | não | Exemplo: `Assinatura Premium`. | | `description` | texto | não | Exemplo: `Acesso completo à plataforma, renovação mensal`. Pode ser nulo. | | `is_active` | booleano | não | Exemplo: `true`. | ### Exemplo do corpo Editar nome e descrição: ```json { "name": "Assinatura Premium", "description": "Acesso completo à plataforma, renovação mensal" } ``` Desativar plano: ```json { "is_active": false } ``` ## Respostas ### 200 — Plano atualizado | Campo | Tipo | Obrigatório | Descrição | | --- | --- | --- | --- | | `data` | [Produto](/docs/referencia/entidades/produto) | não | — | #### Exemplo default: ```json { "data": { "id": "a8b9c0d1-e2f3-4a4b-5c6d-7e8f9a0b1c2d", "name": "Assinatura Premium", "description": "Acesso completo à plataforma, renovação mensal", "author": null, "promotional_text": null, "is_active": true, "image": null, "type": "SUBSCRIPTION", "content_type": "DEFAULT", "warranty_time": 7, "category": null, "created_at": "2026-01-15T12:00:00.000Z", "updated_at": "2026-01-15T12:00:00.000Z" } } ``` ## Erros | Status | Descrição | | --- | --- | | `400` | Dados inválidos ou corpo vazio | | `401` | Token ausente, inválido ou expirado, ou credencial revogada ou expirada. Chame POST /auth/token para obter um token novo. | | `403` | IP não autorizado | | `404` | Plano não encontrado | | `429` | Limite de requisições excedido | Lista completa de códigos e como tratá-los: [Erros](/docs/guias/fundamentos/erros). --- # Listar ofertas de um plano URL: https://staging.pagpolar.com/docs/referencia/planos/list-plan-offers > `id` precisa ser o id (uuid) de um plano da sua conta — planos de outro whitelabel retornam 404. `GET /plans/{id}/offers` ## Autenticação - Token Bearer no header `Authorization` (esquema `BearerAuth`). Token de acesso devolvido por POST /auth/token. Envie como "Authorization: Bearer ". Vale 24 horas. ## Parâmetros de caminho | Parâmetro | Tipo | Obrigatório | Descrição | | --- | --- | --- | --- | | `id` | texto (uuid) | sim | Exemplo: `a8b9c0d1-e2f3-4a4b-5c6d-7e8f9a0b1c2d`. | ## Parâmetros de consulta | Parâmetro | Tipo | Obrigatório | Descrição | | --- | --- | --- | --- | | `page` | inteiro | não | Número da página, começando em 1. | | `per_page` | inteiro | não | Itens por página, de 1 a 100. | | `is_active` | booleano | não | `true` lista só os ativos; `false`, só os inativos. Sem o filtro, lista os dois. | | `title` | texto | não | Título da oferta. Busca por parte do texto, sem diferenciar maiúsculas de minúsculas. | ## Respostas ### 200 — Lista de ofertas do plano | Campo | Tipo | Obrigatório | Descrição | | --- | --- | --- | --- | | `data` | [lista de Oferta](/docs/referencia/entidades/oferta) | não | — | | `meta` | objeto | não | — | | `meta.page` | inteiro | não | Exemplo: `1`. | | `meta.per_page` | inteiro | não | Exemplo: `25`. | | `meta.total` | inteiro | não | Exemplo: `143`. | | `meta.total_pages` | inteiro | não | Exemplo: `6`. | ## Erros | Status | Descrição | | --- | --- | | `401` | Token ausente, inválido ou expirado, ou credencial revogada ou expirada. Chame POST /auth/token para obter um token novo. | | `403` | IP não autorizado | | `404` | Plano não encontrado | | `429` | Limite de requisições excedido | Lista completa de códigos e como tratá-los: [Erros](/docs/guias/fundamentos/erros). --- # Criar oferta de plano URL: https://staging.pagpolar.com/docs/referencia/planos/create-plan-offer > Cria uma oferta **recorrente** para um plano já existente do seu catálogo. `cycle` é obrigatório — `WEEKLY`, `MONTHLY` ou `YEARLY` (`DAILY` não é aceito pela API pública). **Isso cria só a oferta (o preço recorrente) do plano — não cria uma assinatura de verdade para um cliente.** Para assinar um cliente de fato nesta oferta, use `POST /plans/offer/{id}/subscribe`. `POST /plans/{id}/offers` ## Autenticação - Token Bearer no header `Authorization` (esquema `BearerAuth`). Token de acesso devolvido por POST /auth/token. Envie como "Authorization: Bearer ". Vale 24 horas. ## Parâmetros de caminho | Parâmetro | Tipo | Obrigatório | Descrição | | --- | --- | --- | --- | | `id` | texto (uuid) | sim | Exemplo: `a8b9c0d1-e2f3-4a4b-5c6d-7e8f9a0b1c2d`. | ## Corpo da requisição Content-type: `application/json`. | Campo | Tipo | Obrigatório | Descrição | | --- | --- | --- | --- | | `price` | inteiro | sim | Preço da oferta em centavos, número inteiro — a mesma unidade de offer.value. 4990 = R$ 49,90. Valor com casas decimais retorna 400. Exemplo: `9790`. | | `cycle` | enum | sim | Valores: `WEEKLY`, `MONTHLY`, `YEARLY`. Exemplo: `MONTHLY`. | | `cycle_interval` | inteiro | não | A cada quantos ciclos a cobrança se repete (ex.: cycle=MONTHLY + cycle_interval=3 = trimestral). Exemplo: `1`. | | `title` | texto | não | Exemplo: `Plano Mensal`. Pode ser nulo. | | `is_active` | booleano | não | Exemplo: `true`. | | `is_enabled_pix` | booleano | não | PIX. Mínimo de R$ 5,00. Os três meios começam ligados, mas cada um só fica ligado se o preço atinge o mínimo dele: PIX R$ 5,00, cartão R$ 5,00 e boleto R$ 10,00. Abaixo do mínimo, o meio é desligado ao salvar, mesmo enviando `true`. Envie `false` para desligar um meio acima do mínimo. Na edição sem `price`, só os meios enviados são recalculados; com `price`, os três são recalculados. Exemplo: `true`. | | `is_enabled_credit_card` | booleano | não | Cartão de crédito. Mínimo de R$ 5,00. Os três meios começam ligados, mas cada um só fica ligado se o preço atinge o mínimo dele: PIX R$ 5,00, cartão R$ 5,00 e boleto R$ 10,00. Abaixo do mínimo, o meio é desligado ao salvar, mesmo enviando `true`. Envie `false` para desligar um meio acima do mínimo. Na edição sem `price`, só os meios enviados são recalculados; com `price`, os três são recalculados. Exemplo: `true`. | | `is_enabled_billet` | booleano | não | Boleto. Mínimo de R$ 10,00. Os três meios começam ligados, mas cada um só fica ligado se o preço atinge o mínimo dele: PIX R$ 5,00, cartão R$ 5,00 e boleto R$ 10,00. Abaixo do mínimo, o meio é desligado ao salvar, mesmo enviando `true`. Envie `false` para desligar um meio acima do mínimo. Na edição sem `price`, só os meios enviados são recalculados; com `price`, os três são recalculados. Exemplo: `true`. | | `max_credit_card_installments` | inteiro | não | Ofertas de plano não podem ser parceladas no cartão de crédito — envie 1 ou omita o campo (default). Valores acima de 1 retornam 400. Exemplo: `1`. | ### Exemplo do corpo Oferta mensal: ```json { "price": 9790, "cycle": "MONTHLY", "title": "Plano Mensal", "is_enabled_pix": true, "is_enabled_credit_card": true, "is_enabled_billet": false } ``` ## Respostas ### 201 — Oferta de plano criada | Campo | Tipo | Obrigatório | Descrição | | --- | --- | --- | --- | | `data` | [Oferta](/docs/referencia/entidades/oferta) | não | — | #### Exemplo default: ```json { "data": { "id": "b9c0d1e2-f3a4-4b5c-6d7e-8f9a0b1c2d3e", "identifier": "PPP1234567890", "title": "Plano Mensal", "price": 97.9, "is_active": true, "is_default": true, "product_id": "a8b9c0d1-e2f3-4a4b-5c6d-7e8f9a0b1c2d", "payment_methods": { "pix": true, "credit_card": true, "billet": false }, "max_credit_card_installments": 1, "cycle": "MONTHLY", "cycle_interval": 1, "cycle_interval_limit": null, "allow_purchase_quantity": false, "purchase_quantity_limit": null, "purchase_quantity_min": 1, "expires_at": null, "created_at": "2026-01-15T12:00:00.000Z" } } ``` ## Erros | Status | Descrição | | --- | --- | | `400` | Dados inválidos | | `401` | Token ausente, inválido ou expirado, ou credencial revogada ou expirada. Chame POST /auth/token para obter um token novo. | | `403` | IP não autorizado | | `404` | Plano não encontrado | | `429` | Limite de requisições excedido | Lista completa de códigos e como tratá-los: [Erros](/docs/guias/fundamentos/erros). --- # Editar oferta de plano URL: https://staging.pagpolar.com/docs/referencia/planos/update-plan-offer > Edita campos comerciais de uma oferta de plano já criada. Envie apenas os campos que deseja atualizar (edição parcial) — o corpo não pode vir vazio. Não é possível alterar `product_id` ou `is_default` por aqui — a troca de oferta padrão continua sendo feita apenas pelo painel. `PATCH /plan-offers/{id}` ## Autenticação - Token Bearer no header `Authorization` (esquema `BearerAuth`). Token de acesso devolvido por POST /auth/token. Envie como "Authorization: Bearer ". Vale 24 horas. ## Parâmetros de caminho | Parâmetro | Tipo | Obrigatório | Descrição | | --- | --- | --- | --- | | `id` | texto (uuid) | sim | Exemplo: `b9c0d1e2-f3a4-4b5c-6d7e-8f9a0b1c2d3e`. | ## Corpo da requisição Content-type: `application/json`. | Campo | Tipo | Obrigatório | Descrição | | --- | --- | --- | --- | | `price` | inteiro | não | Preço da oferta em centavos, número inteiro — a mesma unidade de offer.value. 4990 = R$ 49,90. Valor com casas decimais retorna 400. Exemplo: `11790`. | | `cycle` | enum | não | Valores: `WEEKLY`, `MONTHLY`, `YEARLY`. Exemplo: `MONTHLY`. | | `cycle_interval` | inteiro | não | Exemplo: `1`. | | `title` | texto | não | Exemplo: `Plano Mensal`. Pode ser nulo. | | `is_active` | booleano | não | Exemplo: `true`. | | `is_enabled_pix` | booleano | não | PIX. Mínimo de R$ 5,00. Os três meios começam ligados, mas cada um só fica ligado se o preço atinge o mínimo dele: PIX R$ 5,00, cartão R$ 5,00 e boleto R$ 10,00. Abaixo do mínimo, o meio é desligado ao salvar, mesmo enviando `true`. Envie `false` para desligar um meio acima do mínimo. Na edição sem `price`, só os meios enviados são recalculados; com `price`, os três são recalculados. Exemplo: `true`. | | `is_enabled_credit_card` | booleano | não | Cartão de crédito. Mínimo de R$ 5,00. Os três meios começam ligados, mas cada um só fica ligado se o preço atinge o mínimo dele: PIX R$ 5,00, cartão R$ 5,00 e boleto R$ 10,00. Abaixo do mínimo, o meio é desligado ao salvar, mesmo enviando `true`. Envie `false` para desligar um meio acima do mínimo. Na edição sem `price`, só os meios enviados são recalculados; com `price`, os três são recalculados. Exemplo: `true`. | | `is_enabled_billet` | booleano | não | Boleto. Mínimo de R$ 10,00. Os três meios começam ligados, mas cada um só fica ligado se o preço atinge o mínimo dele: PIX R$ 5,00, cartão R$ 5,00 e boleto R$ 10,00. Abaixo do mínimo, o meio é desligado ao salvar, mesmo enviando `true`. Envie `false` para desligar um meio acima do mínimo. Na edição sem `price`, só os meios enviados são recalculados; com `price`, os três são recalculados. Exemplo: `true`. | | `max_credit_card_installments` | inteiro | não | Ofertas de plano não podem ser parceladas no cartão de crédito — envie 1 ou omita o campo (default). Valores acima de 1 retornam 400. Exemplo: `1`. | ### Exemplo do corpo Editar preço: ```json { "price": 11790 } ``` Desativar oferta de plano: ```json { "is_active": false } ``` ## Respostas ### 200 — Oferta de plano atualizada | Campo | Tipo | Obrigatório | Descrição | | --- | --- | --- | --- | | `data` | [Oferta](/docs/referencia/entidades/oferta) | não | — | #### Exemplo default: ```json { "data": { "id": "b9c0d1e2-f3a4-4b5c-6d7e-8f9a0b1c2d3e", "identifier": "PPP1234567890", "title": "Plano Mensal", "price": 117.9, "is_active": true, "is_default": true, "product_id": "a8b9c0d1-e2f3-4a4b-5c6d-7e8f9a0b1c2d", "payment_methods": { "pix": true, "credit_card": true, "billet": false }, "max_credit_card_installments": 1, "cycle": "MONTHLY", "cycle_interval": 1, "cycle_interval_limit": null, "allow_purchase_quantity": false, "purchase_quantity_limit": null, "purchase_quantity_min": 1, "expires_at": null, "created_at": "2026-01-15T12:00:00.000Z" } } ``` ## Erros | Status | Descrição | | --- | --- | | `400` | Dados inválidos ou corpo vazio | | `401` | Token ausente, inválido ou expirado, ou credencial revogada ou expirada. Chame POST /auth/token para obter um token novo. | | `403` | IP não autorizado | | `404` | Oferta de plano não encontrada | | `429` | Limite de requisições excedido | Lista completa de códigos e como tratá-los: [Erros](/docs/guias/fundamentos/erros). --- # Assinar um plano (criar assinatura) URL: https://staging.pagpolar.com/docs/referencia/assinaturas/create-subscription > Cria uma assinatura de verdade para um cliente em uma oferta de plano existente. `id` pode ser o id (uuid) **ou** o identifier da oferta de plano — mesma resolução usada em `GET /offers/{identifier}`. Só aceita **cartão de crédito**. A cobrança do cartão é feita no gateway **depois** da resposta desta requisição: a assinatura retorna com `status: DRAFT`. O webhook `SUBSCRIPTION_CONFIRMED` avisa que o gateway aceitou a assinatura — o status continua `DRAFT` — e ela vira `ACTIVE` quando a primeira fatura é paga. Se o gateway recusar, o webhook é `SUBSCRIPTION_FAILED` e o status vira `FAILED`. Como alternativa aos webhooks, faça polling em `GET /subscriptions/{id}`. O header `Idempotency-Key` é **obrigatório**. Em caso de timeout, reenvie a requisição com a mesma chave: a resposta original será devolvida sem criar uma segunda assinatura. **Uma assinatura não é cobrada diretamente — cada ciclo cobrado (o primeiro e todas as renovações seguintes) gera uma `Transaction` própria**, a mesma entidade retornada por `GET /sales`/`GET /sales/{identifier}`. Ou seja, para acompanhar os pagamentos de uma assinatura ao longo do tempo, use os eventos de transação (`TRANSACTION_PAID`, `TRANSACTION_CANCELED`, etc.) e `GET /sales` filtrando pelo cliente/período — os eventos de assinatura (`SUBSCRIPTION_*`) informam mudanças de status da assinatura em si, não de cada cobrança individual. `POST /plans/offer/{id}/subscribe` ## Autenticação - Token Bearer no header `Authorization` (esquema `BearerAuth`). Token de acesso devolvido por POST /auth/token. Envie como "Authorization: Bearer ". Vale 24 horas. ## Parâmetros de caminho | Parâmetro | Tipo | Obrigatório | Descrição | | --- | --- | --- | --- | | `id` | texto | sim | Exemplo: `b9c0d1e2-f3a4-4b5c-6d7e-8f9a0b1c2d3e`. | ## Parâmetros de header | Parâmetro | Tipo | Obrigatório | Descrição | | --- | --- | --- | --- | | `Idempotency-Key` | texto | sim | Exemplo: `2f1c4d2e-8f4a-4c1e-9f7b-6a2d1e3c5b90`. | ## Corpo da requisição Content-type: `application/json`. | Campo | Tipo | Obrigatório | Descrição | | --- | --- | --- | --- | | `installments` | inteiro | sim | O máximo real pode ser menor que 12: é limitado pela configuração da oferta (consulte max_credit_card_installments em GET /offers/{identifier}). Acima do limite da oferta, retorna 400. Exemplo: `3`. | | `customer` | objeto | sim | — | | `customer.name` | texto | sim | Exemplo: `Fulano de Tal`. | | `customer.email` | texto (email) | sim | Exemplo: `fulano@exemplo.com`. | | `customer.document` | texto | sim | CPF (11 dígitos) ou CNPJ (14 dígitos), com ou sem pontuação: `12345678909` ou `123.456.789-09`. A API grava só os dígitos. Fora disso, 400. Exemplo: `12345678909`. | | `customer.phone` | texto | sim | DDD e número, com 10 ou 11 dígitos (12 ou 13 com o código do país 55). Pontuação, espaços, parênteses e `+` são aceitos: `11999998888`, `(11) 99999-8888` ou `+55 11 99999-8888`. A API grava só os dígitos. Fora disso, 400. Exemplo: `11999999999`. | | `address` | objeto | não | Obrigatório quando a oferta é de produto físico (`requires_shipping: true`); nos demais casos é opcional e não é usado no processamento da venda. | | `address.street` | texto | sim | Exemplo: `Rua das Flores`. | | `address.number` | texto | sim | Exemplo: `123`. | | `address.complement` | texto | não | Exemplo: `Apto 4B`. Pode ser nulo. | | `address.neighborhood` | texto | sim | Exemplo: `Centro`. | | `address.city` | texto | sim | Exemplo: `São Paulo`. | | `address.state` | texto | sim | Exemplo: `SP`. | | `address.postal_code` | texto | sim | Exemplo: `01000-000`. | | `credit_card` | objeto | sim | Dados do cartão de crédito usado na cobrança. | | `credit_card.holder_name` | texto | sim | Exemplo: `FULANO DE TAL`. | | `credit_card.holder_document` | texto | sim | CPF (11 dígitos) ou CNPJ (14 dígitos) do titular do cartão, com ou sem pontuação. A API grava só os dígitos. Fora disso, 400. Exemplo: `12345678909`. | | `credit_card.number` | texto | sim | Exemplo: `4111111111111111`. | | `credit_card.expiration_month` | inteiro | sim | Exemplo: `12`. | | `credit_card.expiration_year` | inteiro | sim | Exemplo: `2030`. | | `credit_card.cvv` | texto | sim | Exemplo: `123`. | | `buyer_ip` | texto | não | IP do comprador final. Melhora a análise antifraude. Exemplo: `203.0.113.10`. | | `buyer_user_agent` | texto | não | — | | `affiliate_identifier` | texto | não | Código do afiliado que trouxe a venda, no formato `PAO` seguido de 10 dígitos. Formato inválido retorna `400`. Código de afiliado inexistente, de outro produto, inativo ou sem a oferta liberada é **ignorado**: a venda é processada normalmente, sem afiliado. Não há cookie nem regra de primeiro ou último clique — vale o código enviado. Exemplo: `PAO1234567890`. | | `external_reference` | texto | não | Referência do pedido no seu sistema (por exemplo, o número do pedido). Opcional. Depois você consulta a venda por ela em `GET /sales/{identifier}` e filtra em `GET /sales?external_reference=`. Pode se repetir entre pedidos: a consulta devolve a venda mais recente com essa referência. Exemplo: `PED-2026-0001`. | ### Exemplo do corpo Assinar plano mensal: ```json { "installments": 1, "customer": { "name": "Fulano de Tal", "email": "fulano@exemplo.com", "document": "12345678909", "phone": "11999999999" }, "credit_card": { "holder_name": "FULANO DE TAL", "holder_document": "12345678909", "number": "4111111111111111", "expiration_month": 12, "expiration_year": 2030, "cvv": "123" }, "buyer_ip": "203.0.113.10" } ``` ## Respostas ### 201 — Assinatura criada (status inicial DRAFT, aguardando confirmação do gateway) | Campo | Tipo | Obrigatório | Descrição | | --- | --- | --- | --- | | `data` | [Assinatura](/docs/referencia/entidades/assinatura) | não | — | #### Exemplo default: ```json { "data": { "id": "f7a8b9c0-d1e2-4f3a-4b5c-6d7e8f9a0b1c", "status": "DRAFT", "payment_method": "CREDIT_CARD", "start_at": null, "next_billing_at": null, "next_billing_amount": null, "total_amount": null, "canceled_at": null, "created_at": "2026-01-15T12:00:00.000Z" } } ``` ## Erros | Status | Descrição | | --- | --- | | `400` | Dados inválidos, Idempotency-Key ausente, número de parcelas acima do limite da oferta, ou documento/telefone fora do formato (CPF 11 dígitos, CNPJ 14, telefone 10 a 13 dígitos) | | `401` | Token ausente, inválido ou expirado, ou credencial revogada ou expirada. Chame POST /auth/token para obter um token novo. | | `403` | IP não autorizado | | `404` | Oferta de plano não encontrada | | `409` | Oferta inativa/expirada, método não habilitado ou requisição idêntica em processamento | | `429` | Limite de requisições excedido | | `503` | Serviço de limite indisponível — repita a requisição com a mesma Idempotency-Key | Lista completa de códigos e como tratá-los: [Erros](/docs/guias/fundamentos/erros). --- # Listar vendas URL: https://staging.pagpolar.com/docs/referencia/vendas/list-sales `GET /sales` ## Autenticação - Token Bearer no header `Authorization` (esquema `BearerAuth`). Token de acesso devolvido por POST /auth/token. Envie como "Authorization: Bearer ". Vale 24 horas. ## Parâmetros de consulta | Parâmetro | Tipo | Obrigatório | Descrição | | --- | --- | --- | --- | | `page` | inteiro | não | Número da página, começando em 1. | | `per_page` | inteiro | não | Itens por página, de 1 a 100. | | `status` | enum | não | Situação da venda. Valores: `DRAFT`, `OPEN`, `PROCESSING`, `PAID`, `CANCELED`, `ASK_REFUND`, `ASK_PARTIAL_REFUND`, `REFUNDED`, `PARTIALLY_REFUNDED`, `REFUNDING`, `ABANDONED`, `EXPIRED`, `FAILED`, `CHARGEBACK_REQUESTED`, `CHARGEBACK_APPROVED`. | | `payment_method` | enum | não | Meio de pagamento da venda. Valores: `CREDIT_CARD`, `PIX`, `BOLETO`, `APPLE_PAY`, `GOOGLE_PAY`. | | `created_from` | texto (date-time) | não | Vendas criadas a partir desta data e hora, incluindo ela. ISO 8601 com fuso. | | `created_to` | texto (date-time) | não | Vendas criadas até esta data e hora, incluindo ela. ISO 8601 com fuso. | | `external_reference` | texto | não | Lista só as vendas com esta referência do pedido (a mesma enviada na criação do pagamento ou da assinatura). | | `product_id` | texto (uuid) | não | Vendas que têm este produto em algum item. A venda volta com todos os itens. | | `customer_email` | texto (email) | não | E-mail do cliente da venda. E-mail completo, sem diferenciar maiúsculas de minúsculas. | | `customer_document` | texto | não | Documento do cliente da venda. CPF com 11 dígitos ou CNPJ com 14, com ou sem pontuação. Exemplo: `123.456.789-09`. | ## Respostas ### 200 — Lista de vendas | Campo | Tipo | Obrigatório | Descrição | | --- | --- | --- | --- | | `data` | [lista de Venda](/docs/referencia/entidades/venda) | não | — | | `meta` | objeto | não | — | | `meta.page` | inteiro | não | Exemplo: `1`. | | `meta.per_page` | inteiro | não | Exemplo: `25`. | | `meta.total` | inteiro | não | Exemplo: `143`. | | `meta.total_pages` | inteiro | não | Exemplo: `6`. | ## Erros | Status | Descrição | | --- | --- | | `401` | Token ausente, inválido ou expirado, ou credencial revogada ou expirada. Chame POST /auth/token para obter um token novo. | | `403` | IP não autorizado | | `429` | Limite de requisições excedido | Lista completa de códigos e como tratá-los: [Erros](/docs/guias/fundamentos/erros). --- # Consultar venda URL: https://staging.pagpolar.com/docs/referencia/vendas/get-sale > Aceita três formas de referência, testadas nesta ordem: o `id` da venda (uuid, o mesmo que volta em `transactions` na criação do pagamento), o código da venda e a `external_reference` que você enviou no pedido. O valor só é tratado como código quando tem o formato do código: 10 dígitos, com ou sem o prefixo (ex.: `PPO0087103960`), ou um link terminado nele. Se a mesma `external_reference` foi usada em mais de um pedido, volta a venda mais recente. `GET /sales/{identifier}` ## Autenticação - Token Bearer no header `Authorization` (esquema `BearerAuth`). Token de acesso devolvido por POST /auth/token. Envie como "Authorization: Bearer ". Vale 24 horas. ## Parâmetros de caminho | Parâmetro | Tipo | Obrigatório | Descrição | | --- | --- | --- | --- | | `identifier` | texto | sim | Id (uuid), código ou referência externa da venda. | ## Respostas ### 200 — Venda | Campo | Tipo | Obrigatório | Descrição | | --- | --- | --- | --- | | `data` | [Venda](/docs/referencia/entidades/venda) | não | — | ## Erros | Status | Descrição | | --- | --- | | `401` | Token ausente, inválido ou expirado, ou credencial revogada ou expirada. Chame POST /auth/token para obter um token novo. | | `403` | IP não autorizado | | `404` | Venda não encontrada | | `429` | Limite de requisições excedido | Lista completa de códigos e como tratá-los: [Erros](/docs/guias/fundamentos/erros). --- # Listar reembolsos URL: https://staging.pagpolar.com/docs/referencia/reembolsos/list-refunds > Lista os pedidos de reembolso das suas vendas, do mais recente para o mais antigo — abertos pelo comprador ou por você. Cada pedido traz a situação, o motivo, o valor (quando parcial) e a venda a que pertence. Para ser avisado em tempo real, assine os webhooks `TRANSACTION_ASK_REFUNDING` (pedido recebido) e `TRANSACTION_REFUNDED` (estorno concluído). `GET /refunds` ## Autenticação - Token Bearer no header `Authorization` (esquema `BearerAuth`). Token de acesso devolvido por POST /auth/token. Envie como "Authorization: Bearer ". Vale 24 horas. ## Parâmetros de consulta | Parâmetro | Tipo | Obrigatório | Descrição | | --- | --- | --- | --- | | `page` | inteiro | não | Número da página, começando em 1. | | `per_page` | inteiro | não | Itens por página, de 1 a 100. | | `status` | texto | não | Situação do pedido (veja `Refund.status`). | | `sale_identifier` | texto | não | Código da venda — traz só os pedidos dessa venda. | | `created_from` | texto (date-time) | não | Solicitações de reembolso criadas a partir desta data e hora, incluindo ela. ISO 8601 com fuso. | | `created_to` | texto (date-time) | não | Solicitações de reembolso criadas até esta data e hora, incluindo ela. ISO 8601 com fuso. | | `customer_email` | texto (email) | não | E-mail do cliente da venda. E-mail completo, sem diferenciar maiúsculas de minúsculas. | | `customer_document` | texto | não | Documento do cliente da venda. CPF com 11 dígitos ou CNPJ com 14, com ou sem pontuação. Exemplo: `123.456.789-09`. | ## Respostas ### 200 — Lista de reembolsos | Campo | Tipo | Obrigatório | Descrição | | --- | --- | --- | --- | | `data` | [lista de Reembolso](/docs/referencia/entidades/reembolso) | não | — | | `meta` | objeto | não | — | | `meta.page` | inteiro | não | Exemplo: `1`. | | `meta.per_page` | inteiro | não | Exemplo: `25`. | | `meta.total` | inteiro | não | Exemplo: `143`. | | `meta.total_pages` | inteiro | não | Exemplo: `6`. | ## Erros | Status | Descrição | | --- | --- | | `400` | Filtro inválido | | `401` | Token ausente, inválido ou expirado, ou credencial revogada ou expirada. Chame POST /auth/token para obter um token novo. | | `403` | IP não autorizado | | `429` | Limite de requisições excedido | Lista completa de códigos e como tratá-los: [Erros](/docs/guias/fundamentos/erros). --- # Reembolsar uma venda URL: https://staging.pagpolar.com/docs/referencia/reembolsos/create-refund > Reembolsa uma venda sua. Como quem pede é o próprio vendedor, o pedido **já nasce aceito**: o estorno é enviado ao gateway na hora, a assinatura da venda é cancelada e os acessos do comprador são revogados. **Não há como desfazer.** O reembolso é sempre **total**. Reembolso de alguns itens continua só no painel. A venda precisa estar paga ou já com um pedido de reembolso aberto; em qualquer outra situação a resposta é `409`. Só existe um pedido em andamento por venda. A resposta traz a situação do pedido: `REFUNDING` enquanto o gateway não confirma e `FAILED` quando ele recusa o estorno (por exemplo, por falta de saldo de um co-produtor). A confirmação vem depois pelo webhook `TRANSACTION_REFUNDED`. O header `Idempotency-Key` é **obrigatório**: reenviar a mesma chave devolve a resposta original, sem pedir um segundo estorno. `POST /refunds` ## Autenticação - Token Bearer no header `Authorization` (esquema `BearerAuth`). Token de acesso devolvido por POST /auth/token. Envie como "Authorization: Bearer ". Vale 24 horas. ## Parâmetros de header | Parâmetro | Tipo | Obrigatório | Descrição | | --- | --- | --- | --- | | `Idempotency-Key` | texto | sim | Exemplo: `2f1c4d2e-8f4a-4c1e-9f7b-6a2d1e3c5b90`. | ## Corpo da requisição Content-type: `application/json`. | Campo | Tipo | Obrigatório | Descrição | | --- | --- | --- | --- | | `sale_identifier` | texto | sim | Código público da venda a reembolsar — o `identifier` que vem em `GET /sales`, na resposta da cobrança e no payload dos webhooks. São só dígitos; o prefixo e a URL do checkout que contenha o código também são aceitos. **Não** é o `id` (uuid) da venda: com o uuid a resposta é `404`. A venda precisa ser da própria conta da credencial. Exemplo: `PPO9876543210`. | | `requested_by` | enum | não | Quem pediu o reembolso, para o registro ficar fiel: `SELLER` quando a decisão foi sua e `CLIENT` quando o comprador pediu por fora (e-mail, telefone, atendimento). O campo só muda o registro, que volta em `requested_by` na consulta — o estorno é imediato nos dois casos, porque quem chama a rota é o vendedor. Valores: `SELLER`, `CLIENT`. Exemplo: `SELLER`. | | `reason` | texto | não | Motivo do reembolso, em texto livre. Fica gravado no pedido e aparece em `GET /refunds`. Exemplo: `Cliente desistiu da compra`. | | `customer_observation` | texto | não | Observação do comprador, quando houver. Exemplo: `Pediu o cancelamento por e-mail`. | ### Exemplo do corpo Reembolso total: ```json { "sale_identifier": "PPO9876543210", "reason": "Cliente desistiu da compra" } ``` ## Respostas ### 201 — Reembolso solicitado | Campo | Tipo | Obrigatório | Descrição | | --- | --- | --- | --- | | `data` | [Reembolso](/docs/referencia/entidades/reembolso) | não | — | ## Erros | Status | Descrição | | --- | --- | | `400` | Dados inválidos, Idempotency-Key ausente, ou já existe um pedido de reembolso em andamento para a venda | | `401` | Token ausente, inválido ou expirado, ou credencial revogada ou expirada. Chame POST /auth/token para obter um token novo. | | `403` | IP não autorizado | | `404` | Venda não encontrada | | `409` | A venda não está paga, não é possível reembolsar | | `429` | Limite de requisições excedido | | `503` | Serviço de limite indisponível — repita a requisição com a mesma Idempotency-Key | Lista completa de códigos e como tratá-los: [Erros](/docs/guias/fundamentos/erros). --- # Listar assinaturas URL: https://staging.pagpolar.com/docs/referencia/assinaturas/list-subscriptions `GET /subscriptions` ## Autenticação - Token Bearer no header `Authorization` (esquema `BearerAuth`). Token de acesso devolvido por POST /auth/token. Envie como "Authorization: Bearer ". Vale 24 horas. ## Parâmetros de consulta | Parâmetro | Tipo | Obrigatório | Descrição | | --- | --- | --- | --- | | `page` | inteiro | não | Número da página, começando em 1. | | `per_page` | inteiro | não | Itens por página, de 1 a 100. | | `status` | enum | não | Situação da assinatura. Valores: `DRAFT`, `PENDING_PAYMENT`, `ACTIVE`, `PENDING_RENEWAL`, `PROCESSING`, `CANCELING`, `CANCELED`, `ASK_REFUND`, `REFUNDED`, `ABANDONED`, `EXPIRED`, `FAILED`. | | `plan_id` | texto (uuid) | não | Assinaturas deste plano. | | `customer_email` | texto (email) | não | E-mail do cliente da assinatura. E-mail completo, sem diferenciar maiúsculas de minúsculas. | | `customer_document` | texto | não | Documento do cliente da assinatura. CPF com 11 dígitos ou CNPJ com 14, com ou sem pontuação. Exemplo: `123.456.789-09`. | | `created_from` | texto (date-time) | não | Assinaturas criadas a partir desta data e hora, incluindo ela. ISO 8601 com fuso. | | `created_to` | texto (date-time) | não | Assinaturas criadas até esta data e hora, incluindo ela. ISO 8601 com fuso. | ## Respostas ### 200 — Lista de assinaturas | Campo | Tipo | Obrigatório | Descrição | | --- | --- | --- | --- | | `data` | [lista de Assinatura](/docs/referencia/entidades/assinatura) | não | — | | `meta` | objeto | não | — | | `meta.page` | inteiro | não | Exemplo: `1`. | | `meta.per_page` | inteiro | não | Exemplo: `25`. | | `meta.total` | inteiro | não | Exemplo: `143`. | | `meta.total_pages` | inteiro | não | Exemplo: `6`. | ## Erros | Status | Descrição | | --- | --- | | `401` | Token ausente, inválido ou expirado, ou credencial revogada ou expirada. Chame POST /auth/token para obter um token novo. | | `403` | IP não autorizado | | `429` | Limite de requisições excedido | Lista completa de códigos e como tratá-los: [Erros](/docs/guias/fundamentos/erros). --- # Consultar assinatura URL: https://staging.pagpolar.com/docs/referencia/assinaturas/get-subscription `GET /subscriptions/{id}` ## Autenticação - Token Bearer no header `Authorization` (esquema `BearerAuth`). Token de acesso devolvido por POST /auth/token. Envie como "Authorization: Bearer ". Vale 24 horas. ## Parâmetros de caminho | Parâmetro | Tipo | Obrigatório | Descrição | | --- | --- | --- | --- | | `id` | texto (uuid) | sim | — | ## Respostas ### 200 — Assinatura | Campo | Tipo | Obrigatório | Descrição | | --- | --- | --- | --- | | `data` | [Assinatura](/docs/referencia/entidades/assinatura) | não | — | ## Erros | Status | Descrição | | --- | --- | | `401` | Token ausente, inválido ou expirado, ou credencial revogada ou expirada. Chame POST /auth/token para obter um token novo. | | `403` | IP não autorizado | | `404` | Assinatura não encontrada | | `429` | Limite de requisições excedido | Lista completa de códigos e como tratá-los: [Erros](/docs/guias/fundamentos/erros). --- # Cancelar assinatura URL: https://staging.pagpolar.com/docs/referencia/assinaturas/cancel-subscription > Solicita o cancelamento da assinatura. - **Boleto ou PIX**: cancelamento é imediato — a assinatura muda para `canceled`, o acesso é revogado de imediato nas integrações (MemberKit/Circle) e não há mais cobranças. - **Cartão de crédito**: o cancelamento é solicitado ao gateway de pagamento e a assinatura fica com status `canceling` até a confirmação (assíncrona). Cancelar uma assinatura que já está cancelada ou em outro status que não permite cancelamento não tem efeito (operação idempotente). `DELETE /subscriptions/{id}` ## Autenticação - Token Bearer no header `Authorization` (esquema `BearerAuth`). Token de acesso devolvido por POST /auth/token. Envie como "Authorization: Bearer ". Vale 24 horas. ## Parâmetros de caminho | Parâmetro | Tipo | Obrigatório | Descrição | | --- | --- | --- | --- | | `id` | texto (uuid) | sim | — | ## Respostas ### 200 — Cancelamento solicitado | Campo | Tipo | Obrigatório | Descrição | | --- | --- | --- | --- | | `data` | [Resultado da operação](/docs/referencia/entidades/resultado-da-operacao) | não | — | ## Erros | Status | Descrição | | --- | --- | | `401` | Token ausente, inválido ou expirado, ou credencial revogada ou expirada. Chame POST /auth/token para obter um token novo. | | `403` | IP não autorizado | | `404` | Assinatura não encontrada | | `429` | Limite de requisições excedido | Lista completa de códigos e como tratá-los: [Erros](/docs/guias/fundamentos/erros). --- # Trocar cartão da assinatura URL: https://staging.pagpolar.com/docs/referencia/assinaturas/update-subscription-card > Substitui o cartão de crédito usado nas cobranças de uma assinatura paga com cartão. O novo cartão é salvo no gateway e passa a ser cobrado a partir do próximo ciclo; o plano, o valor e as datas de cobrança não mudam. A troca é aceita em qualquer situação da assinatura — use-a, por exemplo, para regularizar uma assinatura cuja renovação foi recusada. Se o gateway não aceitar a troca naquele estado, a requisição retorna `400` com a mensagem do gateway. A troca não gera cobrança, por isso não exige `Idempotency-Key`. Conta no limite por minuto da credencial, como qualquer rota. `PATCH /subscriptions/{id}/card` ## Autenticação - Token Bearer no header `Authorization` (esquema `BearerAuth`). Token de acesso devolvido por POST /auth/token. Envie como "Authorization: Bearer ". Vale 24 horas. ## Parâmetros de caminho | Parâmetro | Tipo | Obrigatório | Descrição | | --- | --- | --- | --- | | `id` | texto (uuid) | sim | — | ## Corpo da requisição Content-type: `application/json`. | Campo | Tipo | Obrigatório | Descrição | | --- | --- | --- | --- | | `credit_card` | objeto | sim | Dados do cartão de crédito usado na cobrança. | | `credit_card.holder_name` | texto | sim | Exemplo: `FULANO DE TAL`. | | `credit_card.holder_document` | texto | sim | CPF (11 dígitos) ou CNPJ (14 dígitos) do titular do cartão, com ou sem pontuação. A API grava só os dígitos. Fora disso, 400. Exemplo: `12345678909`. | | `credit_card.number` | texto | sim | Exemplo: `4111111111111111`. | | `credit_card.expiration_month` | inteiro | sim | Exemplo: `12`. | | `credit_card.expiration_year` | inteiro | sim | Exemplo: `2030`. | | `credit_card.cvv` | texto | sim | Exemplo: `123`. | ### Exemplo do corpo Novo cartão: ```json { "credit_card": { "holder_name": "FULANO DE TAL", "holder_document": "12345678909", "number": "4111111111111111", "expiration_month": 12, "expiration_year": 2030, "cvv": "123" } } ``` ## Respostas ### 200 — Cartão da assinatura atualizado | Campo | Tipo | Obrigatório | Descrição | | --- | --- | --- | --- | | `data` | [Resultado da operação](/docs/referencia/entidades/resultado-da-operacao) | não | — | ## Erros | Status | Descrição | | --- | --- | | `400` | Dados de cartão inválidos, assinatura sem cobrança no cartão ou troca recusada pelo gateway | | `401` | Token ausente, inválido ou expirado, ou credencial revogada ou expirada. Chame POST /auth/token para obter um token novo. | | `403` | IP não autorizado | | `404` | Assinatura não encontrada | | `429` | Limite de requisições por minuto da credencial excedido na troca de cartão | | `503` | Serviço de limite indisponível — repita a requisição com a mesma Idempotency-Key | Lista completa de códigos e como tratá-los: [Erros](/docs/guias/fundamentos/erros). --- # Listar opções de troca de plano URL: https://staging.pagpolar.com/docs/referencia/assinaturas/list-subscription-plan-options > Lista os planos do mesmo produto disponíveis para troca (upgrade ou downgrade) nesta assinatura, e se o produto permite troca de plano pelo cliente. `GET /subscriptions/{id}/plan-options` ## Autenticação - Token Bearer no header `Authorization` (esquema `BearerAuth`). Token de acesso devolvido por POST /auth/token. Envie como "Authorization: Bearer ". Vale 24 horas. ## Parâmetros de caminho | Parâmetro | Tipo | Obrigatório | Descrição | | --- | --- | --- | --- | | `id` | texto (uuid) | sim | — | ## Respostas ### 200 — Opções de troca de plano | Campo | Tipo | Obrigatório | Descrição | | --- | --- | --- | --- | | `data` | [Opções de troca de plano](/docs/referencia/entidades/opcoes-de-troca-de-plano) | não | — | ## Erros | Status | Descrição | | --- | --- | | `401` | Token ausente, inválido ou expirado, ou credencial revogada ou expirada. Chame POST /auth/token para obter um token novo. | | `403` | IP não autorizado | | `404` | Assinatura não encontrada | | `429` | Limite de requisições excedido | Lista completa de códigos e como tratá-los: [Erros](/docs/guias/fundamentos/erros). --- # Calcular preview de troca de plano URL: https://staging.pagpolar.com/docs/referencia/assinaturas/preview-subscription-plan-change > Calcula o crédito ou cobrança proporcional de uma troca de plano sem efetivá-la. Não tem efeito colateral. `POST /subscriptions/{id}/plan-change/preview` ## Autenticação - Token Bearer no header `Authorization` (esquema `BearerAuth`). Token de acesso devolvido por POST /auth/token. Envie como "Authorization: Bearer ". Vale 24 horas. ## Parâmetros de caminho | Parâmetro | Tipo | Obrigatório | Descrição | | --- | --- | --- | --- | | `id` | texto (uuid) | sim | — | ## Corpo da requisição Content-type: `application/json`. | Campo | Tipo | Obrigatório | Descrição | | --- | --- | --- | --- | | `new_product_price_id` | texto (uuid) | sim | — | ### Exemplo do corpo Preview de troca: ```json { "new_product_price_id": "b2c3d4e5-f6a7-4b8c-9d0e-1f2a3b4c5d6e" } ``` ## Respostas ### 200 — Preview calculado | Campo | Tipo | Obrigatório | Descrição | | --- | --- | --- | --- | | `data` | [Prévia da troca de plano](/docs/referencia/entidades/previa-da-troca-de-plano) | não | — | ## Erros | Status | Descrição | | --- | --- | | `400` | Plano não pertence ao mesmo produto ou não é selecionável | | `401` | Token ausente, inválido ou expirado, ou credencial revogada ou expirada. Chame POST /auth/token para obter um token novo. | | `403` | IP não autorizado | | `404` | Assinatura ou plano não encontrado | | `429` | Limite de requisições excedido | Lista completa de códigos e como tratá-los: [Erros](/docs/guias/fundamentos/erros). --- # Executar upgrade ou downgrade de plano URL: https://staging.pagpolar.com/docs/referencia/assinaturas/change-subscription-plan > Executa a troca de plano de uma assinatura. A direção (upgrade ou downgrade) é determinada automaticamente pela comparação de preço entre o plano atual e o novo plano. - **Upgrade**: cobra a diferença proporcional imediatamente, no cartão salvo da assinatura (`payment_choice=current`) ou em um novo cartão informado no corpo da requisição (`payment_choice=new_card`). - **Upgrade no cartão aprovado, mas sem a troca concluída na hora** (por exemplo, falha no gateway ao atualizar o plano): a resposta é `200` com `upgrade.status` `pending`, e a venda da diferença continua em `PROCESSING`. A troca é confirmada quando o gateway avisa o pagamento; nesse momento a venda passa a `PAID` e o webhook `TRANSACTION_PAID` é enviado. Não cobre de novo. - **Downgrade**: não gera cobrança imediata; é agendado para entrar em vigor na próxima renovação da assinatura. O header `Idempotency-Key` é **obrigatório**. Em caso de timeout, reenvie a requisição com a mesma chave: a resposta original será devolvida sem processar a troca duas vezes. `POST /subscriptions/{id}/plan-change` ## Autenticação - Token Bearer no header `Authorization` (esquema `BearerAuth`). Token de acesso devolvido por POST /auth/token. Envie como "Authorization: Bearer ". Vale 24 horas. ## Parâmetros de caminho | Parâmetro | Tipo | Obrigatório | Descrição | | --- | --- | --- | --- | | `id` | texto (uuid) | sim | — | ## Parâmetros de header | Parâmetro | Tipo | Obrigatório | Descrição | | --- | --- | --- | --- | | `Idempotency-Key` | texto | sim | Exemplo: `3a2b1c4d-9e8f-4a7b-8c6d-5e4f3a2b1c0d`. | ## Corpo da requisição Content-type: `application/json`. | Campo | Tipo | Obrigatório | Descrição | | --- | --- | --- | --- | | `new_product_price_id` | texto (uuid) | sim | — | | `payment_choice` | enum | não | Só é relevante para upgrade. Ignorado em downgrade. Valores: `current`, `new_card`. | | `card` | objeto | não | Obrigatório somente quando payment_choice=new_card | | `card.number` | texto | não | — | | `card.holder_name` | texto | não | — | | `card.holder_document` | texto | não | CPF (11 dígitos) ou CNPJ (14 dígitos) do titular, com ou sem pontuação. Fora disso, 400. | | `card.exp_month` | inteiro | não | — | | `card.exp_year` | inteiro | não | — | | `card.cvv` | texto | não | — | ### Exemplo do corpo Upgrade cobrando no cartão salvo: ```json { "new_product_price_id": "b2c3d4e5-f6a7-4b8c-9d0e-1f2a3b4c5d6e", "payment_choice": "current" } ``` Upgrade com novo cartão: ```json { "new_product_price_id": "b2c3d4e5-f6a7-4b8c-9d0e-1f2a3b4c5d6e", "payment_choice": "new_card", "card": { "number": "4111111111111111", "holder_name": "FULANO DE TAL", "holder_document": "12345678909", "exp_month": 12, "exp_year": 2030, "cvv": "123" } } ``` Downgrade (agendado para a próxima renovação): ```json { "new_product_price_id": "c3d4e5f6-a7b8-4c9d-0e1f-2a3b4c5d6e7f" } ``` ## Respostas ### 200 — Troca de plano processada | Campo | Tipo | Obrigatório | Descrição | | --- | --- | --- | --- | | `data` | [Troca de plano](/docs/referencia/entidades/troca-de-plano) | não | — | #### Exemplo Upgrade cobrado via PIX: ```json { "data": { "type": "UPGRADE", "upgrade": { "transaction_id": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d", "charge_amount": 49.9, "status": "pending", "pix": { "qr_code": "00020126..." } } } } ``` Downgrade agendado: ```json { "data": { "type": "DOWNGRADE" } } ``` ## Erros | Status | Descrição | | --- | --- | | `400` | Dados inválidos ou Idempotency-Key ausente | | `401` | Token ausente, inválido ou expirado, ou credencial revogada ou expirada. Chame POST /auth/token para obter um token novo. | | `403` | Produto não permite troca de plano pelo cliente, plano não selecionável ou IP não autorizado | | `404` | Assinatura ou plano não encontrado | | `409` | Requisição idêntica em processamento | | `429` | Limite de requisições excedido | | `503` | Serviço de limite indisponível — repita a requisição com a mesma Idempotency-Key | Lista completa de códigos e como tratá-los: [Erros](/docs/guias/fundamentos/erros). --- # Listar clientes URL: https://staging.pagpolar.com/docs/referencia/clientes/list-customers > Documento e telefone são sempre mascarados nesta API. `GET /customers` ## Autenticação - Token Bearer no header `Authorization` (esquema `BearerAuth`). Token de acesso devolvido por POST /auth/token. Envie como "Authorization: Bearer ". Vale 24 horas. ## Parâmetros de consulta | Parâmetro | Tipo | Obrigatório | Descrição | | --- | --- | --- | --- | | `page` | inteiro | não | Número da página, começando em 1. | | `per_page` | inteiro | não | Itens por página, de 1 a 100. | | `email` | texto (email) | não | E-mail do cliente. E-mail completo, sem diferenciar maiúsculas de minúsculas. | | `name` | texto | não | Nome do cliente. Busca por parte do texto, sem diferenciar maiúsculas de minúsculas. | | `document` | texto | não | Documento do cliente. CPF com 11 dígitos ou CNPJ com 14, com ou sem pontuação. Exemplo: `123.456.789-09`. | ## Respostas ### 200 — Lista de clientes | Campo | Tipo | Obrigatório | Descrição | | --- | --- | --- | --- | | `data` | [lista de Cliente](/docs/referencia/entidades/cliente) | não | — | | `meta` | objeto | não | — | | `meta.page` | inteiro | não | Exemplo: `1`. | | `meta.per_page` | inteiro | não | Exemplo: `25`. | | `meta.total` | inteiro | não | Exemplo: `143`. | | `meta.total_pages` | inteiro | não | Exemplo: `6`. | ## Erros | Status | Descrição | | --- | --- | | `401` | Token ausente, inválido ou expirado, ou credencial revogada ou expirada. Chame POST /auth/token para obter um token novo. | | `403` | IP não autorizado | | `429` | Limite de requisições excedido | Lista completa de códigos e como tratá-los: [Erros](/docs/guias/fundamentos/erros). --- # Consultar cliente URL: https://staging.pagpolar.com/docs/referencia/clientes/get-customer `GET /customers/{id}` ## Autenticação - Token Bearer no header `Authorization` (esquema `BearerAuth`). Token de acesso devolvido por POST /auth/token. Envie como "Authorization: Bearer ". Vale 24 horas. ## Parâmetros de caminho | Parâmetro | Tipo | Obrigatório | Descrição | | --- | --- | --- | --- | | `id` | texto (uuid) | sim | — | ## Respostas ### 200 — Cliente | Campo | Tipo | Obrigatório | Descrição | | --- | --- | --- | --- | | `data` | [Cliente](/docs/referencia/entidades/cliente) | não | — | ## Erros | Status | Descrição | | --- | --- | | `401` | Token ausente, inválido ou expirado, ou credencial revogada ou expirada. Chame POST /auth/token para obter um token novo. | | `403` | IP não autorizado | | `404` | Cliente não encontrado | | `429` | Limite de requisições excedido | Lista completa de códigos e como tratá-los: [Erros](/docs/guias/fundamentos/erros). --- # Fazer uma venda no PIX URL: https://staging.pagpolar.com/docs/referencia/vendas/create-pix-payment > Cria uma cobrança PIX em cima de uma oferta existente. O método de pagamento é definido pela própria rota, então não é preciso enviar `payment_method` no corpo. O header `Idempotency-Key` é **obrigatório**. Em caso de timeout, reenvie a requisição com a mesma chave: a resposta original será devolvida sem gerar uma segunda cobrança. A resposta desta rota traz o PIX **gerado** (`pix.qr_code`), não o **pago**. A confirmação do pagamento acontece de forma assíncrona — assine o webhook `TRANSACTION_PAID` para ser notificado assim que o PIX for pago, ou consulte `GET /sales/{identifier}` usando o `transactions[0]` da resposta para checar o `status` a qualquer momento. Esta rota **não cria assinaturas**: mesmo apontando para uma oferta recorrente, o resultado é sempre uma cobrança avulsa (`subscriptions` sempre vem vazio na resposta). `POST /payments/pix` ## Autenticação - Token Bearer no header `Authorization` (esquema `BearerAuth`). Token de acesso devolvido por POST /auth/token. Envie como "Authorization: Bearer ". Vale 24 horas. ## Parâmetros de header | Parâmetro | Tipo | Obrigatório | Descrição | | --- | --- | --- | --- | | `Idempotency-Key` | texto | sim | Exemplo: `2f1c4d2e-8f4a-4c1e-9f7b-6a2d1e3c5b90`. | ## Corpo da requisição Content-type: `application/json`. | Campo | Tipo | Obrigatório | Descrição | | --- | --- | --- | --- | | `offer_identifier` | texto | não | Código de uma oferta existente. Envie este campo **ou** `offer` — nunca os dois. Exemplo: `PPP1234567890`. | | `offer` | objeto | não | Oferta informada na hora. Envie este objeto **ou** `offer_identifier` — nunca os dois. Reaproveita a oferta com mesmo produto, nome, valor e visibilidade; senão cria. | | `offer.product_id` | texto (uuid) | sim | Produto da sua conta. | | `offer.name` | texto | sim | Nome da oferta. Exemplo: `Consultoria avulsa`. | | `offer.value` | inteiro | sim | Valor em centavos. Mínimo 500 (R$ 5,00). Exemplo: `4990`. | | `offer.createOffer` | booleano | sim | `true`: oferta visível. `false`: oferta oculta, fora das listagens e usável só por esta API. Exemplo: `false`. | | `quantity` | inteiro | não | — | | `affiliate_identifier` | texto | não | Código do afiliado que trouxe a venda, no formato `PAO` seguido de 10 dígitos. Formato inválido retorna `400`. Código de afiliado inexistente, de outro produto, inativo ou sem a oferta liberada é **ignorado**: a venda é processada normalmente, sem afiliado. Não há cookie nem regra de primeiro ou último clique — vale o código enviado. Exemplo: `PAO1234567890`. | | `external_reference` | texto | não | Referência do pedido no seu sistema (por exemplo, o número do pedido). Opcional. Depois você consulta a venda por ela em `GET /sales/{identifier}` e filtra em `GET /sales?external_reference=`. Pode se repetir entre pedidos: a consulta devolve a venda mais recente com essa referência. Exemplo: `PED-2026-0001`. | | `customer` | objeto | sim | — | | `customer.name` | texto | sim | Exemplo: `Fulano de Tal`. | | `customer.email` | texto (email) | sim | Exemplo: `fulano@exemplo.com`. | | `customer.document` | texto | sim | CPF (11 dígitos) ou CNPJ (14 dígitos), com ou sem pontuação: `12345678909` ou `123.456.789-09`. A API grava só os dígitos. Fora disso, 400. Exemplo: `12345678909`. | | `customer.phone` | texto | sim | DDD e número, com 10 ou 11 dígitos (12 ou 13 com o código do país 55). Pontuação, espaços, parênteses e `+` são aceitos: `11999998888`, `(11) 99999-8888` ou `+55 11 99999-8888`. A API grava só os dígitos. Fora disso, 400. Exemplo: `11999999999`. | | `address` | objeto | não | Obrigatório quando a oferta é de produto físico (`requires_shipping: true`); nos demais casos é opcional e não é usado no processamento da venda. | | `address.street` | texto | sim | Exemplo: `Rua das Flores`. | | `address.number` | texto | sim | Exemplo: `123`. | | `address.complement` | texto | não | Exemplo: `Apto 4B`. Pode ser nulo. | | `address.neighborhood` | texto | sim | Exemplo: `Centro`. | | `address.city` | texto | sim | Exemplo: `São Paulo`. | | `address.state` | texto | sim | Exemplo: `SP`. | | `address.postal_code` | texto | sim | Exemplo: `01000-000`. | | `shipping_option_id` | texto | não | Opção de frete escolhida, obrigatória quando a oferta é de produto físico (`requires_shipping: true`). Use o `id` devolvido por `GET /offers/{identifier}/shipping` para o mesmo CEP enviado em `address.postal_code`. Enviar este campo numa oferta que não exige frete retorna `400`. Exemplo: `wil_a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d`. | | `buyer_ip` | texto | não | IP do comprador final. Melhora a análise antifraude. Exemplo: `203.0.113.10`. | | `buyer_user_agent` | texto | não | User agent do comprador final. | ### Exemplo do corpo PIX com offer_identifier (sem offer): ```json { "offer_identifier": "PPP1234567890", "customer": { "name": "Fulano de Tal", "email": "fulano@exemplo.com", "document": "12345678909", "phone": "11999999999" }, "buyer_ip": "203.0.113.10" } ``` PIX com offer (sem offer_identifier): ```json { "offer": { "product_id": "c4d5e6f7-a8b9-4c0d-1e2f-3a4b5c6d7e8f", "name": "Consultoria avulsa", "value": 4990, "createOffer": false }, "customer": { "name": "Fulano de Tal", "email": "fulano@exemplo.com", "document": "12345678909", "phone": "11999999999" }, "buyer_ip": "203.0.113.10" } ``` ## Respostas ### 201 — Pagamento PIX criado | Campo | Tipo | Obrigatório | Descrição | | --- | --- | --- | --- | | `data` | [Resultado da cobrança](/docs/referencia/entidades/resultado-da-cobranca) | não | — | #### Exemplo PIX: ```json { "data": { "offer_identifier": "PPP1234567890", "transactions": [ "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d" ], "subscriptions": [], "pix": { "qr_code": "00020126..." } } } ``` ## Erros | Status | Descrição | | --- | --- | | `400` | Dados inválidos, Idempotency-Key ausente, número de parcelas acima do limite da oferta, ou documento/telefone fora do formato (CPF 11 dígitos, CNPJ 14, telefone 10 a 13 dígitos) | | `401` | Token ausente, inválido ou expirado, ou credencial revogada ou expirada. Chame POST /auth/token para obter um token novo. | | `403` | IP não autorizado | | `404` | Oferta não encontrada | | `409` | Oferta inativa/expirada, método não habilitado ou requisição idêntica em processamento | | `429` | Limite de requisições excedido | | `503` | Serviço de limite indisponível — repita a requisição com a mesma Idempotency-Key | Lista completa de códigos e como tratá-los: [Erros](/docs/guias/fundamentos/erros). --- # Fazer uma venda no boleto URL: https://staging.pagpolar.com/docs/referencia/vendas/create-boleto-payment > Cria uma cobrança em boleto em cima de uma oferta existente. O método de pagamento é definido pela própria rota, então não é preciso enviar `payment_method` no corpo. O header `Idempotency-Key` é **obrigatório**. Em caso de timeout, reenvie a requisição com a mesma chave: a resposta original será devolvida sem gerar uma segunda cobrança. Esta rota **não cria assinaturas**: mesmo apontando para uma oferta recorrente, o resultado é sempre uma cobrança avulsa (`subscriptions` sempre vem vazio na resposta). `POST /payments/boleto` ## Autenticação - Token Bearer no header `Authorization` (esquema `BearerAuth`). Token de acesso devolvido por POST /auth/token. Envie como "Authorization: Bearer ". Vale 24 horas. ## Parâmetros de header | Parâmetro | Tipo | Obrigatório | Descrição | | --- | --- | --- | --- | | `Idempotency-Key` | texto | sim | Exemplo: `2f1c4d2e-8f4a-4c1e-9f7b-6a2d1e3c5b90`. | ## Corpo da requisição Content-type: `application/json`. | Campo | Tipo | Obrigatório | Descrição | | --- | --- | --- | --- | | `offer_identifier` | texto | não | Código de uma oferta existente. Envie este campo **ou** `offer` — nunca os dois. Exemplo: `PPP1234567890`. | | `offer` | objeto | não | Oferta informada na hora. Envie este objeto **ou** `offer_identifier` — nunca os dois. Reaproveita a oferta com mesmo produto, nome, valor e visibilidade; senão cria. | | `offer.product_id` | texto (uuid) | sim | Produto da sua conta. | | `offer.name` | texto | sim | Nome da oferta. Exemplo: `Consultoria avulsa`. | | `offer.value` | inteiro | sim | Valor em centavos. Mínimo 500 (R$ 5,00). Exemplo: `4990`. | | `offer.createOffer` | booleano | sim | `true`: oferta visível. `false`: oferta oculta, fora das listagens e usável só por esta API. Exemplo: `false`. | | `quantity` | inteiro | não | — | | `affiliate_identifier` | texto | não | Código do afiliado que trouxe a venda, no formato `PAO` seguido de 10 dígitos. Formato inválido retorna `400`. Código de afiliado inexistente, de outro produto, inativo ou sem a oferta liberada é **ignorado**: a venda é processada normalmente, sem afiliado. Não há cookie nem regra de primeiro ou último clique — vale o código enviado. Exemplo: `PAO1234567890`. | | `external_reference` | texto | não | Referência do pedido no seu sistema (por exemplo, o número do pedido). Opcional. Depois você consulta a venda por ela em `GET /sales/{identifier}` e filtra em `GET /sales?external_reference=`. Pode se repetir entre pedidos: a consulta devolve a venda mais recente com essa referência. Exemplo: `PED-2026-0001`. | | `customer` | objeto | sim | — | | `customer.name` | texto | sim | Exemplo: `Fulano de Tal`. | | `customer.email` | texto (email) | sim | Exemplo: `fulano@exemplo.com`. | | `customer.document` | texto | sim | CPF (11 dígitos) ou CNPJ (14 dígitos), com ou sem pontuação: `12345678909` ou `123.456.789-09`. A API grava só os dígitos. Fora disso, 400. Exemplo: `12345678909`. | | `customer.phone` | texto | sim | DDD e número, com 10 ou 11 dígitos (12 ou 13 com o código do país 55). Pontuação, espaços, parênteses e `+` são aceitos: `11999998888`, `(11) 99999-8888` ou `+55 11 99999-8888`. A API grava só os dígitos. Fora disso, 400. Exemplo: `11999999999`. | | `address` | objeto | não | Obrigatório quando a oferta é de produto físico (`requires_shipping: true`); nos demais casos é opcional e não é usado no processamento da venda. | | `address.street` | texto | sim | Exemplo: `Rua das Flores`. | | `address.number` | texto | sim | Exemplo: `123`. | | `address.complement` | texto | não | Exemplo: `Apto 4B`. Pode ser nulo. | | `address.neighborhood` | texto | sim | Exemplo: `Centro`. | | `address.city` | texto | sim | Exemplo: `São Paulo`. | | `address.state` | texto | sim | Exemplo: `SP`. | | `address.postal_code` | texto | sim | Exemplo: `01000-000`. | | `shipping_option_id` | texto | não | Opção de frete escolhida, obrigatória quando a oferta é de produto físico (`requires_shipping: true`). Use o `id` devolvido por `GET /offers/{identifier}/shipping` para o mesmo CEP enviado em `address.postal_code`. Enviar este campo numa oferta que não exige frete retorna `400`. Exemplo: `wil_a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d`. | | `buyer_ip` | texto | não | IP do comprador final. Melhora a análise antifraude. Exemplo: `203.0.113.10`. | | `buyer_user_agent` | texto | não | User agent do comprador final. | ### Exemplo do corpo Boleto com offer_identifier (sem offer): ```json { "offer_identifier": "PPP1234567890", "customer": { "name": "Fulano de Tal", "email": "fulano@exemplo.com", "document": "12345678909", "phone": "11999999999" }, "buyer_ip": "203.0.113.10" } ``` Boleto com offer (sem offer_identifier): ```json { "offer": { "product_id": "c4d5e6f7-a8b9-4c0d-1e2f-3a4b5c6d7e8f", "name": "Consultoria avulsa", "value": 4990, "createOffer": false }, "customer": { "name": "Fulano de Tal", "email": "fulano@exemplo.com", "document": "12345678909", "phone": "11999999999" }, "buyer_ip": "203.0.113.10" } ``` ## Respostas ### 201 — Pagamento em boleto criado | Campo | Tipo | Obrigatório | Descrição | | --- | --- | --- | --- | | `data` | [Resultado da cobrança](/docs/referencia/entidades/resultado-da-cobranca) | não | — | #### Exemplo Boleto: ```json { "data": { "offer_identifier": "PPP1234567890", "transactions": [ "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d" ], "subscriptions": [], "boleto": { "barcode": "34191.79001 01043.510047 91020.150008 1 96610000015000", "pdf_link": "https://boletos.pagpolar.com/a1b2c3d4.pdf" } } } ``` ## Erros | Status | Descrição | | --- | --- | | `400` | Dados inválidos, Idempotency-Key ausente, número de parcelas acima do limite da oferta, ou documento/telefone fora do formato (CPF 11 dígitos, CNPJ 14, telefone 10 a 13 dígitos) | | `401` | Token ausente, inválido ou expirado, ou credencial revogada ou expirada. Chame POST /auth/token para obter um token novo. | | `403` | IP não autorizado | | `404` | Oferta não encontrada | | `409` | Oferta inativa/expirada, método não habilitado ou requisição idêntica em processamento | | `429` | Limite de requisições excedido | | `503` | Serviço de limite indisponível — repita a requisição com a mesma Idempotency-Key | Lista completa de códigos e como tratá-los: [Erros](/docs/guias/fundamentos/erros). --- # Fazer uma venda no cartão de crédito URL: https://staging.pagpolar.com/docs/referencia/vendas/create-credit-card-payment > Cria uma cobrança em cartão de crédito em cima de uma oferta existente. O método de pagamento é definido pela própria rota, então não é preciso enviar `payment_method` no corpo. O header `Idempotency-Key` é **obrigatório**. Em caso de timeout, reenvie a requisição com a mesma chave: a resposta original será devolvida sem gerar uma segunda cobrança. A cobrança no cartão pode ser confirmada ou recusada pelo gateway na hora ou depois da resposta desta requisição. Aprovada, chega o webhook `TRANSACTION_PAID`. **Recusada, a venda fica `FAILED` e nenhum evento é enviado** — nem `TRANSACTION_CANCELED`. Confira o `status` consultando `GET /sales/{identifier}` com o `transactions[0]` da resposta. Esta rota **não cria assinaturas**: mesmo apontando para uma oferta recorrente, o resultado é sempre uma cobrança avulsa (`subscriptions` sempre vem vazio na resposta). `POST /payments/credit-card` ## Autenticação - Token Bearer no header `Authorization` (esquema `BearerAuth`). Token de acesso devolvido por POST /auth/token. Envie como "Authorization: Bearer ". Vale 24 horas. ## Parâmetros de header | Parâmetro | Tipo | Obrigatório | Descrição | | --- | --- | --- | --- | | `Idempotency-Key` | texto | sim | Exemplo: `2f1c4d2e-8f4a-4c1e-9f7b-6a2d1e3c5b90`. | ## Corpo da requisição Content-type: `application/json`. | Campo | Tipo | Obrigatório | Descrição | | --- | --- | --- | --- | | `offer_identifier` | texto | não | Código de uma oferta existente. Envie este campo **ou** `offer` — nunca os dois. Exemplo: `PPP1234567890`. | | `offer` | objeto | não | Oferta informada na hora. Envie este objeto **ou** `offer_identifier` — nunca os dois. Reaproveita a oferta com mesmo produto, nome, valor e visibilidade; senão cria. | | `offer.product_id` | texto (uuid) | sim | Produto da sua conta. | | `offer.name` | texto | sim | Nome da oferta. Exemplo: `Consultoria avulsa`. | | `offer.value` | inteiro | sim | Valor em centavos. Mínimo 500 (R$ 5,00). Exemplo: `4990`. | | `offer.createOffer` | booleano | sim | `true`: oferta visível. `false`: oferta oculta, fora das listagens e usável só por esta API. Exemplo: `false`. | | `quantity` | inteiro | não | — | | `affiliate_identifier` | texto | não | Código do afiliado que trouxe a venda, no formato `PAO` seguido de 10 dígitos. Formato inválido retorna `400`. Código de afiliado inexistente, de outro produto, inativo ou sem a oferta liberada é **ignorado**: a venda é processada normalmente, sem afiliado. Não há cookie nem regra de primeiro ou último clique — vale o código enviado. Exemplo: `PAO1234567890`. | | `external_reference` | texto | não | Referência do pedido no seu sistema (por exemplo, o número do pedido). Opcional. Depois você consulta a venda por ela em `GET /sales/{identifier}` e filtra em `GET /sales?external_reference=`. Pode se repetir entre pedidos: a consulta devolve a venda mais recente com essa referência. Exemplo: `PED-2026-0001`. | | `customer` | objeto | sim | — | | `customer.name` | texto | sim | Exemplo: `Fulano de Tal`. | | `customer.email` | texto (email) | sim | Exemplo: `fulano@exemplo.com`. | | `customer.document` | texto | sim | CPF (11 dígitos) ou CNPJ (14 dígitos), com ou sem pontuação: `12345678909` ou `123.456.789-09`. A API grava só os dígitos. Fora disso, 400. Exemplo: `12345678909`. | | `customer.phone` | texto | sim | DDD e número, com 10 ou 11 dígitos (12 ou 13 com o código do país 55). Pontuação, espaços, parênteses e `+` são aceitos: `11999998888`, `(11) 99999-8888` ou `+55 11 99999-8888`. A API grava só os dígitos. Fora disso, 400. Exemplo: `11999999999`. | | `address` | objeto | não | Obrigatório quando a oferta é de produto físico (`requires_shipping: true`); nos demais casos é opcional e não é usado no processamento da venda. | | `address.street` | texto | sim | Exemplo: `Rua das Flores`. | | `address.number` | texto | sim | Exemplo: `123`. | | `address.complement` | texto | não | Exemplo: `Apto 4B`. Pode ser nulo. | | `address.neighborhood` | texto | sim | Exemplo: `Centro`. | | `address.city` | texto | sim | Exemplo: `São Paulo`. | | `address.state` | texto | sim | Exemplo: `SP`. | | `address.postal_code` | texto | sim | Exemplo: `01000-000`. | | `shipping_option_id` | texto | não | Opção de frete escolhida, obrigatória quando a oferta é de produto físico (`requires_shipping: true`). Use o `id` devolvido por `GET /offers/{identifier}/shipping` para o mesmo CEP enviado em `address.postal_code`. Enviar este campo numa oferta que não exige frete retorna `400`. Exemplo: `wil_a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d`. | | `buyer_ip` | texto | não | IP do comprador final. Melhora a análise antifraude. Exemplo: `203.0.113.10`. | | `buyer_user_agent` | texto | não | User agent do comprador final. | | `installments` | inteiro | sim | O máximo real pode ser menor que 12: é limitado pela configuração da oferta (consulte max_credit_card_installments em GET /offers/{identifier}). Acima do limite da oferta, retorna 400. Exemplo: `3`. | | `credit_card` | objeto | sim | Dados do cartão de crédito usado na cobrança. | | `credit_card.holder_name` | texto | sim | Exemplo: `FULANO DE TAL`. | | `credit_card.holder_document` | texto | sim | CPF (11 dígitos) ou CNPJ (14 dígitos) do titular do cartão, com ou sem pontuação. A API grava só os dígitos. Fora disso, 400. Exemplo: `12345678909`. | | `credit_card.number` | texto | sim | Exemplo: `4111111111111111`. | | `credit_card.expiration_month` | inteiro | sim | Exemplo: `12`. | | `credit_card.expiration_year` | inteiro | sim | Exemplo: `2030`. | | `credit_card.cvv` | texto | sim | Exemplo: `123`. | ### Exemplo do corpo Cartão com offer_identifier (sem offer): ```json { "offer_identifier": "PPP1234567890", "installments": 3, "customer": { "name": "Fulano de Tal", "email": "fulano@exemplo.com", "document": "12345678909", "phone": "11999999999" }, "credit_card": { "holder_name": "FULANO DE TAL", "holder_document": "12345678909", "number": "4111111111111111", "expiration_month": 12, "expiration_year": 2030, "cvv": "123" }, "buyer_ip": "203.0.113.10" } ``` Cartão com offer (sem offer_identifier): ```json { "offer": { "product_id": "c4d5e6f7-a8b9-4c0d-1e2f-3a4b5c6d7e8f", "name": "Consultoria avulsa", "value": 4990, "createOffer": false }, "installments": 3, "customer": { "name": "Fulano de Tal", "email": "fulano@exemplo.com", "document": "12345678909", "phone": "11999999999" }, "credit_card": { "holder_name": "FULANO DE TAL", "holder_document": "12345678909", "number": "4111111111111111", "expiration_month": 12, "expiration_year": 2030, "cvv": "123" }, "buyer_ip": "203.0.113.10" } ``` ## Respostas ### 201 — Pagamento em cartão de crédito criado | Campo | Tipo | Obrigatório | Descrição | | --- | --- | --- | --- | | `data` | [Resultado da cobrança](/docs/referencia/entidades/resultado-da-cobranca) | não | — | #### Exemplo Cartão de crédito: ```json { "data": { "offer_identifier": "PPP1234567890", "transactions": [ "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d" ], "subscriptions": [] } } ``` ## Erros | Status | Descrição | | --- | --- | | `400` | Dados inválidos, Idempotency-Key ausente, número de parcelas acima do limite da oferta, ou documento/telefone fora do formato (CPF 11 dígitos, CNPJ 14, telefone 10 a 13 dígitos) | | `401` | Token ausente, inválido ou expirado, ou credencial revogada ou expirada. Chame POST /auth/token para obter um token novo. | | `403` | IP não autorizado | | `404` | Oferta não encontrada | | `409` | Oferta inativa/expirada, método não habilitado ou requisição idêntica em processamento | | `429` | Limite de requisições excedido | | `503` | Serviço de limite indisponível — repita a requisição com a mesma Idempotency-Key | Lista completa de códigos e como tratá-los: [Erros](/docs/guias/fundamentos/erros). --- # Consultar pagamento URL: https://staging.pagpolar.com/docs/referencia/vendas/get-payment > Mesma consulta de `GET /sales/{identifier}`: aceita o `id` da venda (uuid), o código da venda (10 dígitos, com ou sem o prefixo, ou link terminado nele) ou a `external_reference` enviada no pedido, testados nesta ordem. `GET /payments/{identifier}` ## Autenticação - Token Bearer no header `Authorization` (esquema `BearerAuth`). Token de acesso devolvido por POST /auth/token. Envie como "Authorization: Bearer ". Vale 24 horas. ## Parâmetros de caminho | Parâmetro | Tipo | Obrigatório | Descrição | | --- | --- | --- | --- | | `identifier` | texto | sim | Id (uuid), código ou referência externa da venda. | ## Respostas ### 200 — Venda correspondente ao pagamento | Campo | Tipo | Obrigatório | Descrição | | --- | --- | --- | --- | | `data` | [Venda](/docs/referencia/entidades/venda) | não | — | ## Erros | Status | Descrição | | --- | --- | | `401` | Token ausente, inválido ou expirado, ou credencial revogada ou expirada. Chame POST /auth/token para obter um token novo. | | `403` | IP não autorizado | | `404` | Pagamento não encontrado | | `429` | Limite de requisições excedido | Lista completa de códigos e como tratá-los: [Erros](/docs/guias/fundamentos/erros). --- # Venda criada URL: https://staging.pagpolar.com/docs/webhooks/eventos/transaction-created > **Evento:** `TRANSACTION_CREATED` Uma venda foi registrada. **Quando dispara:** logo depois da criação da venda, no checkout da PagPolar ou pela API, em `POST /v1/payments/pix`, `POST /v1/payments/boleto`, `POST /v1/payments/credit-card` e `POST /v1/plans/offer/{id}/subscribe`. **O que fazer:** registre a venda pelo `transaction.id` e ligue ao seu pedido. Não libere o que foi vendido: espere `TRANSACTION_PAID`. **Atenção:** confira `transaction.status`. No cartão, a venda recusada pelo gateway na criação chega como `FAILED` e não recebe outro evento. O payload é montado na hora do envio, então a venda pode chegar com um status posterior, como `PAID`. Use `source.channel` para saber se a venda veio do checkout ou da API. ### Exemplo de payload ```json { "id": "550e8400-e29b-41d4-a716-446655440000", "event": "TRANSACTION_CREATED", "creation_date": "2026-01-29T14:00:05.000Z", "version": "1.0.0", "data": { "transaction": { "id": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d", "identifier": "PPO0087103960", "status": "PROCESSING", "type": "BILLING", "payment_method": "PIX", "total_amount": "450.0000", "net_amount": 450, "effective_value": "426.5500", "base_tax": "23.4500", "installment_tax": "0.0000", "base_fixed_tax": "0.9900", "base_percentage_tax": "22.4600", "installments": 1, "cycle": 1, "paid_at": null, "created_at": "2026-01-29T14:00:00.000Z" }, "items": [ { "id": "f47ac10b-58cc-4372-a567-0e02b2c3d479", "quantity": 1, "amount": "450.0000", "original_amount": "500.0000", "discount_value": "50.0000", "product": { "id": "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d", "name": "Curso Completo de Marketing Digital", "type": "DIGITAL" }, "price": { "id": "1b9d6bcd-bbfd-4b2d-9b5d-ab8dfbbd4bed", "title": "Plano Anual", "price": "500.0000", "identifier": "PPP1234567890" } } ], "product": { "id": "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d", "name": "Curso Completo de Marketing Digital", "type": "DIGITAL" }, "buyer": { "name": "João Silva", "email": "joao.silva@email.com", "document": "12345678900", "phone": "11999999999" }, "address": { "street": "Rua das Flores", "number": "123", "complement": "Apto 4B", "neighborhood": "Centro", "city": "São Paulo", "state": "SP", "postal_code": "01000-000" }, "payment_details": { "origin": "DIRECT", "qr_code": "00020126580014br.gov.bcb.pix0136a1b2c3d4-e5f6-4a7b-8c9d", "billet_barcode": null, "billet_link": null, "last_credit_card_digits": null, "shipping_value": "0.00" }, "coupon": null, "subscription": null, "affiliate": null, "source": { "channel": "API", "api_credential_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7" }, "order_bumps": [ { "transaction": { "id": "d4e5f6a7-b8c9-4123-8def-456789012345", "identifier": "PPO0087103961", "status": "PAID", "type": "BILLING", "payment_method": "CREDIT_CARD", "total_amount": "47.0000", "net_amount": 47, "effective_value": "43.5000", "base_tax": "3.5000", "installment_tax": "0.0000", "base_fixed_tax": "0.9900", "base_percentage_tax": "2.5100", "installments": 1, "cycle": 1, "paid_at": "2026-01-29T14:35:00.000Z", "created_at": "2026-01-29T14:00:00.000Z" }, "items": [ { "id": "a12bc34d-56ef-4890-abcd-ef1234567890", "quantity": 1, "amount": "47.0000", "original_amount": "47.0000", "discount_value": "0.0000", "product": { "id": "7c3aeb2d-5b8e-4bad-9cdd-3a1d8c4eab7e", "name": "E-book Estratégias Avançadas", "type": "DIGITAL" }, "price": { "id": "3d1f8ace-dd0f-4d4f-bd7f-cd0f1ddf6def", "title": "Oferta Especial", "price": "47.0000", "identifier": "PPP1234567891" } } ], "product": { "id": "7c3aeb2d-5b8e-4bad-9cdd-3a1d8c4eab7e", "name": "E-book Estratégias Avançadas", "type": "DIGITAL" } } ] } } ``` --- # Cobrança de renovação gerada URL: https://staging.pagpolar.com/docs/webhooks/eventos/transaction-pending > **Evento:** `TRANSACTION_PENDING` A cobrança de renovação de uma assinatura em PIX ou boleto foi gerada e espera pagamento. **Quando dispara:** quando a PagPolar gera a nova cobrança do ciclo para o cliente. Assinaturas em PIX ou boleto nascem no checkout da PagPolar; pela API, a assinatura é sempre no cartão. **O que fazer:** mostre ao cliente o PIX (`payment_details.qr_code`) ou o boleto (`payment_details.billet_link` e `payment_details.billet_barcode`) do novo ciclo. Quando ele pagar, chega `TRANSACTION_PAID`. **Atenção:** é uma venda nova, com outro `transaction.id`. `transaction.cycle` indica o ciclo cobrado e `subscription` vem preenchida. Se a cobrança anterior não foi paga, ela chega antes como `TRANSACTION_EXPIRED`. ### Exemplo de payload ```json { "id": "550e8400-e29b-41d4-a716-446655440000", "event": "TRANSACTION_PENDING", "creation_date": "2026-02-28T10:00:05.000Z", "version": "1.0.0", "data": { "transaction": { "id": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d", "identifier": "PPO0087103960", "status": "DRAFT", "type": "BILLING", "payment_method": "BOLETO", "total_amount": "555.2400", "net_amount": 450, "effective_value": "426.5500", "base_tax": "23.4500", "installment_tax": "0.0000", "base_fixed_tax": "0.9900", "base_percentage_tax": "22.4600", "installments": 1, "cycle": 2, "paid_at": null, "created_at": "2026-01-29T14:00:00.000Z" }, "items": [ { "id": "f47ac10b-58cc-4372-a567-0e02b2c3d479", "quantity": 1, "amount": "450.0000", "original_amount": "500.0000", "discount_value": "50.0000", "product": { "id": "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d", "name": "Curso Completo de Marketing Digital", "type": "DIGITAL" }, "price": { "id": "1b9d6bcd-bbfd-4b2d-9b5d-ab8dfbbd4bed", "title": "Plano Anual", "price": "500.0000", "identifier": "PPP1234567890" } } ], "product": { "id": "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d", "name": "Curso Completo de Marketing Digital", "type": "DIGITAL" }, "buyer": { "name": "João Silva", "email": "joao.silva@email.com", "document": "12345678900", "phone": "11999999999" }, "address": null, "payment_details": { "origin": "DIRECT", "qr_code": null, "billet_barcode": "23793381286000000000300000000409184340000004990", "billet_link": "https://boletos.exemplo.com.br/a1b2c3d4.pdf", "last_credit_card_digits": null, "shipping_value": "0.00" }, "coupon": null, "subscription": { "id": "c3d4e5f6-a7b8-4012-8def-123456789012", "status": "PENDING_RENEWAL", "start_at": "2026-01-28T10:00:00.000Z", "next_billing_at": "2026-03-28T10:00:00.000Z" }, "affiliate": null, "source": { "channel": "API", "api_credential_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7" } } } ``` --- # Venda paga URL: https://staging.pagpolar.com/docs/webhooks/eventos/transaction-paid > **Evento:** `TRANSACTION_PAID` O pagamento da venda foi confirmado. **Quando dispara:** quando o gateway confirma o pagamento do PIX, do boleto ou do cartão. Também vale para a cobrança de renovação de assinaturas em PIX ou boleto. **O que fazer:** libere o que foi vendido e marque o pedido como pago. **Atenção:** o mesmo evento pode chegar mais de uma vez para a mesma venda, por novas tentativas de entrega ou por mais de um aviso do gateway. Antes de liberar, confira se o pedido já está pago no seu sistema. ### Exemplo de payload ```json { "id": "550e8400-e29b-41d4-a716-446655440000", "event": "TRANSACTION_PAID", "creation_date": "2026-01-29T14:35:05.000Z", "version": "1.0.0", "data": { "transaction": { "id": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d", "identifier": "PPO0087103960", "status": "PAID", "type": "BILLING", "payment_method": "CREDIT_CARD", "total_amount": "555.2400", "net_amount": 450, "effective_value": "426.5500", "base_tax": "23.4500", "installment_tax": "105.2400", "base_fixed_tax": "0.9900", "base_percentage_tax": "22.4600", "installments": 12, "cycle": 1, "paid_at": "2026-01-29T14:35:00.000Z", "created_at": "2026-01-29T14:00:00.000Z" }, "items": [ { "id": "f47ac10b-58cc-4372-a567-0e02b2c3d479", "quantity": 1, "amount": "450.0000", "original_amount": "500.0000", "discount_value": "50.0000", "product": { "id": "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d", "name": "Curso Completo de Marketing Digital", "type": "DIGITAL" }, "price": { "id": "1b9d6bcd-bbfd-4b2d-9b5d-ab8dfbbd4bed", "title": "Plano Anual", "price": "500.0000", "identifier": "PPP1234567890" } } ], "product": { "id": "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d", "name": "Curso Completo de Marketing Digital", "type": "DIGITAL" }, "buyer": { "name": "João Silva", "email": "joao.silva@email.com", "document": "12345678900", "phone": "11999999999" }, "address": { "street": "Rua das Flores", "number": "123", "complement": "Apto 4B", "neighborhood": "Centro", "city": "São Paulo", "state": "SP", "postal_code": "01000-000" }, "payment_details": { "origin": "DIRECT", "qr_code": null, "billet_barcode": null, "billet_link": null, "last_credit_card_digits": "4242", "shipping_value": "0.00" }, "coupon": { "id": "5f2c8b1a-9d3e-4a7b-8c6d-1e2f3a4b5c6d", "code": "PROMO10", "name": "Promoção de Janeiro", "fixed_value": null, "percentage_value": 10 }, "subscription": null, "affiliate": { "identifier": "PAO1234567890", "name": "Parceiro Digital", "commission_type": "COMMISSION", "commission_value": 90, "product_quantity": null }, "source": { "channel": "API", "api_credential_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7" } } } ``` --- # Venda expirada URL: https://staging.pagpolar.com/docs/webhooks/eventos/transaction-expired > **Evento:** `TRANSACTION_EXPIRED` Um PIX ou boleto venceu sem pagamento. **Quando dispara:** na verificação de vencimentos, que roda a cada 3 horas. O PIX vence quando o QR Code expira; o boleto, 5 dias depois de criado. Também dispara quando a PagPolar gera a cobrança de um novo ciclo de assinatura e a cobrança anterior não foi paga. **O que fazer:** marque o pedido como não pago e não libere o que foi vendido. Para vender de novo, crie uma nova cobrança. **Atenção:** o evento pode chegar algumas horas depois do vencimento. Order bumps e upsells da venda vencem junto, sem evento próprio. Quando a venda é de uma assinatura, `subscription` vem preenchida. ### Exemplo de payload ```json { "id": "550e8400-e29b-41d4-a716-446655440000", "event": "TRANSACTION_EXPIRED", "creation_date": "2026-02-01T03:00:05.000Z", "version": "1.0.0", "data": { "transaction": { "id": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d", "identifier": "PPO0087103960", "status": "EXPIRED", "type": "BILLING", "payment_method": "PIX", "total_amount": "450.0000", "net_amount": 450, "effective_value": "426.5500", "base_tax": "23.4500", "installment_tax": "0.0000", "base_fixed_tax": "0.9900", "base_percentage_tax": "22.4600", "installments": 1, "cycle": 1, "paid_at": null, "created_at": "2026-01-29T14:00:00.000Z" }, "items": [ { "id": "f47ac10b-58cc-4372-a567-0e02b2c3d479", "quantity": 1, "amount": "450.0000", "original_amount": "500.0000", "discount_value": "50.0000", "product": { "id": "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d", "name": "Curso Completo de Marketing Digital", "type": "DIGITAL" }, "price": { "id": "1b9d6bcd-bbfd-4b2d-9b5d-ab8dfbbd4bed", "title": "Plano Anual", "price": "500.0000", "identifier": "PPP1234567890" } } ], "product": { "id": "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d", "name": "Curso Completo de Marketing Digital", "type": "DIGITAL" }, "buyer": { "name": "João Silva", "email": "joao.silva@email.com", "document": "12345678900", "phone": "11999999999" }, "address": { "street": "Rua das Flores", "number": "123", "complement": "Apto 4B", "neighborhood": "Centro", "city": "São Paulo", "state": "SP", "postal_code": "01000-000" }, "payment_details": { "origin": "DIRECT", "qr_code": "00020126580014br.gov.bcb.pix0136a1b2c3d4-e5f6-4a7b-8c9d", "billet_barcode": null, "billet_link": null, "last_credit_card_digits": null, "shipping_value": "0.00" }, "coupon": null, "subscription": null, "affiliate": null, "source": { "channel": "API", "api_credential_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7" } } } ``` --- # Venda cancelada URL: https://staging.pagpolar.com/docs/webhooks/eventos/transaction-canceled > **Evento:** `TRANSACTION_CANCELED` A venda foi cancelada sem ter sido paga. **Quando dispara:** quando o cancelamento de uma venda ainda não paga é pedido à PagPolar, ou quando o gateway estorna uma venda que não contava como paga. No cancelamento pedido à PagPolar, os order bumps e upsells cancelados junto recebem o próprio evento. **O que fazer:** cancele o pedido no seu sistema e não libere o que foi vendido. **Atenção:** venda paga não é cancelada por este caminho. O estorno de uma venda paga chega como `TRANSACTION_REFUNDED`. ### Exemplo de payload ```json { "id": "550e8400-e29b-41d4-a716-446655440000", "event": "TRANSACTION_CANCELED", "creation_date": "2026-01-30T09:00:05.000Z", "version": "1.0.0", "data": { "transaction": { "id": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d", "identifier": "PPO0087103960", "status": "CANCELED", "type": "BILLING", "payment_method": "CREDIT_CARD", "total_amount": "555.2400", "net_amount": 450, "effective_value": "426.5500", "base_tax": "23.4500", "installment_tax": "105.2400", "base_fixed_tax": "0.9900", "base_percentage_tax": "22.4600", "installments": 12, "cycle": 1, "paid_at": null, "created_at": "2026-01-29T14:00:00.000Z" }, "items": [ { "id": "f47ac10b-58cc-4372-a567-0e02b2c3d479", "quantity": 1, "amount": "450.0000", "original_amount": "500.0000", "discount_value": "50.0000", "product": { "id": "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d", "name": "Curso Completo de Marketing Digital", "type": "DIGITAL" }, "price": { "id": "1b9d6bcd-bbfd-4b2d-9b5d-ab8dfbbd4bed", "title": "Plano Anual", "price": "500.0000", "identifier": "PPP1234567890" } } ], "product": { "id": "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d", "name": "Curso Completo de Marketing Digital", "type": "DIGITAL" }, "buyer": { "name": "João Silva", "email": "joao.silva@email.com", "document": "12345678900", "phone": "11999999999" }, "address": { "street": "Rua das Flores", "number": "123", "complement": "Apto 4B", "neighborhood": "Centro", "city": "São Paulo", "state": "SP", "postal_code": "01000-000" }, "payment_details": { "origin": "DIRECT", "qr_code": null, "billet_barcode": null, "billet_link": null, "last_credit_card_digits": "4242", "shipping_value": "0.00" }, "coupon": null, "subscription": null, "affiliate": null, "source": { "channel": "API", "api_credential_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7" } } } ``` --- # Reembolso solicitado URL: https://staging.pagpolar.com/docs/webhooks/eventos/transaction-ask-refunding > **Evento:** `TRANSACTION_ASK_REFUNDING` O cliente pediu reembolso. O dinheiro **ainda não** voltou para ele. **Quando dispara:** quando o cliente abre um pedido de reembolso, da venda inteira ou de parte dos itens. Pedido aberto pelo próprio vendedor ou pelo suporte não dispara este evento. **O que fazer:** registre o pedido e acompanhe em `GET /v1/refunds`. Mantenha o acesso até chegar `TRANSACTION_REFUNDED`. **Atenção:** pedido da venda inteira deixa a venda em `ASK_REFUND`; pedido de parte dos itens, em `ASK_PARTIAL_REFUND`. No pedido da venda inteira de uma assinatura, a PagPolar também pede o cancelamento da assinatura. `transaction.refund_at` chega `null`. ### Exemplo de payload ```json { "id": "550e8400-e29b-41d4-a716-446655440000", "event": "TRANSACTION_ASK_REFUNDING", "creation_date": "2026-02-03T11:30:05.000Z", "version": "1.0.0", "data": { "transaction": { "id": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d", "identifier": "PPO0087103960", "status": "ASK_REFUND", "type": "BILLING", "payment_method": "CREDIT_CARD", "total_amount": "555.2400", "net_amount": 450, "effective_value": "426.5500", "base_tax": "23.4500", "installment_tax": "105.2400", "base_fixed_tax": "0.9900", "base_percentage_tax": "22.4600", "installments": 12, "cycle": 1, "paid_at": "2026-01-29T14:35:00.000Z", "created_at": "2026-01-29T14:00:00.000Z", "refund_reason": "Produto não atendeu às expectativas", "refund_at": null }, "items": [ { "id": "f47ac10b-58cc-4372-a567-0e02b2c3d479", "quantity": 1, "amount": "450.0000", "original_amount": "500.0000", "discount_value": "50.0000", "product": { "id": "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d", "name": "Curso Completo de Marketing Digital", "type": "DIGITAL" }, "price": { "id": "1b9d6bcd-bbfd-4b2d-9b5d-ab8dfbbd4bed", "title": "Plano Anual", "price": "500.0000", "identifier": "PPP1234567890" } } ], "product": { "id": "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d", "name": "Curso Completo de Marketing Digital", "type": "DIGITAL" }, "buyer": { "name": "João Silva", "email": "joao.silva@email.com", "document": "12345678900", "phone": "11999999999" }, "address": { "street": "Rua das Flores", "number": "123", "complement": "Apto 4B", "neighborhood": "Centro", "city": "São Paulo", "state": "SP", "postal_code": "01000-000" }, "payment_details": { "origin": "DIRECT", "qr_code": null, "billet_barcode": null, "billet_link": null, "last_credit_card_digits": "4242", "shipping_value": "0.00" }, "coupon": null, "subscription": null, "affiliate": null, "source": { "channel": "API", "api_credential_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7" } } } ``` --- # Venda reembolsada URL: https://staging.pagpolar.com/docs/webhooks/eventos/transaction-refunded > **Evento:** `TRANSACTION_REFUNDED` O estorno foi concluído e o dinheiro voltou para o cliente. **Quando dispara:** quando o gateway confirma o estorno de uma venda paga, com ou sem pedido de reembolso antes. **O que fazer:** revogue o acesso ao que foi vendido e marque o pedido como reembolsado. **Atenção:** no reembolso de parte dos itens, o evento só chega quando o último item da venda é estornado; antes disso a venda volta para `PAID` e nenhum evento é enviado. O estorno de uma venda que não contava como paga chega como `TRANSACTION_CANCELED`. ### Exemplo de payload ```json { "id": "550e8400-e29b-41d4-a716-446655440000", "event": "TRANSACTION_REFUNDED", "creation_date": "2026-02-05T16:00:05.000Z", "version": "1.0.0", "data": { "transaction": { "id": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d", "identifier": "PPO0087103960", "status": "REFUNDED", "type": "BILLING", "payment_method": "CREDIT_CARD", "total_amount": "555.2400", "net_amount": 450, "effective_value": "426.5500", "base_tax": "23.4500", "installment_tax": "105.2400", "base_fixed_tax": "0.9900", "base_percentage_tax": "22.4600", "installments": 12, "cycle": 1, "paid_at": "2026-01-29T14:35:00.000Z", "created_at": "2026-01-29T14:00:00.000Z", "refund_reason": "Produto não atendeu às expectativas", "refund_at": "2026-02-05T16:00:00.000Z" }, "items": [ { "id": "f47ac10b-58cc-4372-a567-0e02b2c3d479", "quantity": 1, "amount": "450.0000", "original_amount": "500.0000", "discount_value": "50.0000", "product": { "id": "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d", "name": "Curso Completo de Marketing Digital", "type": "DIGITAL" }, "price": { "id": "1b9d6bcd-bbfd-4b2d-9b5d-ab8dfbbd4bed", "title": "Plano Anual", "price": "500.0000", "identifier": "PPP1234567890" } } ], "product": { "id": "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d", "name": "Curso Completo de Marketing Digital", "type": "DIGITAL" }, "buyer": { "name": "João Silva", "email": "joao.silva@email.com", "document": "12345678900", "phone": "11999999999" }, "address": { "street": "Rua das Flores", "number": "123", "complement": "Apto 4B", "neighborhood": "Centro", "city": "São Paulo", "state": "SP", "postal_code": "01000-000" }, "payment_details": { "origin": "DIRECT", "qr_code": null, "billet_barcode": null, "billet_link": null, "last_credit_card_digits": "4242", "shipping_value": "0.00" }, "coupon": null, "subscription": null, "affiliate": null, "source": { "channel": "API", "api_credential_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7" } } } ``` --- # Chargeback aprovado URL: https://staging.pagpolar.com/docs/webhooks/eventos/transaction-chargeback-approved > **Evento:** `TRANSACTION_CHARGEBACK_APPROVED` O banco do cliente aprovou uma contestação da compra (chargeback) e o valor foi revertido. **Quando dispara:** quando o gateway avisa a PagPolar que o chargeback foi aprovado. **O que fazer:** revogue o acesso ao que foi vendido e marque o pedido como contestado. **Atenção:** se a venda já estava `REFUNDED` ou `CANCELED`, o evento chega com esse status, sem mudar para `CHARGEBACK_APPROVED`. Diferente de `TRANSACTION_REFUNDED`, a transação traz `chargeback_approved_at` e não traz `refund_reason` nem `refund_at`. ### Exemplo de payload ```json { "id": "550e8400-e29b-41d4-a716-446655440000", "event": "TRANSACTION_CHARGEBACK_APPROVED", "creation_date": "2026-02-10T09:15:05.000Z", "version": "1.0.0", "data": { "transaction": { "id": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d", "identifier": "PPO0087103960", "status": "CHARGEBACK_APPROVED", "type": "BILLING", "payment_method": "CREDIT_CARD", "total_amount": "555.2400", "net_amount": 450, "effective_value": "426.5500", "base_tax": "23.4500", "installment_tax": "105.2400", "base_fixed_tax": "0.9900", "base_percentage_tax": "22.4600", "installments": 12, "cycle": 1, "paid_at": "2026-01-29T14:35:00.000Z", "created_at": "2026-01-29T14:00:00.000Z", "chargeback_approved_at": "2026-02-10T09:15:00.000Z" }, "items": [ { "id": "f47ac10b-58cc-4372-a567-0e02b2c3d479", "quantity": 1, "amount": "450.0000", "original_amount": "500.0000", "discount_value": "50.0000", "product": { "id": "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d", "name": "Curso Completo de Marketing Digital", "type": "DIGITAL" }, "price": { "id": "1b9d6bcd-bbfd-4b2d-9b5d-ab8dfbbd4bed", "title": "Plano Anual", "price": "500.0000", "identifier": "PPP1234567890" } } ], "product": { "id": "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d", "name": "Curso Completo de Marketing Digital", "type": "DIGITAL" }, "buyer": { "name": "João Silva", "email": "joao.silva@email.com", "document": "12345678900", "phone": "11999999999" }, "address": { "street": "Rua das Flores", "number": "123", "complement": "Apto 4B", "neighborhood": "Centro", "city": "São Paulo", "state": "SP", "postal_code": "01000-000" }, "payment_details": { "origin": "DIRECT", "qr_code": null, "billet_barcode": null, "billet_link": null, "last_credit_card_digits": "4242", "shipping_value": "0.00" }, "coupon": null, "subscription": null, "affiliate": null, "source": { "channel": "API", "api_credential_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7" } } } ``` --- # Assinatura criada URL: https://staging.pagpolar.com/docs/webhooks/eventos/subscription-created > **Evento:** `SUBSCRIPTION_CREATED` Uma assinatura nova foi registrada. **Quando dispara:** logo depois da criação da assinatura, no checkout da PagPolar ou em `POST /v1/plans/offer/{id}/subscribe`. Se ainda não tiver sido enviado para a assinatura, também dispara quando o gateway ativa o primeiro ciclo no cartão. **O que fazer:** registre a assinatura pelo `subscription.id`. Não libere o acesso. No cartão, libere só quando `GET /v1/subscriptions/{id}` trouxer `status` `ACTIVE`. **Atenção:** no cartão, a assinatura nasce `DRAFT`; em PIX ou boleto, nasce `PENDING_PAYMENT`. ### Exemplo de payload ```json { "id": "550e8400-e29b-41d4-a716-446655440000", "event": "SUBSCRIPTION_CREATED", "creation_date": "2026-01-28T10:00:05.000Z", "version": "1.0.0", "data": { "subscription": { "id": "c3d4e5f6-a7b8-4012-8def-123456789012", "external_id": null, "status": "DRAFT", "start_at": "2026-01-28T10:00:00.000Z", "end_at": null, "next_billing_at": "2026-02-28T10:00:00.000Z", "payment_method": "CREDIT_CARD", "total_amount": "49.9000", "created_at": "2026-01-28T10:00:00.000Z" }, "product": { "id": "8a2cdb3c-4a6d-4bad-8add-1a0c6b2cab5c", "name": "Assinatura Premium Mensal", "type": "SUBSCRIPTION" }, "buyer": { "name": "João Silva", "email": "joao.silva@email.com", "document": "12345678900", "phone": "11999999999" }, "source": { "channel": "API", "api_credential_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7" } } } ``` --- # Assinatura confirmada URL: https://staging.pagpolar.com/docs/webhooks/eventos/subscription-confirmed > **Evento:** `SUBSCRIPTION_CONFIRMED` O gateway aceitou a assinatura no cartão de crédito. **Quando dispara:** quando a PagPolar termina de criar a assinatura no gateway. Essa criação acontece depois da resposta de `POST /v1/plans/offer/{id}/subscribe` (ou da compra no checkout). **O que fazer:** guarde `subscription.external_id` se precisar dele. Não libere o acesso: consulte `GET /v1/subscriptions/{id}` e libere só quando `status` for `ACTIVE`. **Atenção:** o `status` **continua `DRAFT`** e só vira `ACTIVE` quando o gateway informa a cobrança paga. Só existe para assinaturas no cartão. Se a criação no gateway falhar, chega `SUBSCRIPTION_FAILED` no lugar deste evento. ### Exemplo de payload ```json { "id": "550e8400-e29b-41d4-a716-446655440000", "event": "SUBSCRIPTION_CONFIRMED", "creation_date": "2026-01-28T10:00:20.000Z", "version": "1.0.0", "data": { "subscription": { "id": "c3d4e5f6-a7b8-4012-8def-123456789012", "external_id": "sub_abc123", "status": "DRAFT", "start_at": "2026-01-28T10:00:00.000Z", "end_at": null, "next_billing_at": "2026-02-28T10:00:00.000Z", "payment_method": "CREDIT_CARD", "total_amount": "49.9000", "created_at": "2026-01-28T10:00:00.000Z" }, "product": { "id": "8a2cdb3c-4a6d-4bad-8add-1a0c6b2cab5c", "name": "Assinatura Premium Mensal", "type": "SUBSCRIPTION" }, "buyer": { "name": "João Silva", "email": "joao.silva@email.com", "document": "12345678900", "phone": "11999999999" }, "source": { "channel": "API", "api_credential_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7" } } } ``` --- # Assinatura recusada URL: https://staging.pagpolar.com/docs/webhooks/eventos/subscription-failed > **Evento:** `SUBSCRIPTION_FAILED` A criação da assinatura no cartão de crédito falhou no gateway. **Quando dispara:** quando a PagPolar tenta criar a assinatura no gateway e ele recusa ou a criação dá erro. Essa tentativa acontece depois da resposta de `POST /v1/plans/offer/{id}/subscribe`, que já devolveu a assinatura em `DRAFT`. **O que fazer:** não libere o acesso. Avise o cliente e peça outro cartão; para tentar de novo, crie uma nova assinatura. **Atenção:** `status` vira `FAILED` e `external_id` chega `null`. As vendas da assinatura também ficam `FAILED`, sem evento de venda próprio. ### Exemplo de payload ```json { "id": "550e8400-e29b-41d4-a716-446655440000", "event": "SUBSCRIPTION_FAILED", "creation_date": "2026-01-28T10:00:20.000Z", "version": "1.0.0", "data": { "subscription": { "id": "c3d4e5f6-a7b8-4012-8def-123456789012", "external_id": null, "status": "FAILED", "start_at": "2026-01-28T10:00:00.000Z", "end_at": null, "next_billing_at": "2026-02-28T10:00:00.000Z", "payment_method": "CREDIT_CARD", "total_amount": "49.9000", "created_at": "2026-01-28T10:00:00.000Z" }, "product": { "id": "8a2cdb3c-4a6d-4bad-8add-1a0c6b2cab5c", "name": "Assinatura Premium Mensal", "type": "SUBSCRIPTION" }, "buyer": { "name": "João Silva", "email": "joao.silva@email.com", "document": "12345678900", "phone": "11999999999" }, "source": { "channel": "API", "api_credential_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7" } } } ``` --- # Assinatura renovada URL: https://staging.pagpolar.com/docs/webhooks/eventos/subscription-renewed > **Evento:** `SUBSCRIPTION_RENEWED` Um ciclo da assinatura no cartão, a partir do segundo, foi pago. **Quando dispara:** quando o gateway avisa a PagPolar que a cobrança de um ciclo a partir do segundo foi paga. **O que fazer:** mantenha o acesso e atualize a próxima data de cobrança com `subscription.next_billing_at`. **Atenção:** chega uma vez a cada ciclo pago, sempre com o mesmo `subscription.id`: não use só esse campo para descartar repetidos. Assinaturas em PIX ou boleto não geram este evento; a renovação delas chega como `TRANSACTION_PAID`. ### Exemplo de payload ```json { "id": "550e8400-e29b-41d4-a716-446655440000", "event": "SUBSCRIPTION_RENEWED", "creation_date": "2026-02-28T10:05:05.000Z", "version": "1.0.0", "data": { "subscription": { "id": "c3d4e5f6-a7b8-4012-8def-123456789012", "external_id": "sub_abc123", "status": "ACTIVE", "start_at": "2026-01-28T10:00:00.000Z", "end_at": null, "next_billing_at": "2026-03-28T10:00:00.000Z", "payment_method": "CREDIT_CARD", "total_amount": "49.9000", "created_at": "2026-01-28T10:00:00.000Z" }, "product": { "id": "8a2cdb3c-4a6d-4bad-8add-1a0c6b2cab5c", "name": "Assinatura Premium Mensal", "type": "SUBSCRIPTION" }, "buyer": { "name": "João Silva", "email": "joao.silva@email.com", "document": "12345678900", "phone": "11999999999" }, "source": { "channel": "API", "api_credential_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7" } } } ``` --- # Assinatura em atraso URL: https://staging.pagpolar.com/docs/webhooks/eventos/subscription-delayed > **Evento:** `SUBSCRIPTION_DELAYED` A renovação de uma assinatura em PIX ou boleto está atrasada, mas ainda dentro do prazo de carência. **Quando dispara:** na verificação diária de assinaturas, quando a data de renovação já passou, a renovação não foi paga e o prazo de carência ainda não acabou. Pode chegar uma vez por dia enquanto a assinatura seguir nessa situação. **O que fazer:** lembre o cliente de pagar. **Atenção:** a assinatura está `PENDING_RENEWAL`. Se o prazo acabar sem pagamento, chega `SUBSCRIPTION_EXPIRED`. Assinaturas no cartão não geram este evento. ### Exemplo de payload ```json { "id": "550e8400-e29b-41d4-a716-446655440000", "event": "SUBSCRIPTION_DELAYED", "creation_date": "2026-03-01T08:00:05.000Z", "version": "1.0.0", "data": { "subscription": { "id": "c3d4e5f6-a7b8-4012-8def-123456789012", "external_id": null, "status": "PENDING_RENEWAL", "start_at": "2026-01-28T10:00:00.000Z", "end_at": null, "next_billing_at": "2026-02-28T10:00:00.000Z", "payment_method": "BOLETO", "total_amount": "49.9000", "created_at": "2026-01-28T10:00:00.000Z" }, "product": { "id": "8a2cdb3c-4a6d-4bad-8add-1a0c6b2cab5c", "name": "Assinatura Premium Mensal", "type": "SUBSCRIPTION" }, "buyer": { "name": "João Silva", "email": "joao.silva@email.com", "document": "12345678900", "phone": "11999999999" }, "source": { "channel": "API", "api_credential_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7" } } } ``` --- # Assinatura expirada URL: https://staging.pagpolar.com/docs/webhooks/eventos/subscription-expired > **Evento:** `SUBSCRIPTION_EXPIRED` A assinatura em PIX ou boleto passou do prazo de carência sem pagar a renovação. **Quando dispara:** na verificação diária de assinaturas, depois que o prazo de carência da renovação acaba sem pagamento. **O que fazer:** revogue o acesso do cliente. **Atenção:** o `status` vira `EXPIRED`. Assinaturas no cartão não geram este evento. ### Exemplo de payload ```json { "id": "550e8400-e29b-41d4-a716-446655440000", "event": "SUBSCRIPTION_EXPIRED", "creation_date": "2026-03-06T08:00:05.000Z", "version": "1.0.0", "data": { "subscription": { "id": "c3d4e5f6-a7b8-4012-8def-123456789012", "external_id": null, "status": "EXPIRED", "start_at": "2026-01-28T10:00:00.000Z", "end_at": null, "next_billing_at": "2026-02-28T10:00:00.000Z", "payment_method": "BOLETO", "total_amount": "49.9000", "created_at": "2026-01-28T10:00:00.000Z" }, "product": { "id": "8a2cdb3c-4a6d-4bad-8add-1a0c6b2cab5c", "name": "Assinatura Premium Mensal", "type": "SUBSCRIPTION" }, "buyer": { "name": "João Silva", "email": "joao.silva@email.com", "document": "12345678900", "phone": "11999999999" }, "source": { "channel": "API", "api_credential_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7" } } } ``` --- # Assinatura cancelada URL: https://staging.pagpolar.com/docs/webhooks/eventos/subscription-canceled > **Evento:** `SUBSCRIPTION_CANCELED` O cancelamento da assinatura foi efetivado. **Quando dispara:** em PIX ou boleto, na hora do pedido de cancelamento. No cartão, quando o gateway informa o cancelamento ou a recusa: o pedido feito em `DELETE /v1/subscriptions/{id}` deixa a assinatura em `CANCELING`, sem evento, até essa confirmação. O pedido de reembolso total de uma venda de assinatura, o estorno concluído e o chargeback aprovado também pedem o cancelamento. **O que fazer:** encerre a assinatura no seu sistema e revogue o acesso. Se precisar da data, confira `end_at` em `GET /v1/subscriptions/{id}`. **Atenção:** no cartão, `end_at` é gravado logo depois do envio deste evento, então o payload pode chegar com `end_at` `null`. Em PIX ou boleto, `end_at` recebe a data do cancelamento. ### Exemplo de payload ```json { "id": "550e8400-e29b-41d4-a716-446655440000", "event": "SUBSCRIPTION_CANCELED", "creation_date": "2026-03-15T08:00:05.000Z", "version": "1.0.0", "data": { "subscription": { "id": "c3d4e5f6-a7b8-4012-8def-123456789012", "external_id": "sub_abc123", "status": "CANCELED", "start_at": "2026-01-28T10:00:00.000Z", "end_at": "2026-03-15T08:00:00.000Z", "next_billing_at": "2026-02-28T10:00:00.000Z", "payment_method": "CREDIT_CARD", "total_amount": "49.9000", "created_at": "2026-01-28T10:00:00.000Z" }, "product": { "id": "8a2cdb3c-4a6d-4bad-8add-1a0c6b2cab5c", "name": "Assinatura Premium Mensal", "type": "SUBSCRIPTION" }, "buyer": { "name": "João Silva", "email": "joao.silva@email.com", "document": "12345678900", "phone": "11999999999" }, "source": { "channel": "API", "api_credential_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7" } } } ```