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
- Uma credencial com a chave de API e a URL do webhook cadastrada. Veja Credenciais da API.
- Um token de acesso. Veja Autenticação; 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.
- Um servidor seu para chamar a API.
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.
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_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 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â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):
{
"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.
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:
| 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. |
customer.phone | Sim | Telefone 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:
| Evento | Quando chega | O que fazer |
|---|---|---|
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.
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"
}
}
}
}| 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.
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çã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, 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. |
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 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
shippingdeGET /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.