Vender um produto físico

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 e Vender com cartão de crédito. Aqui está só o que muda por causa da entrega.

Com a chave de Homologação, a cobrança roda no 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.

Antes de começar

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 painelO que cadastrar
Configurações → IntegraçõesA 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ísicoPeso em gramas e Altura, Largura e Comprimento em centímetros.
No produto, aba FretePelo 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.

Confirme que a oferta exige frete

Consulte a oferta e leia requires_shipping:

curl "https://api.pagpolar.com/v1/offers/PPP1234567890" \
  -H "Authorization: Bearer <SEU_TOKEN_DE_ACESSO>"
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):

{
  "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_shippingO que fazer
trueO produto é físico. Siga para o passo 2: a cobrança vai exigir address e shipping_option_id.
falseSiga direto para Vender com PIX ou boleto ou Vender com cartão de crédito. 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.

Contrato completo: GET /offers/{identifier}.

Consulte o frete pelo CEP

Envie o CEP de destino. A API devolve as opções de entrega daquele produto para aquele CEP.

curl "https://api.pagpolar.com/v1/offers/PPP1234567890/shipping?postal_code=01311000&quantity=1" \
  -H "Authorization: Bearer <SEU_TOKEN_DE_ACESSO>"
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âmetroOnde vaiObrigatórioO que é
identifierNo caminhoSimCódigo da oferta (PPP e 10 dígitos, como PPP1234567890) ou o id da oferta (uuid).
postal_codeNa querySimCEP de destino, com ou sem pontuação. Precisa ter 8 dígitos.
quantityNa queryNãoQuantidade de unidades, número inteiro a partir de 1. Padrão 1. Entra no cálculo do peso.

Resposta 200 (resumida):

{
  "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"
    }
  ]
}
CampoO que é
idIdentificador 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.
nameNome da opção, como o comprador vê.
typeFREE frete grátis, PAID valor fixo definido pelo vendedor, DYNAMIC calculado na hora pela transportadora.
costValor do frete em reais: 32.9 é R$ 32,90.
min_days e max_daysPrazo 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_typeBUSINESS_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.

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.

O exemplo cobra por PIX:

curl -X POST "https://api.pagpolar.com/v1/payments/pix" \
  -H "Authorization: Bearer <SEU_TOKEN_DE_ACESSO>" \
  -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": "<CPF_DO_CLIENTE>",
      "phone": "<TELEFONE_DO_CLIENTE>"
    },
    "address": {
      "street": "Rua das Flores",
      "number": "123",
      "neighborhood": "Centro",
      "city": "São Paulo",
      "state": "SP",
      "postal_code": "01311000"
    }
  }'
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: '<CPF_DO_CLIENTE>',
      phone: '<TELEFONE_DO_CLIENTE>',
    },
    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. Os dois campos de entrega são os mesmos nas três rotas.

Os campos de entrega:

CampoObrigatórioO que é
shipping_option_idSim, quando requires_shipping é trueO 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.
addressSim, quando requires_shipping é trueEndereço de entrega. Os campos obrigatórios estão na tabela abaixo.

Dentro de address:

CampoObrigatórioO que é
streetSimRua, avenida ou logradouro.
numberSimNúmero, como texto.
complementNãoComplemento, como Apto 4B. Aceita texto vazio ou null.
neighborhoodSimBairro.
citySimCidade.
stateSimEstado, como SP.
postal_codeSimCEP de entrega. Use o mesmo CEP que você consultou no passo 2: é com ele que o valor do frete é recalculado.

Dados do cliente:

CampoObrigatórioO que é
customer.nameSimNome do cliente, até 255 caracteres.
customer.emailSimE-mail válido do cliente.
customer.documentSimCPF ou CNPJ do cliente, com ou sem pontuação. Veja o formato em Documento e telefone.
customer.phoneSimTelefone do cliente com DDD, com ou sem pontuação. Veja o formato em 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.

Resposta 201 do PIX (resumida):

{
  "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, POST /payments/boleto e POST /payments/credit-card.

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 e Processar sem duplicar.

O evento que confirma o pagamento é o mesmo do produto digital:

EventoQuando chegaO que fazer
TRANSACTION_PAIDO 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.

Os outros eventos do fluxo mudam conforme o meio de pagamento. Para PIX e boleto, veja Espere o webhook; para cartão, Vender com cartão de crédito. Os status da venda estão em Ciclo de vida da venda.

Confira o frete na venda

A consulta da venda traz o bloco shipping, com o frete cobrado e o endereço gravado.

curl "https://api.pagpolar.com/v1/sales/a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d" \
  -H "Authorization: Bearer <SEU_TOKEN_DE_ACESSO>"
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):

{
  "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"
      }
    }
  }
}
CampoO que é
shipping.amountValor 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_nameNome da opção escolhida na cobrança. Pode vir null.
shipping.addressO endereço de entrega gravado na venda.
shippingVem null quando a venda não tem entrega.

Os valores vêm em reais, como número. Veja Valores nas respostas.

Contrato completo: GET /sales/{identifier}.

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çãoO que você recebeO 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, que incluem 401, 403 e 429, este fluxo pode responder:

RespostaQuando aconteceO que fazer
400 com postal_code deve ter 8 dígitosO 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.
404 com Oferta não encontradaNa 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 produtoA 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}/shippingA 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álidoO 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 encontradaO 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_idshipping_option_id foi enviado numa oferta com requires_shipping: false.Remova o campo.
400, 404 ou 409 nas regras da ofertaAs 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 e 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