Técnico · 30 de janeiro de 2026 · Por João Pereira, Founder, Build Up Labs · Atualizado em 28 de agosto de 2026 · 8 min

Webhooks do Stripe: guia prático para faturação

Como tratar webhooks Stripe para faturação portuguesa sem duplicar documentos: assinatura, idempotência, ordem dos eventos e Stripe Connect.

Na faturação portuguesa, um webhook Stripe só é seguro quando valida a assinatura sobre o corpo original, regista o evento antes de responder, processa a emissão de forma assíncrona e aplica idempotência ao evento e ao pagamento. Estas camadas evitam FT ou FR duplicadas quando as notificações chegam repetidas, atrasadas ou fora de ordem.

Porque exigem os webhooks dois níveis de idempotência?

Um webhook é uma notificação HTTP enviada pelo Stripe quando muda o estado de um pagamento ou de uma fatura Stripe. A entrega é feita pelo menos uma vez: se o endpoint não responder com sucesso, o Stripe pode repetir a mesma notificação. Em produção, as novas tentativas podem continuar durante até três dias, com intervalos crescentes.

Guardar apenas o identificador do evento, como evt_123, evita processar duas entregas desse evento. Não impede, porém, que dois eventos diferentes representem o mesmo pagamento. Uma renovação pode originar payment_intent.succeeded e invoice.payment_succeeded, cada um com o seu identificador. Se ambos iniciarem a emissão, surgem dois documentos fiscais válidos e sequenciais que depois exigem correção.

CamadaChave estávelFalha que evita
EntregaOrganização + ID do eventoRepetição do mesmo webhook
EmissãoOrganização + fatura Stripe ou PaymentIntentEventos de sucesso diferentes para a mesma cobrança
DocumentoOrganização + fornecedor + ID do documentoRepetição após sucesso no fornecedor

Exemplo: duas entregas de evt_123 são travadas pela primeira camada; evt_123 e evt_456 para a mesma fatura Stripe são reunidas pela segunda. Esta chave de emissão aplica-se apenas aos eventos de sucesso que podem criar o documento. Reembolsos, disputas e tentativas falhadas conservam chaves próprias, pois pode haver várias ações legítimas para o mesmo pagamento.

Como se verifica a assinatura sem alterar o corpo?

A assinatura depende dos bytes exatos enviados pelo Stripe. O endpoint deve ler o pedido uma única vez como texto ou bytes, obter o cabeçalho Stripe-Signature e chamar a biblioteca oficial com esse corpo original e o segredo whsec_.... Só depois da verificação se deve interpretar o JSON.

Um componente intermédio que converte o corpo em objeto e volta a serializá-lo pode mudar espaços, escapes ou a ordem das propriedades. O conteúdo continua a parecer JSON válido, mas já não corresponde à assinatura. Um pedido sem assinatura, com assinatura inválida ou com corpo vazio deve receber uma resposta 400 e não pode entrar na fila de faturação.

Exemplo: em vez de aceder a um request.body já analisado, o endpoint lê request.text(), verifica a assinatura e apenas então trata event.data.object.

Quando deve o endpoint responder com 2xx?

A resposta deve ser rápida, mas não prematura. Antes do 2xx, a integração valida a assinatura, identifica a organização, reserva a chave idempotente numa operação atómica e guarda os dados mínimos para continuar. Depois envia trabalho durável para uma fila e responde ao Stripe. A chamada ao fornecedor de faturação fica fora do pedido HTTP.

  1. Ler o corpo original e verificar a assinatura.
  2. Resolver a organização ou conta Connect do evento verificado.
  3. Registar a chave idempotente e a transação de forma durável.
  4. Colocar a emissão numa fila persistente.
  5. Responder com 2xx e deixar o processador assíncrono concluir a emissão.

Exemplo: se a API de faturação estiver indisponível durante 40 segundos, o endpoint não espera. O Stripe recebe 2xx após o trabalho ter sido guardado, e o processador tenta novamente segundo uma política controlada. Se nem a base de dados nem a fila aceitarem o trabalho, a resposta deve ser de erro para permitir nova entrega.

Como se evita a corrida entre PaymentIntent e fatura Stripe?

Nos pagamentos sem fatura Stripe associada, payment_intent.succeeded pode iniciar o fluxo. Nas subscrições e noutros pagamentos faturados pelo Stripe, invoice.payment_succeeded fornece a fatura Stripe que deve servir de referência. A ordem de entrega não é garantida, pelo que não basta esperar que o evento da fatura Stripe chegue primeiro.

