Este guia cobre duas peças da plataforma que ficam em lados opostos de cada mudança de estado:
  • Webhooks de saída para o lojista — as chamadas HTTPS que a plataforma dispara para o seu backend quando algo acontece (por exemplo, o status de uma transação muda).
  • O sistema interno de rotinas — o mecanismo de execução em processo que busca dados da infraestrutura de processamento, atualiza modelos e, por fim, decide quando chamar o seu webhook.
O mecanismo interno que a plataforma utiliza para receber atualizações da infraestrutura de processamento é intencionalmente omitido aqui — esses endpoints são internos da plataforma e não fazem parte da superfície pública.

Webhooks de saída

Um lojista registra um ou mais Webhooks, cada um apontando para uma URL no backend do lojista. Quando a plataforma termina de processar uma mudança de estado relevante, ela percorre os webhooks do lojista e faz um POST com um payload JSON para cada correspondência.

Registrando um webhook

Webhooks são criados com POST /integrations/webhooks/{seller_id}/webhooks/ e atualizados com PATCH /integrations/webhooks/{seller_id}/webhooks/{id}/. Uma assinatura é composta por três campos:
CampoSignificado
urlPara onde fazer o POST. A plataforma adiciona automaticamente o prefixo https:// se você omitir o esquema.
secretUm segredo compartilhado. A plataforma o envia de volta dentro do corpo para que seu handler possa autenticar a chamada. Apenas escrita — uma vez armazenado, a API nunca o devolve novamente.
eventsUma string livre que determina quais eventos disparam este webhook. A correspondência é por substring, sem diferenciar maiúsculas e minúsculas.
A forma recomendada de preencher events é uma lista separada por vírgulas com nomes canônicos de evento: "transaction.update.status". Se você armazenar um fragmento mais amplo como "transaction.", todo evento futuro cujo nome contenha essa substring também será disparado.

Tipos de evento atualmente emitidos

A plataforma emite os seguintes eventos para webhooks de lojistas:
EventoDisparado quando
transaction.update.statusUma transação pertencente ao lojista foi concluída, falhou ou mudou de estado, após a plataforma ter sincronizado o novo estado com a infraestrutura de processamento.
Esse é o único tipo de evento atualmente despachado. Outros eventos internos (recebíveis, transferências, alterações de documentos, atualizações de lojista) são processados dentro da plataforma, mas não são encaminhados para webhooks de lojista hoje.

Formato do payload

Toda chamada de saída usa o mesmo envelope:
{
  "event": "transaction.update.status",
  "secret": "<o segredo que você registrou>",
  "data": {
    "id": "...",
    "status": "...",
    "amount": 1000,
    "reference_id": "ORDER-12345",
    "...": "..."
  }
}
  • event — o nome canônico do evento (atualmente sempre transaction.update.status).
  • secret — o valor exato que você forneceu ao criar o webhook. Use-o para autenticar a chamada.
  • data — a transação serializada. Se a transação estiver vinculada a uma venda online, data.reference_id carrega a referência fornecida pelo lojista para essa venda.

Verificando o segredo

Não há assinatura HMAC hoje. A verificação é uma comparação direta de strings entre o campo secret no corpo e o valor que você armazenou ao criar o webhook. Duas consequências importantes:
  • A conexão TLS é a única coisa que protege o segredo em trânsito. Sempre exponha sua URL de webhook em https://.
  • Se você acidentalmente expuser o segredo em logs ou no reporte de erros, faça a rotação imediatamente via PATCH no webhook com um novo valor.
import os
from flask import Flask, request, abort, jsonify

app = Flask(__name__)
WEBHOOK_SECRET = os.environ["DLPAY_WEBHOOK_SECRET"]

@app.post("/hooks/dlpay")
def dlpay_webhook():
    payload = request.get_json(silent=True) or {}

    if payload.get("secret") != WEBHOOK_SECRET:
        abort(401)

    event = payload.get("event")
    data = payload.get("data") or {}

    if event == "transaction.update.status":
        handle_transaction_update(data)
    else:
        return jsonify({"ignorado": True}), 200

    return jsonify({"ok": True}), 200


