Esta página explica por que a integração é desenhada como é do ponto de vista de segurança. Ela é conceitual: a mecânica detalhada — como montar um JWS, o formato do header, como assinar na prática — fica em Assinatura JWS na prática. Aqui o objetivo é você entender três coisas: por que cada chamada é assinada, por que existem duas chaves por servidor e por que o mesmo identificador (jti) significa coisas opostas em rotas diferentes.
Em linguagem acessível: a Lootfy e o seu servidor conversam por uma rede pública, e parte dessa conversa passa pela máquina do jogador, que não é confiável. A assinatura é o que garante que uma mensagem veio mesmo de quem diz ter vindo e não foi adulterada no caminho. Sem ela, qualquer um poderia forjar uma ordem de entrega ou um anúncio.
Toda chamada entre o seu servidor e a Lootfy é assinada com JWS, uma assinatura criptográfica que viaja junto com o payload. Ela garante autenticidade e integridade — confiança na origem —, mas não esconde o conteúdo (isso seria cifra, um conceito diferente). A assinatura é obrigatória nos dois sentidos: nas chamadas que o seu servidor faz à Lootfy e nos webhooks que a Lootfy envia ao seu servidor.
A assinatura é assimétrica: cada lado assina com sua própria chave privada e a contraparte valida com a chave pública correspondente. O algoritmo é RS256 (RSA 2048), fixo. Ele foi escolhido por interoperabilidade, não por elegância: o plugin do lado do servidor é responsabilidade sua, e RSA está disponível em praticamente toda biblioteca de cripto, inclusive as antigas. Trocar de algoritmo depois exigiria reintegrar todos os parceiros, então trate-o como decisão fixa.
Cada servidor integrado tem dois pares de chaves de assinatura, não um — e a distinção importa.
Par SERVER
O par do seu servidor. Você assina com a privada; a Lootfy valida com a pública. Cobre as chamadas que partem do seu lado: as operações de transação (/v1/api/transactions/*), vincular personagem (POST /v1/api/character-links) e as operações de anúncio (/v1/api/market/listings).
Par PLATFORM
Um par da Lootfy dedicado ao seu servidor. A Lootfy assina os webhooks de ordem de transferência (evento transfer_order) e de estado terminal (evento transaction_terminal); você valida com a pública dela. É uma chave por parceiro, nunca global.
Quando o seu par SERVER é gerado, a Lootfy entrega a chave privada uma única vez e guarda apenas a pública. Não existe re-download: se você perde o arquivo, recebe uma chave nova por rotação, nunca a mesma de volta.
Isso é estrutural, não uma cortesia. Para validar as suas chamadas, a Lootfy só precisa da sua chave pública — reter a privada não habilitaria nenhuma função dela. Mas, com a sua privada em mãos, a Lootfy conseguiria forjar uma confirmação de entrega (POST /v1/api/transactions/{transactionId}/delivery), que é a fonte da verdade que libera o dinheiro. O não-repúdio — o único motivo de a assinatura ser assimétrica em vez de um segredo compartilhado — deixaria de existir. Por isso a ausência da sua privada no armazenamento da Lootfy é uma propriedade do desenho: a linha dela simplesmente não tem onde guardá-la.
Uma assinatura válida prova origem e integridade, mas não prova que a mensagem é recente. Uma requisição assinada e capturada — de um log, de um proxy que termina TLS, da infra do próprio parceiro — validaria indefinidamente se reenviada. E entrega duplicada nem sempre é ataque: os webhooks preveem retry com backoff.
Por isso, todo envelope assinado carrega, além da assinatura, um iat e um exp de validade curta e um jti único. A validade da assinatura é deliberadamente menor que o tempo em que a Lootfy retém o registro de idempotência: quando esse registro é podado, a assinatura correspondente já expirou, e não sobra instante em que um replay seja executável.
Aqui está o ponto que mais confunde na integração. O jti é um identificador único por envelope, mas o que ele faz depende da rota — e os dois comportamentos são opostos. Prestar atenção a isso evita erros difíceis de diagnosticar.
Rotas transportadas pelo client — jti é chave de idempotência
Nas duas rotas signature-only (criar transação, POST /v1/api/transactions, e registrar anúncio, POST /v1/api/market/listings), reusar o mesmo jti num retry devolve o mesmo resultado. Um retry de rede e um replay malicioso são bit a bit idênticos; recusar o segundo puniria o primeiro. Então repetir é seguro e esperado.
Demais rotas assinadas — jti é nonce
Nas rotas service — consultar transação (GET /v1/api/transactions/{transactionId}), confirmar entrega, reportar falha de entrega (POST /v1/api/transactions/{transactionId}/delivery-failure), vincular personagem (POST /v1/api/character-links) e ajustar ou retirar anúncio (PUT /v1/api/market/listings) —, o jti é um nonce: repetir um jti já visto devolve 401. Gere um jti novo a cada tentativa, inclusive nos retries.
As rotas service autenticam com API key mais assinatura. As duas rotas transportadas pelo client — criar transação e registrar anúncio — autenticam com apenas a assinatura, sem API key. O motivo é direto: essas chamadas passam pela máquina do jogador, e uma API key distribuída em máquina de jogador é uma API key vazada. A assinatura do seu servidor, sozinha, é a credencial — e ela é segura porque a chave privada nunca sai do seu servidor; só a assinatura pronta viaja pelo client.
A Lootfy prevê JWE — cifra que garantiria confidencialidade do conteúdo — de forma seletiva, apenas para payloads sensíveis. No MVP, porém, nenhum payload das operações da integração carrega dado que a justifique: dados de KYC vivem no PSP e o Pix roda no PSP. Por isso o JWE não é exercido hoje, e isso é decisão, não lacuna. Trate-o como capacidade planejada: quando surgir um payload que a justifique, o par de cifra entra pela mesma via de rotação já existente, sem reintegrar o parceiro.
Assinatura JWS na práticaA mecânica detalhada: formato do header, envelope detached e como assinar cada chamada.