O endpoint deve detetar se o PaymentIntent pertence a uma fatura Stripe antes de reservar a chave de pagamento. Se pertencer, esse evento é reconhecido sem emitir e deixa-se a fatura Stripe ser processada. Esta ordem é importante: reservar primeiro a chave baseada na fatura Stripe e só depois ignorar o PaymentIntent bloquearia o evento correto quando chegasse.

A associação deve respeitar a versão da API configurada e as estruturas efetivamente recebidas. Quando o objeto necessário não está completo, é preferível obtê-lo pela API do Stripe a adivinhar campos. Exemplo: se o PaymentIntent de uma renovação chega às 10:00:00 e o evento da fatura Stripe às 10:00:02, o primeiro é adiado e apenas o segundo cria a transação de faturação.

Qual é a diferença entre invoice.paid e invoice.payment_succeeded?

Os dois eventos podem transportar os mesmos dados da fatura Stripe, mas não são sinónimos. invoice.payment_succeeded indica que uma tentativa de pagamento da fatura Stripe teve sucesso. invoice.paid indica que o estado da fatura passou a pago e também pode ocorrer quando a fatura é marcada como paga fora do Stripe ou liquidada por saldo de crédito do cliente.

EventoSignificado útil para faturaçãoDecisão necessária
payment_intent.succeededPaymentIntent concluídoProcessar apenas se não estiver ligado a uma fatura Stripe
invoice.payment_succeededTentativa de pagamento da fatura Stripe concluídaUsar uma chave baseada na fatura Stripe
invoice.paidFatura Stripe passou ao estado pago por qualquer via suportadaIncluir ou excluir pagamentos fora do Stripe de propósito
charge.refundedReembolso total ou parcial registadoAtualizar a transação e avaliar a correção fiscal

Exemplo: uma fatura marcada manualmente como paga pode originar invoice.paid sem representar uma nova cobrança Stripe. Uma integração que trata esse evento como sinónimo pode emitir um documento para um pagamento que não passou pelo fluxo esperado.

Como se processam eventos repetidos ou fora de ordem?

O processamento não deve depender de uma sequência fixa. Cada evento deve conseguir verificar o estado atual, obter objetos em falta e decidir entre processar, aguardar, ignorar ou enviar para revisão. O registo deve distinguir processing, processed e failed, para que um evento com falha transitória possa ser novamente reservado sem abrir duas execuções concorrentes.

Exemplo: customer.updated pode chegar depois de um evento de pagamento criado mais tarde. A emissão não deve substituir os dados históricos do pagamento por uma fotografia atual sem verificar qual é a fonte correta. Da mesma forma, repetir manualmente um evento já concluído deve terminar como operação sem efeito e devolver sucesso.

E se o fornecedor criar o documento antes de o processador registar o sucesso?

Esta é a falha mais perigosa numa integração fiscal. O fornecedor pode criar uma FT ou FR e a ligação cair antes de a aplicação guardar o ID do documento. Também pode falhar a transação local ou a confirmação da tarefa logo depois da resposta do fornecedor. Repetir cegamente a chamada pode criar um segundo documento legal.

A defesa combina uma chave estável por pagamento, a idempotência oferecida pelo fornecedor quando existe, uma restrição única sobre o ID do documento e um caminho de recuperação. Antes de voltar a emitir, o processador procura um documento já associado à transação e, quando a API o permite, consulta o fornecedor pela referência externa. Se o documento existir mas faltar a relação local, repara-se o registo local da fatura e a ligação à transação sem nova emissão.

Exemplo: o fornecedor devolve doc_789, mas a gravação local falha. Na tentativa seguinte, encontrar doc_789 deve levar à persistência da fatura local, não a outro pedido de criação. Este é um reconhecimento da tarefa assíncrona e não altera o 2xx já devolvido ao Stripe.

Como se identifica corretamente uma conta Stripe Connect?

Num evento Connect recebido, o identificador da conta conectada está na propriedade de nível superior event.account. A organização só deve ser resolvida a partir desse valor depois de a assinatura ter sido verificada. O cabeçalho Stripe-Account tem outra função: é usado em pedidos enviados para a API do Stripe em nome da conta conectada, não para identificar um webhook recebido.

Exemplo: um evento com account: "acct_123" é associado à organização que detém essa ligação. Ao obter depois a fatura Stripe pela API, o pedido de saída usa Stripe-Account: acct_123. Confiar num cabeçalho de entrada com esse nome permitiria encaminhamento incorreto.

