Pular para o conteúdo
10 min de leitura

Webhooks do Shopify na prática: capturar orders/paid sem perder nem duplicar venda

Por Equipe Owiew ·

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 para InitiateCheckout, 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 alimentar Purchase / 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 a event_id para deduplicação com o pixel. Estável, único por venda, previsível.
  • financial_status — o estado financeiro. Mesmo assinando orders/paid, verifique que vale paid antes de contar; blindagem barata contra surpresa.
  • total_price e currency — 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, currency varia por pedido e precisa acompanhar o valor.
  • customer.email e customer.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, com product_id, quantity e price. Alimentam content_ids e 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:

  1. Você recebe o mesmo evento repetido. Se cada recebimento dispara uma conversão, uma venda vira duas, três, quantas forem as tentativas.
  2. Você recebe eventos fora de ordem. Um orders/paid pode chegar antes de um orders/create reenviado; um orders/updated pode 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:

  1. Mapear o pedido pago para o evento canônico de cada plataforma — Purchase na Meta, CompletePayment no TikTok, conversion/purchase no Google.
  2. Extrair e normalizar a PII (email, phone), aplicar hash e montar os parâmetros de correspondência.
  3. Passar o id do pedido como event_id estável, para que a plataforma deduplique este envio server-side contra o disparo do pixel no navegador.
  4. Recuperar os parâmetros de clique de landing_site para preservar a atribuição.
  5. 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/create como 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 trata refunded/cancelled), não orders/create como conversão.
  • Responde 200 em milissegundos e enfileira o processamento.
  • Deduplica pelo id do pedido antes de disparar qualquer conversão.
  • Verifica financial_status == paid como blindagem.
  • Normaliza e faz hash da PII; nunca envia texto puro.
  • Envia valor numérico e a currency correta por pedido.
  • Recupera fbclid/gclid de landing_site para 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.

Leituras relacionadas

Nenhum comentário ainda

Seja o primeiro a comentar.

Deixe seu comentário

Entre com sua conta Canverly para comentar. Você pode usar a mesma conta em qualquer site da rede.

Entrar com Canverly