Webhooks do Shopify na prática: capturar orders/paid sem perder nem duplicar venda
Qual webhook do Shopify representa a conversão real, como ler o payload de orders/paid e por que a entrega at-least-once exige responder rápido e enfileirar.
Neste artigo
Existe um erro de configuração que infla a conversão de uma loja Shopify sem gerar nenhum alarme: assinar o webhook errado. Alguém liga a mensuração no evento orders/create, vê os pedidos chegando no relatório, comemora a integração funcionando — e só semanas depois descobre que estava contando pedidos com boleto não pago, PIX abandonado e pedidos de teste como se fossem venda. O relatório mostra mais conversão do que o caixa registrou, a plataforma de anúncios otimiza para o público errado, e a causa está a um clique de distância: o evento que dispara não é o evento que representa dinheiro.
Capturar a conversão real de uma loja Shopify é menos sobre "conectar o webhook" e mais sobre escolher o evento certo, ler o payload com disciplina e tratar a entrega como o que ela é: uma entrega não confiável, que chega repetida, atrasada e fora de ordem. Quem ignora essas três características monta uma integração que funciona na demonstração e falha na produção.
O evento errado: por que <code>orders/create</code> engana#
O Shopify emite webhooks para dezenas de tópicos, e vários parecem representar "uma venda". A diferença entre eles é exatamente onde mora a precisão da mensuração.
checkouts/create— dispara quando alguém inicia um checkout. É um sinal de intenção, não de compra. Serve paraInitiateCheckout, jamais para conversão de receita.orders/create— dispara quando um pedido é criado, independentemente de estar pago. Um pedido com boleto emitido, PIX pendente, pagamento manual ou fraude em análise já existe aqui. Contar isso como venda é contar dinheiro que talvez nunca entre.orders/paid— dispara quando o pedido atinge o estado de pago. É o sinal de conversão de receita: o dinheiro foi confirmado. É este o evento que deve alimentarPurchase/CompletePayment.orders/fulfilled— dispara no envio/atendimento do pedido. Importa para logística e para conversões offline de entrega, não para a conversão de compra.
A regra prática é direta: o evento de conversão de receita é orders/paid, porque ele reflete o financial_status de pagamento confirmado. O orders/create só serve como evento de conversão quando a loja opera exclusivamente com pagamentos aprovados na hora e sem exceções — uma condição rara o suficiente para não ser a premissa padrão.
Há uma sutileza: em lojas com aprovação instantânea (cartão aprovado no ato), orders/create e orders/paid chegam quase juntos, e a diferença parece acadêmica. Ela deixa de ser acadêmica no primeiro boleto, no primeiro PIX que expira, no primeiro pedido manual lançado pelo suporte. A escolha certa é a que continua correta quando o método de pagamento muda.
Assinar o webhook certo#
Há dois caminhos para registrar a assinatura: pela interface administrativa (em Configurações, na área de notificações) ou pela API de webhooks. Para mensuração séria, o registro via API é preferível, porque fica versionado, é reproduzível entre ambientes e não depende de alguém lembrar de reconfigurar a loja.
Ao registrar, dois pontos merecem atenção. O primeiro é o versionamento da API do Shopify: o payload de um webhook segue a versão da API no momento do registro, e o Shopify aposenta versões antigas periodicamente. Um campo que existe hoje pode mudar de forma ou de lugar na próxima versão — e a integração que dependia dele silenciosamente para de extrair o dado. Fixe a versão explicitamente e trate a atualização como uma mudança de contrato, testada antes de subir.
O segundo é o formato: o Shopify entrega JSON por padrão, com o corpo do pedido inteiro no POST. É esse corpo que você vai ler.
A anatomia do payload de <code>orders/paid</code>#
O payload é grande, mas só um punhado de campos importa para mensuração. Saber quais são evita ler o dado errado.
``json { "id": 820982911946154500, "financial_status": "paid", "currency": "BRL", "total_price": "259.90", "customer": { "email": "cliente@exemplo.com", "phone": "+5511999998888" }, "line_items": [ { "product_id": 632910392, "quantity": 1, "price": "259.90" } ], "landing_site": "/?fbclid=IwAR...&utm_source=meta" } ``
Os campos que sustentam a conversão:
id— o identificador único do pedido. É o seu melhor candidato a chave de idempotência e aevent_idpara deduplicação com o pixel. Estável, único por venda, previsível.financial_status— o estado financeiro. Mesmo assinandoorders/paid, verifique que valepaidantes de contar; blindagem barata contra surpresa.total_priceecurrency— valor e moeda da conversão. Envie o valor como número (259.90), nunca a string formatada com símbolo. Em loja multi-moeda,currencyvaria por pedido e precisa acompanhar o valor.customer.emailecustomer.phone— a PII que, normalizada e com hash, eleva a correspondência nas plataformas. Nunca envie em texto puro; normalize (minúsculas, trim, telefone em E.164) e aplique SHA-256.line_items— os itens, comproduct_id,quantityeprice. Alimentamcontent_idse o valor por item.landing_site(e campos correlatos de referência) — carregam os parâmetros de clique (fbclid,gclid) e UTMs que ligam a compra à campanha. Sem eles, a plataforma perde a atribuição do clique.
Entrega no mundo real: at-least-once#
Aqui está a parte que separa a integração de brinquedo da integração de produção. O Shopify não entrega cada webhook exatamente uma vez. Ele entrega pelo menos uma vez — e às vezes mais.
O mecanismo é simples: o Shopify envia o POST e espera um 200 rápido. Se o seu endpoint demora demais, devolve erro, ou não responde, o Shopify considera a entrega falha e reenvia o mesmo evento mais tarde, com novas tentativas ao longo de um período. A consequência é dupla e inevitável:
- Você recebe o mesmo evento repetido. Se cada recebimento dispara uma conversão, uma venda vira duas, três, quantas forem as tentativas.
- Você recebe eventos fora de ordem. Um
orders/paidpode chegar antes de umorders/createreenviado; umorders/updatedpode chegar antes do estado que o originou.
A resposta correta a isso tem uma regra de ouro: responda 200 rápido e processe depois. O endpoint do webhook não deve enviar eventos às plataformas de forma síncrona enquanto o Shopify espera. Ele deve validar o mínimo, gravar o evento numa fila (ou tabela de entrada) e responder 200 imediatamente. O processamento pesado — hash de PII, chamadas às APIs de conversão, retries — acontece de forma assíncrona, fora do caminho da resposta.
Fazer o contrário é convidar o desastre: se a chamada ao CAPI demora, o Shopify estoura o timeout, marca a entrega como falha e reenvia — e agora você tem uma chamada em andamento e outra chegando para o mesmo pedido.
Idempotência mínima#
Como o mesmo evento chega mais de uma vez, o processamento precisa ser idempotente: processar o pedido id duas vezes deve ter o mesmo efeito que processá-lo uma. A chave natural é o próprio id do pedido, registrado num controle de "já processados" antes de disparar a conversão. Este artigo só toca no ponto de leve porque idempotência de eventos merece — e tem — um tratamento dedicado; o que importa aqui é reconhecer que sem ela a entrega at-least-once vira contagem dupla garantida.
Do webhook ao evento de conversão#
Com o orders/paid validado e desduplicado, a transformação em evento de conversão segue um caminho previsível:
- Mapear o pedido pago para o evento canônico de cada plataforma —
Purchasena Meta,CompletePaymentno TikTok,conversion/purchaseno Google. - Extrair e normalizar a PII (
email,phone), aplicar hash e montar os parâmetros de correspondência. - Passar o
iddo pedido comoevent_idestável, para que a plataforma deduplique este envio server-side contra o disparo do pixel no navegador. - Recuperar os parâmetros de clique de
landing_sitepara preservar a atribuição. - Enviar o valor como número, com a moeda correta.
O id do pedido reaparece três vezes — como chave de idempotência interna, como event_id de deduplicação e como referência para reconciliação. Não é coincidência: um bom identificador de negócio atravessa todas as camadas.
Erros comuns que derrubam vendas do relatório#
- Contar
orders/createcomo venda — infla com pedidos não pagos e cancelados. - Ignorar
orders/refunded/orders/cancelled— a venda entrou no relatório e o reembolso nunca saiu; a receita medida diverge da real. - Confiar na ordem de chegada — tratar a sequência dos webhooks como garantida quebra na primeira reentrega.
- Processar de forma síncrona — segurar o Shopify enquanto chama o CAPI provoca timeout, reentrega e duplicação.
- Não verificar a assinatura HMAC — sem validar a origem, qualquer POST forjado vira conversão fantasma (a verificação tem artigo próprio, mas a ausência dela é um furo real).
- Ignorar a moeda em loja multi-mercado — enviar tudo como se fosse uma moeda só distorce o valor.
Um checklist de robustez#
Antes de considerar a integração pronta, confirme:
- Assina
orders/paid(e tratarefunded/cancelled), nãoorders/createcomo conversão. - Responde
200em milissegundos e enfileira o processamento. - Deduplica pelo
iddo pedido antes de disparar qualquer conversão. - Verifica
financial_status == paidcomo blindagem. - Normaliza e faz hash da PII; nunca envia texto puro.
- Envia valor numérico e a
currencycorreta por pedido. - Recupera
fbclid/gcliddelanding_sitepara não perder a atribuição. - Fixa a versão da API do Shopify e trata a atualização como mudança de contrato.
Reembolsos e cancelamentos: a conversão que precisa ser desfeita#
Um ponto que quase toda integração esquece é o outro lado da venda. O orders/paid faz a receita entrar no relatório, mas nada a faz sair quando o cliente é reembolsado ou o pedido é cancelado. O efeito é uma distorção que só cresce: a receita medida se descola da receita real, e a diferença é exatamente o volume de estornos que nunca foi contabilizado. Em categorias com devolução alta — moda, eletrônicos —, esse descompasso pode ser grande o bastante para envenenar a otimização por valor, porque a plataforma continua acreditando que compras estornadas foram receita boa.
O tratamento correto assina também orders/refunded (e orders/cancelled quando aplicável) e envia o evento negativo que cada plataforma oferece — um Refund, um ajuste de conversão, uma conversão de valor negativo. Alguns pontos de disciplina:
- Amarre o estorno ao pedido original pelo mesmo
id, para que a plataforma saiba qual conversão está sendo revertida. - Reembolso parcial não é total — devolver a diferença exige enviar o valor efetivamente estornado, não o valor cheio do pedido.
- Respeite a janela — estornos muito posteriores podem cair fora da janela em que a plataforma aceita ajuste; meça esse atraso para saber o que ainda é reversível.
Sem esse fechamento do ciclo, o relatório de conversão é uma conta que só soma e nunca subtrai — e uma conta assim sempre mente para mais.
O panorama completo#
Uma integração de webhook do Shopify não é difícil de ligar — é difícil de manter correta sob entrega não confiável. A diferença entre a que funciona na demonstração e a que sobrevive à Black Friday está inteira nessas decisões: o evento certo, a resposta rápida, a idempotência e o respeito ao fato de que o mesmo pedido vai bater à sua porta mais de uma vez.