def handle_transaction_update(tx):
    transaction_id = tx["id"]
    new_status = tx["status"]
    reference_id = tx.get("reference_id")
    pass

Como o despacho acontece

O despachante roda como última etapa da rotina de sincronização de transação, depois que o modelo da venda foi sincronizado e os status derivados (checkouts de evento, pagamentos de assinatura) foram atualizados. Conceitualmente: O despachante é fire-and-forget com um timeout HTTP de 5 segundos. Não existe fila automática de retentativas: se o seu endpoint sofrer timeout ou retornar um status diferente de 2xx, a plataforma registra a falha e segue adiante. A próxima mudança de estado na mesma transação produzirá uma nova entrega.

Boas práticas

  • Seja idempotente. Re-entregas não são garantidas, mas são possíveis — qualquer mudança de estado na mesma transação dispara outro transaction.update.status para o mesmo data.id. Use data.id (junto com data.status) como sua chave de idempotência.
  • Responda rápido. O despachante te dá 5 segundos. Responda com 200 imediatamente e faça o trabalho pesado em uma rotina de segundo plano no seu lado.
  • Ignore eventos desconhecidos. O conjunto de eventos despachados pode crescer ao longo do tempo. Trate qualquer evento diferente dos que você explicitamente manuseia como um no-op 200, não como erro.
  • Faça a rotação do segredo via PATCH. Não há um endpoint separado de rotação — basta fazer PATCH no webhook com um novo secret. Atualize sua cópia armazenada antes de enviar a requisição e depois atualize na plataforma.
  • Filtre de forma estrita. Como events é uma correspondência por substring, prefira armazenar o nome canônico exato (transaction.update.status) em vez de um fragmento (transaction.). Isso te protege no futuro contra novos eventos que compartilhem o prefixo.
  • Valide o lojista do seu lado. Um único backend pode atender várias contas DL Pay; baseie seu roteamento no identificador da conta embutido na URL ou em data.account / data.seller, e não apenas no segredo.

O sistema interno de rotinas

Tudo descrito acima — buscar dados da infraestrutura de processamento, persistir o estado e finalmente chamar o seu webhook — acontece dentro de uma única rotina. O sistema de rotinas é o modelo de execução em processo da plataforma.

Como as rotinas são organizadas

Cada módulo da plataforma que precisa executar trabalho expõe seu conjunto de rotinas em duas seções:
  1. Lançadores — pequenas funções utilitárias que solicitam a execução de uma rotina à fila de jobs, passando o nome fixo e os parâmetros adicionais.
  2. Funções de rotina — o trabalho propriamente dito. Recebem os parâmetros e um logger injetado.
Rotinas são registradas no boot da plataforma associando um nome a uma função, e opcionalmente a uma pré-condição que precisa ser satisfeita antes da execução.

Ciclo de vida e status de uma rotina

Toda rotina lançada é persistida como um registro de tarefa. Seu status percorre:
StatusSignificado
AGUARDANDOCriada, ainda não executada.
AGUARDANDO_PRECONDICAOUma pré-condição registrada retornou falso. Uma escala de tentativas reagenda a execução em 3, 5, 10, 15 e 30 minutos — depois passa para ERRO.
PROCESSANDOPrimeira execução em andamento.
REEXECUTANDOUma reexecução após uma tentativa anterior.
SUCESSORotina concluída sem lançar exceção.
ERROA rotina lançou exceção, ou as pré-condições falharam além do orçamento de retentativas. Reportado para o sistema de monitoramento de erros.
Quando uma rotina lança exceção, a mensagem e o stack trace completo são salvos no registro de tarefa correspondente. Use a área administrativa para investigar falhas.

Deduplicação por resource_id + tipo de rotina