Um reembolso ou cancelamento deve emitir automaticamente uma NC?

Não. charge.refunded confirma uma alteração financeira no Stripe, mas não decide sozinho a correção fiscal. FR significa fatura-recibo; NC significa nota de crédito. O cancelamento de uma subscrição também não prova que houve reembolso nem que existe um documento fiscal anterior a corrigir.

O sistema deve primeiro localizar a fatura original, distinguir reembolso total de parcial, verificar se o documento foi finalizado e aplicar a política contabilística adequada. Na ausência de dados ou configuração suficiente, o resultado seguro é revisão manual. O guia de notas de crédito para pagamentos Stripe detalha esta decisão.

Exemplo: cancelar uma subscrição no fim do período não devolve dinheiro e não justifica, por si só, uma NC. Um reembolso parcial de uma FT já emitida exige analisar o valor e o motivo antes de criar a correção.

Como se escolhe entre FT e FR?

O tipo de evento não deve escolher o documento fiscal. FT é fatura; FR é fatura-recibo. Num pagamento Stripe concluído, o pagamento é tratado como liquidado. A escolha entre FT e FR depende da configuração da organização. Separadamente, a emissão em rascunho ou em estado final depende das capacidades do fornecedor e da opção da organização. Os dados fiscais e o imposto são transmitidos a partir do Stripe e do mapeamento configurado, não derivados da localização do cliente.

Exemplo: duas organizações recebem o mesmo tipo de evento. Uma está configurada para FT final e outra para FR em rascunho suportado. O webhook inicia ambos os fluxos sem codificar o tipo documental no nome do evento. Para o enquadramento completo, consulte-se como automatizar a faturação Stripe em Portugal.

Que testes devem anteceder a entrada em produção?

O cenário de sucesso não chega. A Stripe CLI permite encaminhar eventos para um ambiente local e disparar exemplos, mas o segredo apresentado por stripe listen é próprio dessa sessão e não substitui o segredo do endpoint de produção.

  1. Enviar um corpo válido e confirmar a rejeição após alterar um byte.
  2. Entregar o mesmo ID de evento duas vezes em paralelo.
  3. Inverter a ordem entre PaymentIntent e fatura Stripe.
  4. Simular sucesso no fornecedor seguido de falha de persistência.
  5. Testar duas contas Connect e impedir cruzamento entre organizações.
  6. Confirmar que reembolso e cancelamento não criam NC automaticamente.

Exemplo: após repetir dez vezes o mesmo pagamento, deve existir uma única fatura local, uma única ligação à transação e um único ID de documento do fornecedor. A validade do resultado depende ainda dos requisitos de uma fatura portuguesa e não do recibo do Stripe. Veja-se também quando uma fatura Stripe é válida em Portugal.

Perguntas frequentes

Porque não basta guardar o ID do evento Stripe?

Porque o Stripe pode enviar eventos diferentes para o mesmo pagamento e repetir cada entrega. Deve guardar-se o ID de cada evento e, apenas nos eventos de sucesso que iniciam emissão, uma chave estável da fatura Stripe ou do PaymentIntent. Reembolsos e tentativas falhadas conservam chaves próprias.

A assinatura pode ser verificada depois de analisar o JSON?

Não. A verificação exige os bytes originais e o cabeçalho Stripe-Signature antes de qualquer transformação. Analisar e voltar a serializar o JSON pode mudar espaços, escapes ou a ordem das propriedades, fazendo falhar uma assinatura válida. O conteúdo só deve ser interpretado depois da verificação, e o pedido deve ser rejeitado se esta falhar.

invoice.paid é igual a invoice.payment_succeeded?

Não. invoice.payment_succeeded indica que uma tentativa de pagamento da fatura teve sucesso. invoice.paid indica que a fatura passou ao estado pago e também pode ocorrer quando foi marcada como paga fora do Stripe ou liquidada através do saldo de crédito do cliente. O tratamento deve escolher intencionalmente qual evento representa a liquidação faturável.

Um reembolso Stripe deve criar automaticamente uma NC?

Não. O evento charge.refunded confirma uma devolução financeira, não a emissão de um documento fiscal português. Deve localizar-se a fatura original, avaliar o valor e o motivo do reembolso e só então aplicar a política contabilística configurada ou encaminhar o caso para revisão manual.

Fontes

Automatize a faturação Stripe com Faturado

Ligue Stripe, TOConline ou InvoiceXpress e valide o fluxo com preço por utilização.