Para receber webhooks da OpenAI com segurança, valide a assinatura sobre o corpo original da requisição e só depois aceite o evento. Para impedir processamento duplicado, grave a identificação da entrega em armazenamento persistente com restrição de unicidade e faça o trabalho pesado em uma fila. A confirmação HTTP deve significar que a aplicação assumiu responsabilidade pelo evento.
São duas proteções diferentes: a assinatura ajuda a verificar a origem e a integridade da mensagem; a idempotência faz repetidas tentativas produzirem um único efeito de negócio. O desenho abaixo é uma recomendação de arquitetura para quem conecta a API a sistemas internos. Os recursos relacionados estão reunidos na página de integrações com OpenAI e ChatGPT.
Resumo rápido
- Preserve o corpo bruto e os cabeçalhos até a verificação pelo SDK oficial.
- Use o segredo de assinatura do endpoint, guardado fora do código.
- Deduplicate entregas com
webhook-ide uma operação atômica no banco.- Responda com sucesso depois da gravação durável, antes do processamento demorado.
- Proteja também a ação final, como atualizar um pedido ou emitir uma notificação.
- Teste falhas entre cada etapa; receber duas vezes precisa ser um cenário normal.
O contrato de entrega que sua aplicação precisa respeitar
Segundo o guia de webhooks da OpenAI, a plataforma envia uma requisição POST ao endereço configurado. A documentação recomenda resposta rápida com código 2xx e processamento não trivial fora do receptor. Entregas podem se repetir; o cabeçalho webhook-id é indicado como chave de deduplicação. Redirecionamentos HTTP não são seguidos. Isso torna relevante cadastrar o endereço final, inclusive o caminho e a configuração HTTPS corretos.
Trate o receptor como uma portaria com protocolo de entrada. Ele confere a autenticidade, registra o recebimento e libera o processamento. Não é o lugar para gerar um relatório, aguardar outro modelo ou enviar dezenas de mensagens. Quanto mais tarefas ficam dentro da mesma conexão, mais pontos existem para uma interrupção deixar o resultado incerto.
Uma resposta 200 isolada não prova que o pedido foi atendido. Ela encerra a conversa de transporte com o remetente. Se seu servidor devolve sucesso e cai antes de salvar o evento, a empresa perde a possibilidade de recuperação pelo próprio registro. Por isso, defina uma fronteira explícita: primeiro persista; depois confirme. A fila pode ser uma infraestrutura dedicada ou uma tabela de tarefas duráveis, desde que sobreviva ao encerramento do processo.
Validar a assinatura sem alterar a mensagem
O SDK Python oficial oferece client.webhooks.unwrap(), que verifica e interpreta o evento, e verify_signature(), para separar a verificação da interpretação. O repositório mantido pela OpenAI explica que a entrada deve ser a string JSON original. Converter o corpo em objeto e serializar novamente pode mudar espaços, ordem de propriedades ou codificação. Preserve a representação recebida.
O trecho abaixo mostra somente a etapa de verificação em uma rota Flask. Ele não é um servidor completo: persistência, tratamento de falhas do banco, limite de tamanho e configuração do processo precisam ser adicionados. As variáveis de ambiente são carregadas pelo ambiente de execução, sem colocar credenciais no arquivo.
import os
from flask import request
from openai import OpenAI
client = OpenAI()
secret = os.environ["OPENAI_WEBHOOK_SECRET"]
def verificar_evento():
corpo = request.get_data(as_text=True)
return client.webhooks.unwrap(
corpo,
request.headers,
secret=secret,
)
Na aplicação real, trate a exceção de assinatura inválida como entrada recusada e registre apenas um motivo sanitizado. Uma falha do banco depois da verificação é outra classe de problema: não deve aparecer como assinatura inválida. Separar esses caminhos reduz investigações erradas, como trocar o segredo quando o disco ficou indisponível.
Também verifique a cadeia antes da rota. Um middleware pode consumir o corpo, um proxy pode alterar o caminho e uma biblioteca pode normalizar dados cedo demais. Teste o percurso completo usado em produção. Restrinja acesso ao segredo e estabeleça procedimento de troca com teste controlado. O segredo do webhook e a chave usada para chamar a API têm funções diferentes; registre isso no inventário de credenciais da equipe.
Deduplicação persistente e efeito de negócio
Uma lista em memória resolve apenas demonstrações. Ela desaparece quando o processo reinicia e não coordena duas instâncias do serviço. A recomendação é uma tabela de entrada com chave única composta pelo fornecedor, pelo contexto interno do endpoint e pelo identificador de entrega. O contexto impede misturar ambientes ou organizações que compartilham infraestrutura.
| Camada | Identificação recomendada | Proteção oferecida |
|---|---|---|
| Recebimento | Contexto e webhook-id | Reconhece a mesma entrega já aceita |
| Evento | ID do evento e tipo | Ajuda investigação e correlação |
| Recurso | ID da resposta vinculada à tarefa | Relaciona resultado ao pedido interno |
| Ação final | Pedido e versão da ação | Evita repetir atualização ou envio |
Faça a tentativa de inserção de maneira atômica. Consultar se existe e inserir depois cria uma corrida: dois processos podem ler “não existe” antes de qualquer inserção. Uma restrição UNIQUE no banco arbitra essa disputa. O processo que encontrar conflito deve conferir que o registro pertence ao mesmo contexto e já representa uma aceitação durável.
Um fluxo recomendado, em pseudocódigo, deixa a sequência visível:
verificar assinatura e interpretar evento
iniciar transação
tentar inserir evento na caixa de entrada com chave única
se inseriu:
criar tarefa pendente vinculada ao evento
confirmar transação
responder 2xx
Salvar a entrada e criar a tarefa na mesma transação evita um intervalo em que o evento existe mas nenhum trabalhador sabe que precisa executá-lo. Se houver mensageria externa, considere uma tabela de saída transacional, também chamada outbox: o banco guarda a intenção e um despachante entrega a tarefa à fila. Não existe transação implícita entre um INSERT e uma chamada de rede.
O efeito final também precisa de proteção. Imagine que o trabalhador envia um aviso e cai antes de marcar a tarefa como concluída. A repetição pode enviar outro aviso. Quando o destino aceita uma chave idempotente, use a chave estável da ação. Quando não aceita, registre a intenção, a resposta e um estado de resultado incerto; encaminhe para reconciliação antes de repetir um envio sensível. Deduplicar o webhook não cria garantia universal de execução única.
Organizar o trabalhador e recuperar interrupções
Separe estados como pendente, em processamento, concluído, aguardando nova tentativa e revisão manual. O trabalhador deve assumir uma tarefa com exclusão mútua ou uma reserva com prazo. Se morrer, outro processo poderá retomá-la depois do prazo, sem abandonar tarefas indefinidamente. Registre tentativa, horário, erro resumido e próxima ação.
Para eventos de conclusão de resposta, associe o recurso a uma tarefa previamente criada pela sua aplicação. A referência de eventos da OpenAI descreve os tipos e seus campos. Aceitar uma assinatura não autoriza ligar qualquer resposta a qualquer cliente: a associação entre organização, tarefa e recurso deve existir no seu banco.
Quando precisar reconciliar uma resposta executada em segundo plano, o guia de background mode documenta a consulta de seu estado pela API de Responses. Use essa consulta como mecanismo de recuperação de tarefas conhecidas. Ela não substitui a validação da entrega, nem justifica consultas contínuas sem limite. Guarde o identificador retornado na criação para conseguir encontrar o recurso depois.
Configure limites de tentativas, intervalo crescente e uma fila de exceções com responsável. Observe idade da tarefa mais antiga, falhas de persistência e tarefas presas em processamento. Métricas só de HTTP 200 escondem problemas do trabalhador. Para organizar a correlação, veja o guia de observabilidade de erros, latência e custo.
Exemplo: agência de marketing em Curitiba
Considere uma agência fictícia em Curitiba que produz rascunhos de análise para clientes de comércio local. O painel cria uma tarefa interna, envia a solicitação à OpenAI e salva o ID da resposta. Ao receber o evento de conclusão, a aplicação precisa anexar o rascunho à tarefa correta e avisar o analista responsável.
O receptor verifica a assinatura, grava a entrada e cria uma tarefa de processamento na mesma transação. O trabalhador encontra a tarefa original pelo ID da resposta, consulta o resultado quando necessário e salva uma versão de rascunho. A notificação ganha uma chave própria, formada pela tarefa e pela versão salva. Uma segunda entrega do webhook encontra a entrada existente e termina sem criar outra tarefa.
Durante um exercício, a equipe interrompe o trabalhador depois de salvar o rascunho. Ao retomar, a aplicação percebe que aquela versão já existe e continua apenas a etapa de notificação. Depois, simula falha após solicitar o aviso ao sistema externo. Como o resultado ficou incerto, a tarefa entra em reconciliação. O desenho evita transformar incerteza em autorização automática para enviar novamente.
O teste não promete redução de custos ou de tempo. Ele demonstra critérios verificáveis: um rascunho associado ao cliente certo, uma ação registrada por versão e exceções visíveis para a operação. Use dados fictícios durante o ensaio e reduza informações pessoais nos registros, seguindo os cuidados de mascaramento de dados pessoais antes de usar APIs.
Erros comuns
- Validar depois de interpretar e reconstruir o JSON. Preserve a entrada original até o SDK terminar a verificação.
- Responder antes do commit. O sucesso de transporte deve ocorrer depois da aceitação durável.
- Usar apenas um indicador “processado”. Ele não explica tarefas interrompidas nem resultados externos incertos.
- Apagar registros de deduplicação cedo demais. Defina retenção considerando a janela de entrega documentada, reprocessamentos internos e necessidade de auditoria.
- Confiar na ordem de chegada. Desenvolva transições de estado que não rebaixem um resultado final por causa de um evento atrasado.
- Registrar corpo e cabeçalhos completos por padrão. Prefira IDs, estados e erros sanitizados; restrinja o acesso ao conteúdo quando necessário.
Antes de liberar, repita a mesma entrega em paralelo, reinicie o receptor após o commit, desligue o banco e interrompa o trabalhador antes e depois da ação final. Para cada falha, escreva o resultado esperado. Um teste que só mostra o caminho feliz não comprova idempotência.
Perguntas frequentes
A assinatura impede eventos duplicados?
Não. Ela verifica a mensagem recebida; deduplicação exige estado persistente. Um evento autêntico pode chegar novamente. Mantenha as duas proteções separadas nos testes e nas métricas.
Posso usar o ID da resposta para tudo?
Use cada identificação para sua finalidade. A resposta liga o resultado à tarefa; a entrega identifica o recebimento; a ação final pode ter sua própria versão. Misturar essas camadas dificulta distinguir repetição legítima de uma nova operação.
Quando devolver sucesso para uma duplicata?
Quando a aplicação confirmar que aquela entrega já foi aceita de forma durável no contexto correto. O processamento pode continuar na fila. Se não for possível consultar ou persistir o estado, não finja que a aceitação aconteceu.
Preciso guardar o corpo inteiro para sempre?
Não. Defina retenção e acesso conforme a finalidade do sistema. Metadados mínimos podem bastar para deduplicação; conteúdo adicional pode ser necessário para recuperação. Documente o motivo e elimine dados quando deixarem de ser necessários.
Esse fluxo garante exatamente uma execução?
Não de ponta a ponta. Ele reduz duplicações e permite recuperação. A garantia depende também das transações locais e do comportamento de cada sistema externo, especialmente quando uma chamada termina sem resposta conhecida.