Ao solicitar uma nova execução, a fila de jobs procura um registro de tarefa existente com o mesmo (resource_id, tipo) cujo status seja AGUARDANDO ou AGUARDANDO_PRECONDICAO. Se houver, essa entrada é reaproveitada e nenhuma nova rotina é criada. É assim que a plataforma absorve eventos duplicados vindos da infraestrutura de processamento: uma rajada de webhooks recebidos para a mesma transação colapsa em uma única rotina de sincronização. Duas consequências para quem chama:
  • O resource_id deve ser a chave natural do recurso sendo processado (por exemplo, o identificador externo da transação, o identificador do documento unificado).
  • A deduplicação se aplica apenas a trabalho pendente. Assim que uma rotina muda para PROCESSANDO / SUCESSO / ERRO, o próximo lançamento com o mesmo resource_id cria um novo registro.

Encadeamento

Rotinas frequentemente se encadeiam. Uma rotina de busca no lado da venda (infraestrutura de processamento → registro da venda) finaliza lançando uma rotina de atualização no lado unificado (registro da venda → registro unificado). Isso mantém cada rotina focada em uma única responsabilidade — buscar, persistir e repassar — e permite que a lógica de deduplicação agrupe o segundo passo de forma independente do primeiro.

Execução imediata vs. enfileirada

A fila de jobs aceita uma flag de execução imediata. Quando ligada, a rotina roda inline dentro da requisição ou rotina atual. Sem a flag, a rotina permanece em AGUARDANDO e é capturada pelo executor de segundo plano. A plataforma usa execução imediata para trabalho orientado a webhook recebido que precisa se estabilizar antes do receptor retornar (o receptor de webhooks da infraestrutura de processamento não deve retornar antes do estado da venda e do unificado estar consistente). Sincronizações em lote e atualizações agendadas deixam essa flag desligada e permitem que o executor faça o ritmo.

Webhook → Rotina → Sincronização — fluxo de ponta a ponta

O fluxo completo, de um evento da infraestrutura de processamento até um webhook de saída para o lojista, fica assim: A ideia central: no momento em que a sua URL é chamada, a visão que a plataforma tem da transação já está atualizada. Você pode tranquilamente buscá-la novamente via GET /sales/transaction/{id}/ ou pelo endpoint de transação unificada e obter o mesmo status que você acabou de receber em data.status.

Armadilhas comuns

  • A chamada de saída não é assinada. O único marcador de autenticação é o campo secret dentro do corpo JSON. Não há cabeçalho X-Signature e não há HMAC sobre o payload. Trate o secret como uma senha e exponha sua URL de webhook apenas sobre HTTPS.
  • O campo events é uma correspondência por substring. Armazenar "transaction" casará com todo evento futuro cujo nome contenha essa string. Prefira o nome canônico exato, a não ser que você queira deliberadamente uma assinatura abrangente.
  • Apenas transaction.update.status é emitido hoje. Não construa caminhos de código que dependam de outros nomes de evento — a plataforma não os dispara. Se você precisar de sinais mais granulares (por exemplo, para recebíveis ou transferências), abra um pedido em vez de depender de tipos de evento não documentados.
  • Não há política de retentativa por webhook. Uma entrega que falha (timeout, status diferente de 2xx, erro de rede) é registrada e descartada. A próxima mudança de estado na mesma transação dispara uma nova entrega. Construa handlers idempotentes e reconcilie via API para eventos perdidos.
  • Chaves de API são tokens Bearer, nada mais. A chave de API expõe apenas name, description, key e active — não há prefix, expires_at nem escopo de permissão por chave. Permissões são concedidas no nível do lojista/usuário, não por chave.
  • A deduplicação de rotinas só se aplica a registros pendentes. Se você relançar uma rotina enquanto a anterior está PROCESSANDO, um segundo registro de tarefa é criado. Isso é intencional (uma transação que mudou duas vezes em meio ao processamento deve ser buscada novamente), mas é bom saber ao ler os logs administrativos.

Leitura relacionada